彻底理解JS的原型

374 阅读4分钟

原型

太长直接看总结

所有创建的实例对象都有一个__proto__属性,指向原型对象,所有实例对象的构造函数都有prototype属性,指向原型对象,原型对象有constructor函数,这个函数就是构造函数(__proto__是早期浏览器为了方便我们访问到原型而增加的一个属性,现在不推荐直接使用这个属性访问原型)

  1. 所有对象可以通过__proto__找到Object.prototype
  2. 所有函数可以通过__proto__找到Function.prototype
  3. Function.prototype.__proto__又指向Object.prototype,Object.prototype.__proto__指向null
  4. 如果试图引用对象的某个属性,会首先在对象内部寻找该属性,直至找不到,然后会在该对象的原型里去寻找这个属性,这种搜索轨迹,类似一条长链,prototype在这条链中充当链接的作用,所以把这种实例与原型的链条称作原型链

原型链经典图例

原型图例标准图.jpg

构造函数

构造函数从性质上说,就等同于普通函数,只是使用了new来构造实例对象

function Animal(type){
    this.type = type;
}
// 通过new构造实例对象,这个时候Animal就是构造函数,也可以称作就是cat的构造函数
const cat = new Animal('cat');

我们一步一步来解析构造函数和对象的关系

首先明确

  1. 所有对象都有一个原型对象,对象以其原型为模板,从原型继承方法和属性

  2. 对象有一个__proto__属性指向原型,构造函数有一个prototype属性同样指向原型

  3. 原型上有一个constructor属性,指向构造函数

cat对象的控制台输出,可以看到原型上有constructor指向Animal,而cat的原型也是一个对象,这个原型的原型,又指向的是Object的原型 原型1.png

function Animal(type)函数的控制台输出,可以看到Animal.prototype和cat.__proto__指向的是同一个对象,也就是原型

这里可以看到Animal.__proto__指向了一个看起来很奇怪的东西,但是下面又可以看到 Animal.__proto__ === Function.prototype,这里就是因为,函数在js中很特殊,函数也是对象,只要是对象,就存在原型,而函数的构造函数就是Function(),所以函数的原型就是Function.prototype 原型2.png

这里总结一下原型、构造函数、对象的一个基本关系图 原型构造函数对象关系图.jpg

上面我们可以看到函数Animal,作为对象的原型是Function.prototype,那么Function.prototype的__proto__,又指向什么呢,根据输出我们可以看到,Function.prototype的__proto__ === Object.prototype,这里就追溯到了Object的原型 原型3.png

这里可能就会有点迷惑了,一会是Object,一会又是Function,下面一张图我们可以梳理出一个总结

原型4.png

这里做一个总结

  1. Animal作为函数有prototype指向原型,函数同时也是对象,对象就有__proto__指向构造这个函数的原型,这是两个不同的原型
  2. Object和Function都是函数,所以同时存在prototype和__proto__
  3. 所有函数的__proto__都会指向Function.prototype
  4. Function.prototypeFunction.__proto__相同
  5. Function.prototype.__proto__指向Object.prototype
  6. Object.prototype.__proto__指向最终的结果null

原型链

每个对象拥有一个原型对象,通过__proto__指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法

可以看到,在原型上定义的属性,可以直接通过实例访问到 原型5.png

原型链的访问路径 原型链访问路径.jpg

总结

  • 所有对象都有__proto__,所有构造函数都有prototype属性,这两个属性不一样,但是指向的是同一个对象
  • __proto__是ES6标准化的一个参数,但是因为访问的性能问题,不推荐使用,获取原型推荐使用Object.getPrototypeOf
  • Function.prototypeFunction.__proto__,是指向的同一个对象
  • 引用类型constructor属性值可以修改,对于基本类型来说是只读的,null和undefined没有constructor
  • Symbol很特殊,Symbol不支持通过new调用,但是原型上有constructor属性
  • 每个对象都有一个原型对象,通过__proto__一层一层往上,最终指向null,这就是原型链

最后附上两个常用与原型相关的必会手写原理方法

手写实现new

function create(fn, ...args) {
    if (typeof fn !== 'function') {
        throw new Error('类型错误');
    }

    const newObj = Object.create(fn.prototype);

    const res = fn.apply(newObj, args);

    return res instanceof Object ? res : newObj;
}

手写实现instanceof

x instanceof Y,通过原型链判断对象x的原型链上是否有Y.prototype

instanceof的原理就是基于原型链,一层一层查找 __proto__,如果和 Y.prototype 相等则返回 true,如果一直没有查找成功则返回 false。

function A(){}
function B(){}

const a = new A();

a instanceof A // true
a instanceof B // false
// 手动实现instanceof
function myInstanceof(left, right) {
    if (typeof left !== 'object' || left === null) {
        return false;
    }
    let prototype = Object.getPrototypeOf(left);
    while (true) {
        if (prototype === null) {
            return false;
        }
        if (prototype === right.prototype) {
            return true;
        }
        prototype = Object.getPrototypeOf(prototype);
    }
}