原型和原型链

  首先,在 JS 中可以使用类或构造函数来创建一个实例:

function Person() {}
// or
class Person {}

Person.prototype.name = 'Kevin';

let p1 = new Person();
let p2 = new Person();
// p1.name === p2.name = 'Kevin';

prototype

  每个函数/类都有一个 prototype 属性,它指向了一个对象,这个对象就是我们调用这个构造函数/类而创建的实例的原型,即上面代码中 p1p2 的原型。

  而原型可以这样理解:每一个 JS 对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中 “继承” 属性

  用一张图来表示构造函数和实例原型之间的关系:

构造函数和实例原型的关系图

__proto__

  每一个 JS 对象(除了 null)都具有 __proto__ 这个属性,它指向该对象的原型。

如:

p1.__proto__ === Person.prototype;

let n = 3;
n.__proto__ === Number.prototype;

  因此关系图更新为:

实例与实例原型的关系图

  除了可以使用 __proto_ 属性来获取实例的原型之外,还能使用 Object.getPrototypeOf(p1) (ES5 的方法)来获取对象原型:

Object.getPrototypeOf(p1) === Person.prototype;

constructor

  每个原型都有一个 constructor 属性来指向与其关联的构造函数/类。

如:

Person.prototype.constructor === Person;

Number.prototype.constructor === Number;

  因此再次更新关系图:

实例原型与构造函数的关系图

原型的原型

  前面原型理解中有提到,原型也是一个对象,因此我们可以使用最原始的方式创建它:

let obj = new Object();

  其实原型对象也就是通过 Object 构造函数生成的,因此原型对象的 __proto__ 属性会指向构造函数的 prototype ,也就是:

p1.__proto__.__proto__ === Object.prototype;
// 即
Person.prototype.__proto__ === Object.prototype;

  那么 Object.prototype 的原型是什么呢?

  答案是 nullObject.prototype.__proto__ === null

  代表了原型链的尽头。

  因此关系图可以更新为:其中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

原型链示意图

实例与原型链

  当读取实例的属性时,如果找不到,就会查找与其相关的原型中的属性,如果还找不到,就会去找原型的原型,直到最顶层的 null 为止。

如:

Object.prototype.name = 'End';

function Person() {}
Person.prototype.name = 'Kevin';

let p = new Person();
p.name = 'Daisy';

// 首先会在当前对象中查找
console.log(p.name); // Daisy

// 若找不到,则去与其关联的原型中查找
delete p.name;
console.log(p.name); // Kevin

// 若其原型中也没有,则去原型的原型中查找
delete Person.prototype.name;
console.log(p.name); // End

补充

constructor

  先看个例子:

function Person() {}

let person = new Person();
person.constructor === Person; //true

  根据我们前面的描述,constructorperson 对象的原型中的属性,person 中并没有这个属性;但当我们获取 person.constructor 时,会从 person 的原型中读取,而原型中正好有这个属性,因此还是能够正确读取。

  因此:

person.constructor === Person.prototype.constructor;

__proto__

  绝大部分浏览器都支持这个非标准的方法来访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解为返回了 Object.getPrototypeOf(obj)

属性 “继承”

  前面有说到,每一个对象都会从原型中 “继承” 属性。实际上,继承是一个比较迷惑的说法,在 《你不知道的 JS》 中有这样一段话:

继承意味着复制操作,然而 JS 默认并不会复制对象中的属性,相反,JS 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数。

所以与其叫继承,委托的说法反而更准确些。


REF:

https://github.com/mqyqingfeng/Blog/issues/2


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录