JS 原型概览

628 阅读4分钟

本文首发于微信公众号 前端边界

要理解原型,首先就要理解__proto__prototype的区别与联系,这里先抛出一个结论:

  1. 对象都有隐式原型__proto__(除了nullObject.create(null));
  2. 函数都有显式原型prototype
  3. 实例对象的__proto__指向其构造函数的prototype

我们可以通过new操作符来看看其到底发生了什么?

new 的实现

以下是一个new操作符的模拟实现,如果对Object.create方法不了解,可以先看看后面的介绍。

function newFactory(Constructor: Function, ...args: unknown[]): object {
  const obj = Object.create(Constructor.prototype)
  const result = Constructor.apply(obj, args)
  return typeof result === 'object' && result !== null ? result : obj
}
  1. 创建一个对象,并将其原型指向构造函数的prototype
  2. 执行构造函数,并将创建的对象绑定为this
  3. 如果构造函数的返回值为对象,则返回此对象,否则返回创建的对象(即this)。

经过以上步骤,我们实例化的对象obj就能访问到构造函数定义的属性和原型方法,此时obj.__proto__ = Constructor.prototype

原型链

在 JavaScript 中,当试图访问一个对象的属性时,如果当前对象没有该属性,则会查询该对象的原型,以及原型的原型,直到匹配到该属性或者查询到原型链的末端。而原型链的查询则是通过对象私有属性__proto__实现。

const p = {a: 1, b: 2}
const o = Object.create(p)

console.log(o.__proto__ === p) // true
console.log(o) // {}
console.log(o.__proto__.a) // 1
console.log(o.a) // 1

o.a = 3

console.log(o) // {a: 3}
console.log(o.__proto__.a) // 1
console.log(o.a) // 3

console.log(o.__proto__.__proto__ === Object.prototype) // true
console.log(o.__proto__.__proto__.__proto__) // null

所以当查询一个对象属性时,会通过__proto__层层向上查询该属性,直到其原型对象为null。整个查询的链路就叫作原型链。它也是我们实现原型继承的重要机制。

原型继承

JavaScript 的原型继承遵守以下几个规则:

  1. 所有数据皆对象;
  2. 创建对象,需找到一个对象作为原型并克隆它;
  3. 对象会记住它的原型;
  4. 如果对象无法响应某个请求,它会把请求委托到自己的原型。

下面看看寄生组合式继承的实现:

function inherits(subCtor: Function, superCtor: Function) {
  // subCtor.prototype.__proto__ = superCtor.prototype,继承原型方法
  subCtor.prototype = Object.create(superCtor.prototype)
  // 修正构造函数
  subCtor.prototype.constructor = subCtor
  // subCtor.__proto__ = superCtor,继承静态方法和属性
  Object.setPrototypeOf(subCtor, superCtor)
}

function Super(name: string, age: number) {
  this.name = name
  this.age = age
}

Super.prototype.hello = function() {
  console.log('hello, ' + this.name)
}

Super.hello = function() {
  console.log('hello, static')
}

function Sub() {
  // 继承实例属性,可向父类传参
  Super.call(this, 'super', 30)
}

inherits(Sub, Super)

const sub = new Sub()
sub.hello() // hello, super
console.log(sub.constructor === Sub) // true
console.log(sub.name) // super

Sub.hello() // hello, static

这里做了以下几个事:

  1. 子类通过call方法调用父类构造函数,继承父类实例属性,并可向父类构造函数传参;
  2. 将子类构造函数原型指向父类构造函数原型,继承其原型方法,利用Object.create避免父子两个构造函数prototype共享一个引用;
  3. 修正子类构造函数;
  4. 继承静态属性和方法。

通过以上内容,相信我们对__proto__prototype有了清楚的认识,下面我们再列举一些关于原型操作的常用方法。

涉及原型的方法

__proto__作为对象私有属性,并没有被包括在 EcmaScript 语言规范中,目前只有少数浏览器还在支持。以下列举一些涉及原型操作的常用方法,以帮助我们更好的理解原型。

Object.create(proto, propertiesObject?)

创建对象,并指定 proto 对象为其原型对象,返回新对象。等同于obj.__proto__ = proto,以下是其 polyfill 实现:

function create(proto: object) {
  function f() {}
  f.prototype = proto
  return new f()
}

// 等同于
function create2(proto: object) {
  const obj = {}
  obj.__proto__ = proto
  return obj
}

此方法可以通过传入参数null来创建一个没有原型的新对象,它不会继承任何属性和方法。

const o = Object.create(null)
console.log(o.toString()) // o.toString is not a function

如果想创建一个普通对象,需要传入Object.prototype

const o1 = Object.create(Object.prototype)
// 等同于
const o2 = {}
const o3 = new Object()
Object.setPrototypeOf(object, prototype)

设置指定对象的原型,并返回指定对象。等同于object.__proto__ = prototype

const proto = {c: 3}
const o = {a: 1}
const c = Object.setPrototypeOf(o, proto)
console.log(c.__proto__ === proto) // true
Object.getPrototypeOf(object)

获取对象的原型,等同于object.__proto__

const proto = {}
const o = Object.create(proto)
console.log(Object.getPrototypeOf(o) === proto) // true
console.log(o.__proto__ === proto) // true
Object.prototype.isPrototypeOf(object)

用于检测一个对象是否存在于另一个对象(object)的原型链上。

const proto = {}
const o = Object.create(proto)
console.log(proto.isPrototypeOf(o)) // true

function F() {}
F.prototype = Object.create(proto)
const o2 = new F()
console.log(proto.isPrototypeOf(o2)) // true
instanceof 运算符

用于检测右侧构造函数的prototype属性是否出现在左侧实例对象的原型链上。

function P() {}
function F() {}
F.prototype = Object.create(P.prototype)
const o = new F()
console.log(o instanceof F) // true
console.log(o instanceof P) // true

// polyfill
function _instanceof(left: object, right: Function) {
  const proto = right.prototype
  left = left.__proto__
  while (true) {
    // 查询到原型链末端
    if (left === null) return false
    if (left === proto) return true
    left = left.__proto__
  }
}

console.log(_instanceof(o, F)) // true
console.log(_instanceof(o, P)) // true