原型链

213 阅读3分钟

原型链

MDN的定义:

每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

从上面的定义中我们可以得到下图中的关系:

从图中我们不难总结以下几点:

  • 只有函数才有 prototype,实例对象只有 __proto__
  • Function.__proto__Function.prototype 相等
  • 函数是 Function 的实例,函数的 prototypeObject 的实例
  • ObjectFunction 的实例,Function.prototypeObject 的实例
  • Object.prototype.__proto__ 指向 null

实现一个new关键字

要实现一个 new 关键字,首先要知道 new 关键字做了什么

这里引用 MDN 的定义,new 关键字会进行如下的操作:

  1. 创建一个空的简单 JavaScript 对象(即{});
  2. 链接该对象(设置该对象的 constructor )到另一个对象 ;
  3. 将步骤1新创建的对象作为 this 的上下文 ;
  4. 如果该函数没有返回对象,则返回 this

根据以上步骤我们实现的代码为:

function myNew (fn, ...args) {
    if(typeof fn !== 'function'){
      	throw 'first param must be a function';
    }
    const obj = {};
    obj.__proto__ = fn.prototype;
    const res = fn.apply(obj, args);
    return typeof res === 'object' ? res : obj;
};

这里是直接贴出了最终的代码,小伙伴们可以对比这上面的图好好体会一下原型链在这里面起的作用

ps: 构造函数函数也是函数的一种,是函数就可以有返回值,小伙伴们可以尝试一下如果构造函数有返回值,原生的 new 函数会返回什么,返回值的类型不同又会返回什么?

使用 prototype 模拟继承

假设我们有一个父类

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

如果一个类是另一个类的子类,有哪些特征?

一个子类要拥有父类全部的功能(不能选择继承),所以我们的第一步是

function Child (value) {
	// 调用父类构造函数
	Parent.call(this, value);
}
// Child.prototype原来是Object的实例,要让Child继承Parent的原型,就需要让Child.prototype成为Parent的实例
Child.prototype = new Parnet();

这样我们子类的对象实例也能访问父类的变量和方法。而且子类的对象实例用 instanceof 去验证也能得到正确结果

const child = new Child('white_give');
cnosole.log(child instanceof Child); // true
console.log(child instanceof Parent): // true

但是由第一部分原型链的图我们知道每一个构造函数的原型对象的 constructor 指向自身的构造函数

console.log(Child.prototype.constructor); // [Function: Parent]

但是从上面的代码我们得到,Child 的原型对象的 constructor 指向的是 Parent 的构造函数。所以用 prototype 来模拟继承还应该加上一句:

Child.prototype.constructor = Child;

以上就是我们经常用到的组合继承,但是组合继承的父类会在使用过程中被调用两次;一次是在子类的构造函数中 Parent.call(this),另一次是在改变子类原型对象的指向的时候 Child.prototype = new Parent()

还有一种经常使用的方式就是寄生组合继承,寄生组合继承的代码为:

function Child (value) {
  Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
	// 改变 constructor 的指向
    constructor: {
        value: Child,
        enumerable: false,
        writeable: true,
        configurable: true
    }
});

看了以上关于原型链的知识,你知道下面两行代码的结果是什么吗?

console.log(Function instanceof Object);
console.log(Object instanceof Function);