先上总结
JavaScript 通过原型和原型链机制,实现了面对对象中”继承“的思想。
原型链主要属性
主要属性就是下面两种,我们一个个的讲:
prototype
__proto__
prototype
是什么
我们通过 Person 构造函数创建一个 person 实例:
function Person(name) { // 构造函数
this.name = name;
}
// Person 是个构造函数,所以有 prototype 属性,指向了一个原型对象
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
// person.__proto__ 也指向了相同的原型对象
const person = new Person('John');
我们可以通过打印 Person.prototype
查看 Person
的原型,可以看到,原型就是一个对象:
new Person
相当于把 Person 当做构造函数调用,实例化了 person 对象。
那么 Person 函数是怎么知道通过自己创建的实例都继承了哪些方法呢?就是通过 prototype
属性,这个属性指向了 Person
的原型对象,里面保存了 person 实例可以使用的方法和属性。
下图可以看到,Person.prototype
指向了一个原型对象,p1.__proto__
也指向了这个原型对象:
只有函数有 prototype 属性,其他类型比如普通对象没有这个属性,因为其他类型不能当做构造函数使用,只有函数可以当做构造函数使用。
__proto__
是什么
__proto__
是所有对象都有的属性。和 prototype
一样,__proto__
指向的也是一个原型,同时某个构造函数的 prototype
属性也指向了这个原型。
我们可以通过打印这个对象,查看它的 __proto__
属性:
原型链是由 __proto__
属性构成的单链表,最终指向 Object.prototype
,也就是所有对象的最顶层原型:
__proto__
隐式原型,为什么”隐式“ ?
为啥 __proto__
叫隐式原型呢,因为__proto__
这个名字不是官方标准定义的,而是所有浏览器都把它叫做 __proto__
。所以不推荐直接使用 __proto__
,而是使用Object.getPrototypeOf()
。
__proto__
VS prototype
prototype
是构造函数的属性,指向了这个构造函数的原型,比如 Person.prototype
指向了 Person 构造函数的原型,用这个构造函数实例化的对象,它们的 __proto__
属性也指向了这个原型。
__proto__
是对象的属性,用来形成原型链。比如,孙子对象的 __proto__
指向了儿子对象的 __proto__
,儿子对象的 __proto__
指向了父对象的 __proto__
,从而形成了原型链。
__proto__
和 prototype
的关系:
手写 instanceof,进一步理解原型链
instanceof
的原理是通过原型链查找符合要求的原型对象,搞明白原型链,就搞明白了instanceof
的原理。
function instance_of(obj, cls) {
let clsProto = cls.prototype; // 原型对象
let objProto = obj.__proto__; // 原型对象
while (true) {
// 一直顺着原型链找到 null 了都没有找到
if (!objProto) {
return false;
}
// 对象的原型链上存在一个原型对象,和构造函数的原型对象一样,说明对象是由这个构造函数创建的
if (objProto === clsProto) {
return true;
}
// 往原型链上继续走
objProto = objProto.__proto__;
}
}
function Person() {}
const p = new Person();
console.log(instance_of(p, Person)); // true
console.log(instance_of(p, Object)); // true
console.log(instance_of(p, String)); // false
Symbol.hasInstance
可以自定义 instanceof
,这也是 instanceof 为什么不靠谱的原因。
Reference
developer.mozilla.org/en-US/docs/…