Description
JS 最初是作为一门脚本语言来设计,主要就是在浏览器里与 DOM 做交互。因此它并不像 Java,C++ 那种面向对象编程语言一样,有完整的类继承机制。在 JS 里,一个对象继承另一个对象的属性和方法,是通过 原型链委托
的方式来实现。理解原型链,要搞清楚 构造函数
,prototype
,__proto__
这几个概念。
构造函数、prototype 和 proto
JS 里一切皆对象,每个实例对象都有一个私有属性,叫 __proto__
,这个属性指向它构造函数的原型对象。所谓构造函数,是指能被 new
操作符调用的函数,比如箭头函数就不能被 new 调用;所谓原型对象,是指每个函数都有一个 prototype
属性,它是个对象,存储一些属性和方法,这些属性和方法能够在构造函数的实例间 共享
。举个例子:
function Foo(name) {
this.name = name
}
Foo.prototype.getName = function() {
console.log('getName: ', this.name)
}
const foo = new Foo('张三')
foo.getName()
console.log(foo.__proto__ === Foo.prototype) // true
console.log(Foo.prototype.constructor === Foo) // true
上面这段代码,Foo
就是 构造函数
,Foo.prototype
就是原型对象
,而 foo
是实例对象
,这个实例对象可以访问 Foo 原型对象上的属性和方法,它是怎么访问到的?这是因为 foo.__proto__
链接到了 Foo.prototype
上,所以在访问 foo.××
时,首先会查找这个实例对象自身的属性,如果找不到,就去它的原型对象也就是 Foo.prototype
上去找。现在实例对象可以通过 __proto__
找到原型了,那如果有多个实例呢?怎么知道实例是由哪个构造函数创建的?这就要引出 constructor
属性。每个原型对象例如 Foo.prototype 里有一个 constructor
属性,用于记录实例由谁创建而来,在这里指向的是 Foo 构造函数。
原型链
前面的代码里,foo 是 Foo 构造函数的一个实例对象,那 Foo 是个函数,根据 JS 里一切皆对象的说法,Foo 函数也有它自己的原型,它的原型是啥呢?答案是 Function.prototype
,因为当你声明 Foo 函数的时候,就类似 new Function(Foo)
的操作。我们来验证下:
console.log(Foo.__proto__ === Function.prototype) // true
那 Function.prototype
是个对象,它的原型又指向啥?还有上面的 Foo.prototype
也是个对象,它的原型又是啥呢?一提到对象,那可以联想到,是不是通过 new Object
创建的呢,如果是的话,那它的原型应该就是构造函数 Object 的 prototype,我们打印出来验证下:
console.log(Function.prototype.__proto__ === Object.prototype) // true
console.log(Foo.prototype.__proto__ === Object.prototype) // true
从上面代码可以得出信息:不管是 Function.prototype 还是 Foo.prototype,它们的原型都指向 Object.prototype
。
那问题又来了,Object.prototype
又是个对象了,这时它的原型该指向谁?ECMAScript 规范规定,Object.prototype
的原型指向 null
。
现在可以解释 原型链
了:每个对象通过它的内部属性 __proto__
访问它的构造函数原型对象,原型对象继续访问它上一层的对象,一直到原型链的最顶层,也就是 null
。下面这张经典图描述了原型的整个过程:
原型的两种检测方法
JS 里可以通过以下方法检测一个对象是否是另一个对象的原型:
- isPrototypeOf
- instanceOf
isPrototypeOf 的语法
function Foo() {}
function Bar() {}
Bar.prototype = Object.create(Foo.prototype)
const bar = new Bar()
console.log(Foo.prototype.isPrototypeOf(bar)) // true
console.log(Bar.prototype.isPrototypeOf(bar)) // true
instanceOf 的使用及模拟实现
instanceOf 的原理是去检测构造函数的 prototype 属性是否出现在某个对象实例的原型链上。还是上面那个例子的代码:
function Foo() {}
function Bar() {}
Bar.prototype = Object.create(Foo.prototype)
const bar = new Bar()
console.log(bar instanceof Foo) // true
console.log(bar instanceof Bar) // true
以下是 instanceof 的模拟实现:
function mockInstanceof(leftValue, rightValue) {
let rightProto = rightValue.prototype
leftValue = leftValue.__proto__
while (true) {
if (leftValue === null) return false
if (leftValue === rightProto) return true
leftValue = leftValue.__proto__
}
}
测试一下:
function Foo() {}
function Bar() {}
Bar.prototype = Object.create(Foo.prototype)
const bar = new Bar()
console.log(mockInstanceof(bar, Foo)) // true
console.log(mockInstanceof(bar, Bar)) // true