Javascript 原型链,这个链究竟是什么?

1.构造函数

特点:

  • 函数名首字母大写
  • 函数内部使用了this关键字,代表了所要生成的实例对象
  • 生成对象的时候必须使用new命令

2.new 命令

new 命令的作用就是执行构造函数生成实例对象

new 命令的原理:使用 new 命令时,后面的构造函数依次执行了下面的步骤:

  • 创建一个空对象,作为要返回的实例对象
  • 将这个空对象的原型,指向构造函数的 prototype 属性
  • 将这个空对象赋值给函数内部的 this 关键字
  • 开始执行构造函数内部的代码

3. 原型链

首先理解三个概念:

  • prototype:原型对象,每个函数都有一个 prototype 属性,再通过 new 命令实例对象时,该属性会成为该实例的原型对象。
  • constructor:构造函数。指向原型对象的 constructor
  • __proto__:实例对象的原型

首先看第一张图:

Person是构造函数,它的 prototype 属性是 Person.prototype;

反过来,Person.prototype 的 constructor 属性又指向 Person

person 是 Person 通过 new 命令生成的实例对象,它拥有__proto__属性,指向构造函数的原型对象,即 Person.prototype;

对于数组:let arr = [];

对于 Array.prototype 虽然是实例原型,但本质上还是一个对象,也就是构造函数 Object 的实例对象,所以:

而对于 Object.prototype,它的实例原型(__proto__)是 null;

于是,我们得出这样一条原型链:

同样对于构造函数 Array,它可以被当做为构造函数 Function 的实例对象:

对于 Function.prototype,本质上也是一个对象,也是构造函数 Object 的实例对象,所以:

同理,对于普通函数:let func = function(){}

类似于 String(), Number(), Boolean() 等,都能得到这样的原型链。

还剩最后两个 Function 和 Object;

Object 同样当做是 Function 的实例对象,所以:Object.__proto__ === Function.prototype;

而 Function 也可以当做是 Function 的实例对象,所以:Function.__proto__ === Function.prototype;

以上,算是把原型链搞清楚了。

4.constructor

prototype 对象上有一个 constructor 属性,默认指向 prototype 对象所在的构造函数;就是说 constructor 属性定义在 prototype 对象上面,意味着可以被实例对象所继承。

function P() {} 
P.prototype.constructor === P // true

function P() {}
var p = new P();
p.constructor === P // true

p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false

constructor 属性表示原型对象和构造函数之间的关系,如果修改了原型对象,一般都会修改 constructor 属性,防止出错;

function Person (name) {
    this.name = name;
}

Person.prototype.constructor === Person // true

Person.prototype = {
    method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true

以上代码中,prototype 对象被修改后,它的 constructor 属性不再指向 Person,而是指向一个普通对象,而普通对象的 constructor 指向 Object;

所以,在修改原型对象时,一般要同时修改 constructor 属性的指向。(这段话很重要)

// 坏的写法
C.prototype = {
    method1: function (...) { ... },
    // ...
};

// 好的写法
C.prototype = {
    constructor: C,
    method1: function (...) { ... },
    // ...
};

// 更好的写法
C.prototype.method1 = function (...) { ... };

5.构造函数的继承

第一步:是在子类的构造函数中,调用父类的构造函数

第二步:让子类的原型对象指向父类的原型,这样子类就可以继承父类;

举例来说,下面是一个 Shape 构造函数。

function Shape() {
    this.x = 0;
    this.y = 0;
}

Shape.prototype.move = function (x, y) {
    this.x += x;
    this.y += y;
    console.info('Shape moved.');
};

我们需要让 Rectangle 构造函数继承 Shape。

// 第一步,子类继承父类的实例
function Rectangle() {
    Shape.call(this); // 调用父类构造函数
}

// 另一种写法
function Rectangle() {
    this.base = Shape;
    this.base();
}
// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle;

如果子类只想继承父类的单个方法,这是可以采用下面的方法:

ClassB.prototype.print = function() {
    ClassA.prototype.print.call(this);
    // some code
}

6.多重继承

function M1 () {
    this.hello = 'hello';
}

function M2 () {
    this.world = 'world';
}

function S () {
    M1.call(this);
    M2.call(this);
}

// 继承
M1 S.prototype = Object.create(M1.prototype); 

// 继承链上加入
M2 Object.assign(S.prototype, M2.prototype);

// 指定构造函数
S.prototype.constructor = S;

var s = new S();
s.hello // 'hello'
s.world // 'world'

猜你喜欢

转载自blog.csdn.net/josavion/article/details/100287635