Skip to content

原型与原型链 #1

Open
Open
@myLightLin

Description

@myLightLin

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。下面这张经典图描述了原型的整个过程:
image

原型的两种检测方法

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions