系列文章推荐
JavaScript原型与原型链(基础篇)
JavaScript原型与原型链(进阶篇)
JavaScript原型与原型链(总结篇)
1 intanceof
运算符
instanceof
运算符用于判断构造函数的prototype
属性是否出现在对象的原型链中,使用方法如下:
function Cat(name, age) {
this.name = name
this.age = age
}
const kat = new Cat('kat', 2)
console.log(kat instanceof Cat) // true
console.log(kat instanceof Object) // true
在这个例子中,由于kat.__proto__ === Cat.prototype
,所以kat instanceof Cat
输出为true
;由于原型链的终点为Object.prototype
,所以Object.prototype
肯定在kat
的原型链上,所以kat instanceof Object
输出也为true
。
下面是自定义instanceof
的实现代码,实现思路为沿着原型链一个接一个的判断,当instance.__proto__ === constructor.prototype
时返回true
,否则返回false
:
function myInstanceof(instance, constructor) {
// 获取实例对象的原型
let proto = Object.getPrototypeOf(instance)
// 获取构造函数的prototype对象
let prototype = constructor.prototype;
// 判断构造函数的prototype属性是否在实例对象的原型链上
while (true) {
// 若!proto === null则说明已经到原型的终点Object.prototype
if (!proto) {
return false
}
// 实例对象的原型与构造函数的原型相等
if (proto === prototype) {
return true
}
// 如果没有找到,就继续在原型上找
proto = Object.getPrototypeOf(proto)
}
}
2 函数的原型
2.1 函数也是一种对象
在JavaScript中函数也是一种对象类型,如下面代码中创建一个add
函数:
function add(x, y) {
return x + y
}
console.log(add instanceof Object) // true
等价于使用new
操作符和Function
构造函数创建一个函数对象add
:
let add = new Function('x', 'y', 'return x + y')
console.log(add instanceof Object) // true
上面两种写法是等价的,举这个例子是为了说明一个问题,即函数也是一个对象;同样的,构造函数也是函数,所以构造函数也是一个对象,既然是对象,那么就存在原型,所有构造函数的原型对象都是Object
构造函数的实例对象,于是满足关系:
function Cat() {}
const kat = new Cat()
console.log(kat.__proto__.__proto__ === Object.prototype) // true
// kat.__proto__ === Cat.prototype
console.log(Cat.prototype.__proto__ === Object.prototype) // true
上述关系可以用如下图例表示:
2.2 函数的构造函数Function
在JavaScript中的所有函数都是由Function
构造函数实例化而来,因此存在如下关系:
function Cat() {}
console.log(Cat.__proto__ === Function.prototype) // true
console.log(Cat.__proto__ === Cat.constructor.prototype) // true
上述关系可以用如下图例表示:
3 “奇怪”的Object
和Function
3.1 引例
下面例子用于和后文中的例子作为对照,例中声明构造函数Cat
,并实例化对象kat
,并且所有的结论在前文中已经提到:
function Cat(name, age) {
this.name = name
this.age = age
}
const kat = new Cat('kat', 2)
console.log(kat.__proto__ === Cat.prototype) // true
console.log(Cat.__proto__ === Function.prototype) // true
console.log(Cat.prototype.__proto__ === Object.prototype) // true
console.log(kat instanceof Cat) // true
console.log(kat instanceof Object) // true
console.log(Cat instanceof Cat) // false
console.log(Cat instanceof Object) // true
3.2 Object
和Function
这里直接给出结论,下面四个操作的运算结果都为true
:
Function instanceof Object
Object instanceof Function
Function instanceof Function
Object instanceof Object
已知instanceof
运算符的原理是判断构造函数的prototype
属性是否出现在对象的原型链上,根据上述结论还能推导及验证出如下关系:
- 由
Function instanceof Object
,推导出Function.__proto__.__proto__ === Object.prototype
; - 由
Object instanceof Function
,推导出Object.__proto__ === Function.prototype
; - 由
Function instanceof Function
,推导出Function.__proto__ === Function.prototype
; - 由
Object instanceof Object
,推导出Object.__proto__.__proto__ === Object.prototype
。
由上述第三个关系可知:Function
自身是构造函数,且自身又是自身构造函数的实例,这种关系是非常“奇怪”的,和“先有鸡还是先有蛋?”这个问题一样,在3.1节的例子中对于构造函数Cat
是不存在这种关系的,这种关系只存在于Function
上。
3.3 既是函数,也是对象
为了解释3.2节中的问题,这里把3.2节中推导出的四个关系分为两组:
- 对象组:
Function.__proto__.__proto__ === Object.prototype
;Object.__proto__.__proto__ === Object.prototype
。
- 函数组:
Object.__proto__ === Function.prototype
;Function.__proto__ === Function.prototype
。
之所以这么分组,是因为看待事物的角度不同,会导致结果的不同。
3.3.1 对象组
先看下面例子:
function Cat(name, age) {
this.name = name
this.age = age
}
const kat = new Cat('kat', 2)
console.log(kat.__proto__.__proto__ === Object.prototype) // true
// Cat.__proto__ === Function.prototype
// Function.prototype.__proto__ === Object.prototype
console.log(Cat.__proto__.__proto__ === Object.prototype) // true
在这个例子中声明构造函数Cat
,并实例化对象kat
,前面提到构造函数Cat
也是一种对象,所以满足关系:
kat.__proto__.__proto__ === Object.prototype
;Cat.__proto__.__proto__ === Object.prototype
。
上述关系可以用如下图例表示:
把这个结论推广到Function
和Object
上,由于可以把Function
和Object
看作一种对象,所以满足关系:
Function.__proto__.__proto__ === Object.prototype
;Object.__proto__.__proto__ === Object.prototype
。
3.3.2 函数组
先看下面例子:
function Cat() {}
console.log(Cat.__proto__ === Function.prototype) // true
在JavaScript中的所有函数都是由Function
构造函数实例化而来,在这个例子中构造函数Cat
是由Function
构造函数实例化而来,具体见2.2节,因此满足如下关系:
Cat.__proto__ === Function.prototype
。
把这个结论推广到Function
和Object
上,由于可以把Function
和Object
看作一种函数,所以满足关系:
Object.__proto__ === Function.prototype
;Function.__proto__ === Function.prototype
。
3.4 function anonymous()
其实在3.3.1节中还有一个疑点,即:
kat.__proto__.__proto__ === Object.prototype // true
Cat.__proto__.__proto__ === Object.prototype // true
此时读者可能觉得kat.__proto__ === Cat.__proto__
也是成立的,其实不然,这个问题的落脚点在于kat.__proto__
和Cat.__proto__
到底是什么,见下面例子:
function Cat(name, age) {
this.name = name
this.age = age
}
const kat = new Cat('kat', 2)
console.dir(kat.__proto__)
console.dir(Cat.__proto__)
输出的结果如下:
对于kat.__proto__
,根据前文中的结论,可以判断出kat.__proto__ === Cat.prototype
,输出结果也是如此。
对于Cat.__proto__
输出的是之前没有见过的函数function anonymous()
,译为匿名函数,由于构造函数Cat
是由构造函数Function
实例化而来,所以Cat.__proto__ === Function.prototype
,事实上所有构造函数的__proto__
值都为function anonymous()
,又由于Function.prototype === Function.__proto__
,所以Function.prototype
和Function.__proto__
的值也为函数function anonymous()
:
- 当把
Function
看作函数时,Function.prototype === function anonymous()
,即所有由Function
构造函数实例化的其他构造函数的原型都为function anonymous()
; - 当把
Function
看作对象时,Function.__proto__ === function anonymous()
,由于Function
自身是构造函数,且自身又是自身构造函数的实例,所以实例对象Function
的原型为function anonymous()
。
上述关系可以用如下图例表示:
根据结论“所有构造函数的原型对象都是Object
构造函数的实例对象”,所以Cat.__proto__.__proto__ === Object.prototype
。
4 其他内置构造函数
JavaScript中的内置构造函数参考:MDN - Global_Objects,其他内置构造函数指的是除去Function
和Object
之外的构造函数,这里仅列举常见的几种,如Number
、String
和Array
,具有如下特点:
- 和
Function
和Object
一样,其他内置构造函数的__proto__
属性也等于Function.prototype
:
console.log(Number.__proto__ === Function.prototype) // true
console.log(String.__proto__ === Function.prototype) // true
console.log(Array.__proto__ === Function.prototype) // true
- 其他内置构造函数不满足
instanceof
运算符自身与自身计算为true
的特点:
console.log(Number instanceof Number) // false
console.log(String instanceof String) // false
console.log(Array instanceof Array) // false
注意区分内置构造函数和内置对象,JavaScript中的内置对象,如
Math
和JSON
是以对象形式存在的,满足如下关系:
Math.__proto__ === Object.prototype
;JSON.__proto__ === Object.prototype
。