JS中的原型与继承

为什么要有原型?JS设计之初没有Class的概念,所以使用了构造函数+原型实现继承机制(es6的class可以看作语法糖)
function Person(name){
this.name = name;
}
const p = new Person('jack');

构造函数创建实例的过程 (模拟new操作符):

BLOOMCHIC
function new(constructor){
return (...args) => {
const obj = Object.create(constructor.prototype);
// not standard
obj.__proto__ = constructor.prototype;
constructor.call(obj, ...args);
return obj;
}
}
function Person(name){
this.name = name;
}
const constructor = new(Person);
const p = constructor('jack');

原型与原型链

原型

js每个函数在创建的时候,都会有一个prototype属性,这个属性指向一个对象,这个对象就是原型对象,原型对象中有一个constructor属性,指向函数本身。

function Person() {}
const p = Person.prototype
p.constructor === Person // true

image.png

__proto__

__proto__不是一个标准属性,是各大浏览器厂商添加的私有属性。一般可以用Object.getPrototypeOf获取实例对象原型。

从上面构造函数创建实例的过程可以看出来,我们新建的对象的原型对象指向构造函数的prototype。而在实例对象中,我们可以通过Object.getPrototypeOf获取实例的原型。

每个对象都有一个__proto__属性,指向创建它的构造函数的原型对象。由于原型对象(prototype)本身也是一个对象,因此它也存在一个__proto__指向自己构造函数的原型对象。当访问一个对象的某个属性时,会先在这个对象本身上查找,如果没找到,则会到它的__proto__上找该属性,如果还没找到,就会在它构造函数的原型对象的__proto__上找。这样一层一层向上查找形成的链式结构我们称为原型链。

原型的尽头

js中函数实际上也是一个对象,因此每个函数实际上都是一个函数对象。而只要是对象就会有自己的原型对象。因此对于一个函数:

function A() {}
// eslint-disable-next-line no-proto
A.__proto__ === Function.prototype;

每个函数对象的原型对象都是Function.prototype

Function.prototype也是一个对象,因此它也有一个原型对象。

// eslint-disable-next-line no-proto
Function.prototype.__proto__ === Object.prototype;

Function的prototype的原型指向Object.prototype。而Object.prototype的原型:

// eslint-disable-next-line no-proto
Object.prototype.__proto__ === null;

最终指向null。原型链的尽头为null。

容易困惑的点

  • Object.__proto__ === Function.prototype因为Object本身是一个函数对象,是通过new Function()创建的。这恒河里

  • Function.__proto__ === Function.prototype因为Function自身也是一个函数对象,它也是通过new Function()创建出来的,因此它的原型对象指向它自己。这也恒河里

  • Function.prototype.__proto__ === Object.prototype因为Function.prototype实际上也是一个对象,也就是它是由new Object()创建出来的,所以实例的__proto__指向构造函数的prototype。嗯,河里

总之,构造函数.prototype === 实例.__proto__,而对于构造函数,它本身也是一个函数对象,因此我们也可以把它当作一个实例对象去分析。最终的原型对象指向的都是Object.prototype.__proto__,即null,保证原型链有一个终点。

评论加载中 (ง •̀ω•́)ง