系列文章推荐
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)
console.log(kat instanceof Object)
在这个例子中,由于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)
let prototype = constructor.prototype;
while (true) {
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)
等价于使用new 操作符和Function 构造函数创建一个函数对象add :
let add = new Function('x', 'y', 'return x + y')
console.log(add instanceof Object)
上面两种写法是等价的,举这个例子是为了说明一个问题,即函数也是一个对象;同样的,构造函数也是函数,所以构造函数也是一个对象,既然是对象,那么就存在原型,所有构造函数的原型对象都是Object 构造函数的实例对象,于是满足关系:
function Cat() {}
const kat = new Cat()
console.log(kat.__proto__.__proto__ === Object.prototype)
console.log(Cat.prototype.__proto__ === Object.prototype)
上述关系可以用如下图例表示:
2.2 函数的构造函数Function
在JavaScript中的所有函数都是由Function 构造函数实例化而来,因此存在如下关系:
function Cat() {}
console.log(Cat.__proto__ === Function.prototype)
console.log(Cat.__proto__ === Cat.constructor.prototype)
上述关系可以用如下图例表示:
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)
console.log(Cat.__proto__ === Function.prototype)
console.log(Cat.prototype.__proto__ === Object.prototype)
console.log(kat instanceof Cat)
console.log(kat instanceof Object)
console.log(Cat instanceof Cat)
console.log(Cat instanceof Object)
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)
console.log(Cat.__proto__.__proto__ === Object.prototype)
在这个例子中声明构造函数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)
在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
Cat.__proto__.__proto__ === Object.prototype
此时读者可能觉得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)
console.log(String.__proto__ === Function.prototype)
console.log(Array.__proto__ === Function.prototype)
- 其他内置构造函数不满足
instanceof 运算符自身与自身计算为true 的特点:
console.log(Number instanceof Number)
console.log(String instanceof String)
console.log(Array instanceof Array)
注意区分内置构造函数和内置对象,JavaScript中的内置对象,如Math 和JSON 是以对象形式存在的,满足如下关系:
Math.__proto__ === Object.prototype ;JSON.__proto__ === Object.prototype 。
|