loading

原型,原型链

# 原型对象

对象由构造函数来新建,每个构造函数内部都有一个prototype 属性,这个属性指向一个对象,包含了由该构造函数创建的实例对象共享的所有属性和所有方法。这个prototype属性的值就是原型对象。

# proto

当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性的值,即构造函数的原型对象。这个指针在浏览器中为__proto__属性。所以大多数情况下(还有少数情况):

 对象.__proto__ === 对象的构造函数.prototype  
1

注意: 最好不要使用__proto__这个属性,因为它不是规范中规定的。 可以通过Object.getPrototypeOf()方法来获取对象的原型。 原型链

# 原型链

当访问一个对象的属性时,如果对象内部不存在这个属性,name就会去这个对象的原型对象里去找这个属性,这个原型对象又会有自己的原型,所以就会一直找下去,这就是原型链的具体表现。 原型链上的所有原型都是对象,一般来说都会继承 Object.prototype ,所以这就是新建的对象也能够使用 Object.prototype.toString() 等方法的原因。

# 构造函数

所谓"构造函数",用来初始化新创建对象的函数,内部使用了this变量。

对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

构造函数被赋予一个prototype属性,该属性指向了实例对象的原型对象。

# 构造函数的特点

  • 函数体内使用this关键字,代表了所要生成的对象实例
  • 生成实例对象,必须使用new关键字实例化
  • 为了与普通函数区别第一个字母通常大写

# new关键字

new命令的作用,就是执行构造函数,创建一个用户定义的对象类型的实例对象或具有构造函数的内置对象的实例对象

  • 创建一个空对象,作为将要返回的对象实例
  • 将这个空对象的原型(proto),指向了构造函数的prototype属性
  • 函数内部的this指向这个新对象
  • 执行构造函数内的代码(为这个新对象添加属性)。
  • 判断函数的返回(return)值类型,如果是一个对象,返回return语句指定的对象。否则,就会不管return语句,返回this对象。

# constructor属性

返回创建实例对象的构造函数的引用。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

let a = 1;
a.constructor === Number // true
a.__proto__.constructor === Number // true
1
2
3

实例对象本身没有constructor属性,于是会从实例对象的原型 proto 查找 constructor 属性,也就是 prototype.constructor

# 实例对象

由构造函数创建的对象一般被称为实例对象。实例对象将自动引用原型对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用原型对象的。 实例对象会自动含有一个constructor属性,这个constructor是通过继承原型对象继承来的,它指向了实例对象的构造函数。

实例对象的__proto__指向构造函数的prototype

# 简单总结

Object.prototype.proto === null

Object.proto === Function.prototype Object为构造函数,所有构造函数的__proto__都指向Function.prototype

let a=10 
a.__proto__ === Number.prototype // true
1
2

基本类型的转换会先把基本类型转换为对应的构造函数

Object.__proto__ === Function.prototype === Function.__proto__ // true
1

Object,Number, Error,Function等等这些函数都是Function创建的,它们的constructor为Function,需要额外注意的是Function.constructor也是Function。

 Object.constructor === Function // true
 Object.constructor.prototype  === Function.prototype  // true
 Function.constructor === Function// true
1
2
3
Function.prototype.__proto__ === Object.prototype
1

# 原型链继承

原型链继承,就是让对象实例通过原型链的方式串联起来,当访问目标对象的某一属性时,能顺着原型链进行查找,从而达到类似继承的效果。

// 父类
function SuperType (colors = ['red', 'blue', 'green']) {
    this.colors = colors;
}

// 子类
function SubType () {}
// 继承父类
SubType.prototype = new SuperType();
// 以这种方式将 constructor 属性指回 SubType 会改变 constructor 为可遍历属性
SubType.prototype.constructor = SubType;

let superInstance1 = new SuperType(['yellow', 'pink']);
let subInstance1 = new SubType();
let subInstance2 = new SubType();
superInstance1.colors; // => ['yellow', 'pink']
subInstance1.colors; // => ['red', 'blue', 'green']
subInstance2.colors; // => ['red', 'blue', 'green']
subInstance1.colors.push('black');
subInstance1.colors; // => ['red', 'blue', 'green', 'black']
subInstance2.colors; // => ['red', 'blue', 'green', 'black']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

缺点:

  • 在创建子类实例的时候,不能向超类型的构造函数中传递参数。
  • 这样创建的子类原型会包含父类的实例属性,造成引用类型属性同步修改的问题。

# 寄生组合继承

使用 Object.create(Parent.prototype) 创建一个新的原型对象赋予子类

// 寄生组合继承实现

function Parent(value) {
    this.value = value;
}

Parent.prototype.getValue = function() {
    console.log(this.value);
}

function Child(value) {
    Parent.call(this, value)
}

Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false, // 不可枚举该属性
        writable: true, // 可改写该属性
        configurable: true // 可用 delete 删除该属性
    }
})

const child = new Child(1)
child.getValue();
child instanceof Parent;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

寄生组合继承的模式是现在业内公认的比较可靠的 JS 继承模式,ES6 的 class 继承在 babel 转义后,底层也是使用的寄生组合继承的方式实现的。

最近更新时间: 2022/09/28 16:26:36
最近更新
01
2023/07/03 00:00:00
02
2023/04/22 00:00:00
03
2023/02/16 00:00:00