JS 中的面向对象 prototype class

栏目: 编程语言 · 发布时间: 5年前

内容简介:面向对象编程是将事物看成一个个对象,对象有自己的属性有自己的方法。比如人,我们先定义一个对象模板,我们可以定义一些属性 比如,名字年龄和功能,比如走路。我们把这个叫做类。然后帮们将具体数据传入模板,成为一个个具体的人,我们将它叫做实例。

面向对象编程是将事物看成一个个对象,对象有自己的属性有自己的方法。

比如人,我们先定义一个对象模板,我们可以定义一些属性 比如,名字年龄和功能,比如走路。我们把这个叫做类。

然后帮们将具体数据传入模板,成为一个个具体的人,我们将它叫做实例。

JS 中面向对象是使用原型( prototype )实现的。

function Person(name, age) {
    this.name = name
    this.age = age
    this.walk = function(){}
}

Person.prototype.walk = function () {}

var bob = new Person('bob', 10)
console.log(bob.age)
复制代码

其中的 Person 函数叫做构造函数,构造函数一般会将第一个字母大写, 构造函数创建特定类型的对象,构造函数中没有,显式的创建对象,和返回对象,直接将属性赋值给 this

我们使用 new 关键字创建对象实例,它会经历 4 个步骤,

constructor

我们也可以将 walk 函数写在构造函数中 this.walk=function(){} ,但是这样写的话,每新建一个实例,实例都会新建一个 walk 函数,这样就浪费内存空间,我们将它放在 prototype 上这样就会让所有实例共享一个 walk 函数,但是如果都写了它会调用自己的 walk 函数而不是共享的。

每一个函数都有一个 prototype 属性,函数的 prototype 对象上的属性方法,所有实例都是共享的。

prototype 对象有个 constructor 属性,它指向它的构造函数。

JS 中的面向对象 prototype class

当创建一个实例时,实例内有会有个 [[Prototype]] 指针指向构造函数的原型对象,在浏览器中查看显示为 __proto__ 属性。

当实例访问一个属性或者调用一个方法,比如 bob.walk() ,内部会首先在自身上查找这个方法,如果找到的话就完成,如果没有找到的话,就会沿着 [[prototype]] 向上查找,这就是为什么 prototype 上的方法都是共享,如果沿着 [[prototype]] 找到头,还没找到,那么就会报错 bob.walk 不是一个函数。

继承

继承主要是利用原型链,让子类的prototype等于父类的实例,也就是利用实例寻找属性和方法时,会沿着 [[prototype]] 向上找。

继承就是,一个子类继承父类的代码,而不用重新编写重复的代码。比如我们要写 Cat , Dog 等类,我们发现每个类都有类似 this.name = name; this.age = age 这些重复的代码,所以我们可以先写一个 Animal 类,让 Cat , Dog 继承这个类,我们就不用编写重复的属性和方法了。

function Animal(name) { this.name = name; this.age = 10 }
Animal.prototype.say = function () {
    console.log(this.name)
}
function Cat() { Animal.apply(this, arguments) }
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
复制代码

我们用 apply 改变 Catthis 指向,让我们可以借用 Animal 的构造函数,然后再让 Catprototype 指向一个 Animal 实例,并把 constructor 修改正常。

如果我们初始化一个 Cat 类,然后调用 say 方法,那么在内部的查找流程是:

自身 -> 沿着[[prototype]]找到Cat.prototype(它是一个Animal实例)-> 沿着Animal实例的[[prototype]]查找 -> 找到Animal.prototype(找到run方法并调用)

我们发现 Cat.prototype = new Animal() 这样就会让 Cat 的prototype多出 nameage 两个属性。

function Animal(name) { this.name = name; this.age = 10 }
Animal.prototype.say = function () {
    console.log(this.name)
}
function Cat() { Animal.apply(this, arguments) }

function F(){}
F.prototype = Animal.prototype

Cat.prototype = new F()
Cat.prototype.constructor = Cat
复制代码

我们使用了一个中间类函数 F ,让它的 prototype 等于父级的 prototype ,那么我们查找到 F.prototype 时,就自动到了 Animal.prototype 上。

我们如果想直到一个属性是不是自身的可以使用

实例.hasOwnProperty(属性) 查看该属性是否来自本身。

Object.getOwnPropertyNames(obj) 返回所有对象本身属性名数组,无论是否能枚举

属性 in 对象 判断能否通过该对象访问该属性,无论是在本身还是原型上

如果我们想获取一个对象的 prototype ,我们可以使用

Object.getPrototypeOf(obj) 方法,他返回对象的 prototype

Object.setPrototypeOf(object, prototype) 方法,设置对象的 prototype

还可以使用对象的 __proto__ 属性获取和修改对象的 prototype (不推荐)

属性描述符

在 js 中定义了只有内部才能用的特性,描述了属性的各种特性。

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

数据属性

  1. configurable 是否能配置此属性,为 false 时不能删除,而且再设置时会报错除了Writable
  2. enumerable 当且仅当该属性的 enumerabletrue 时,该属性才能够出现在对象的枚举属性中
  3. value 包含了此属性的值。
  4. writable 是否能修改属性值

存取描述符

configurable
enumerable
get
set

我们可以使用 Object.defineProperty 方法定义或修改一个对象属性的特性。

var obj = {}

Object.defineProperty(obj, "key", {
  enumerable: false, // 默认为 false
  configurable: false, // 默认为 false
  writable: false, // 默认为 false
  value: "static" // 默认为 undefined
});

Object.defineProperty(obj, 'k', {
    get: function () { // 默认为 undefined
        return '123'
    },
    set: function (v) {
        this.kk = v
    } // 默认为 undefined
})
复制代码

使用 Object.getOwnPropertyDescriptor 可以一次定义多个属性

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
});
复制代码

class

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。

这样编写面向对象就更加的简单。

和类表达式一样,类声明体在严格模式下运行。构造函数是可选的。

类声明不可以提升(这与函数声明不同)。

class Person {
    age = 0 // 属性除了写在构造函数中也可以写在外面。
    static a = 0 // 静态属性

    constructor (name) { 
    // 构造函数,可选(如果没有显式定义,一个空的constructor方法会被默认添加)
        this.name = name
    } 
    
    // 类的内部所有定义的方法,都是不可枚举的
    say () { // 方法 共享函数
        return this.name
    }
    
    static walk() { // 静态方法
        
    }
}

typeof Person // "function"
Person === Person.prototype.constructor // true
复制代码

使用的时候,也是直接对类使用 new 命令,跟构造函数的用法完全一致,但是忘记加 new 会报错。

静态属性和静态方法,是属于类的,而不是属于实例的,要使用 Person.walk() 调用。

类的所有方法都定义在类的 prototype 属性上面。

// 上面等同于

Person.prototype = {
  constructor() {},
  say() {}
};
Person.a = 0
Person.walk = function () {}
复制代码

ES6 为 new 命令引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令或 Reflect.construct() 调用的, new.target 会返回 undefined ,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}
复制代码

Class 内部调用 new.target ,返回当前 Class

与函数一样,类也可以使用表达式的形式定义。

const AA = class A {}
// 这个类的名字是A,但是A只在内部用,指代当前类。在外部,这个类只能用AA引用
const BB = class {}

let person = new class { // 立即执行的 Class
  constructor(name) {
    this.name = name;
  }
}('张三');
复制代码

Class 继承

Class 可以通过 extends 关键字实现继承。

class Animal {
    constructor (name) {
        this.name = name
    }
}

class Cat extends Animal {
    constructor (...args) {
        super(...args) // 调用父类的 constructor 方法
                        // 必须调用且放在 constructor 最前面
    }
}
复制代码

如果子类没有定义 constructor 方法,这个方法会被默认添加。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
复制代码

父类函数的静态属性和方法也会继承

super 这个关键字,既可以当作函数使用,也可以当作对象使用。

super 作为函数时,只能用在子类的构造函数之中,用在其他地方就会报错。

super 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

在子类普通方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。

构造函数方法是不能继承原生对象的,

Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
复制代码

但是 class 可以继承。这样就可以构造自己的 Array 子类。

可以继承了 Object ,但是无法通过 super 方法向父类 Object 传参。这是因为 ES6 改变了 Object 构造函数的行为,一旦发现 Object 方法不是通过 new Object() 这种形式调用,ES6 规定 Object 构造函数会忽略参数。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

RESTful Web Services Cookbook

RESTful Web Services Cookbook

Subbu Allamaraju / Yahoo Press / 2010-3-11 / USD 39.99

While the REST design philosophy has captured the imagination of web and enterprise developers alike, using this approach to develop real web services is no picnic. This cookbook includes more than 10......一起来看看 《RESTful Web Services Cookbook》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器