原型和原型链
首先,在 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
属性,它指向了一个对象,这个对象就是我们调用这个构造函数/类而创建的实例的原型,即上面代码中 p1
和 p2
的原型。
而原型可以这样理解:每一个 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
的原型是什么呢?
答案是 null
:Object.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
根据我们前面的描述,constructor
是 person
对象的原型中的属性,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:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!