本文首发于微信公众号 前端边界
要理解原型,首先就要理解__proto__
和prototype
的区别与联系,这里先抛出一个结论:
- 对象都有隐式原型
__proto__
(除了null
和Object.create(null)
); - 函数都有显式原型
prototype
; - 实例对象的
__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
}
- 创建一个对象,并将其原型指向构造函数的
prototype
; - 执行构造函数,并将创建的对象绑定为
this
; - 如果构造函数的返回值为对象,则返回此对象,否则返回创建的对象(即
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 的原型继承遵守以下几个规则:
- 所有数据皆对象;
- 创建对象,需找到一个对象作为原型并克隆它;
- 对象会记住它的原型;
- 如果对象无法响应某个请求,它会把请求委托到自己的原型。
下面看看寄生组合式继承的实现:
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
这里做了以下几个事:
- 子类通过
call
方法调用父类构造函数,继承父类实例属性,并可向父类构造函数传参; - 将子类构造函数原型指向父类构造函数原型,继承其原型方法,利用
Object.create
避免父子两个构造函数prototype
共享一个引用; - 修正子类构造函数;
- 继承静态属性和方法。
通过以上内容,相信我们对__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