Object.prototype.toString引发的原型链学习
前言
判断数据格式时,都会使用Object.prototype.toString.call()
进行判断。某次使用时,不小心写成了Object.toString.call()
,结果浏览器直接报错了:
我直接疑惑,调用
Object.toString
关你Function.prototype.toString
什么事?
后来才发现写错了😅,但为什么是Object.prototype.toString
而不是Object.toString
呢?它们不一样吗?Object
上找不到toString
不是直接去原型上面找吗?
只能说,原型的水很深,没有理解原型的话,容易跟本人一样,产生这个疑问。
注意:后续的内容会提及类
class
,但是在js
中,class
只是语法糖,本质上还是函数Function
。
1. 函数的prototype
一般情况下,prototype
这个属性只有函数拥有,也就是Function
的实例拥有(通过function
或者class
定义的,箭头函数除外)。
没有特殊处理的情况下,其他的类型都不会拥有prototype
:
const num = 123
console.log('num.prototype:', num.prototype) // undefined
const str = 'abc'
console.log('str.prototype:', str.prototype) // undefined
const bool = true
console.log('bool.prototype:', bool.prototype) // undefined
const sym = Symbol('sym')
console.log('sym.prototype:', sym.prototype) // undefined
const obj = {}
console.log('obj.prototype:', obj.prototype) // undefined
const arr = []
console.log('arr.prototype:', arr.prototype) // undefined
function fun () {}
console.log('fun.prototype:', fun.prototype) // {constructor: ƒ}
在代码中,可以直接对
prototype
进行操作,如下:
function Person () {}
// 直接对函数的prototype进行操作
Person.prototype.walk = () => {
console.log('walking !')
}
// 打印结果
console.log('Person.prototype:', Person.prototype)
可以看到,
walk
函数已经存在Person
函数的prototype
上。
到这里为止,只需要记住,prototype
只有函数的拥有,且prototype
里面的属性可以进行修改。
而且prototype
虽然翻译过来是原型,但是Person
是无法直接调用的,需要通过prototype
进行调用,即Person.prototype.walk()
。
然而Person
通过new
创建的实例却是可以直接调用。
2. 原型与原型链
对于任何数据或者对象而言,my.__proto__
就是指向自身原型,而my.__proto__.__proto__
就是指向自身原型的原型,以此类推,多个__proto__
组成了原型链。
还是上面的例子:
首先,把上面的Person
函数通过new
实例化,看看其原型:
const person = new Person()
console.log('person:', person)
// 打印原型
console.log('person.__proto__:', person.__proto__)
通过
__proto__
可以访问person
的原型,而且是不是觉得person.__proto__
有些熟悉,其实就是Person.prototype
,函数的prototype
就是函数实例化之后的原型。
console.log('person.__proto__ === Person.prototype:', person.__proto__ === Person.prototype)
可以看到,
person.__proto__
和Person.prototype
是完全相等的,因为就是同一个对象。
然而在js
中,不管是数据也好,函数也好等等一切都是由某个构造函数实例化而来的,也就是说万物皆有原型(null
和undefine
除外), 还是拿上面的person
说明:
- 上面已经说了,
person
的原型就是Person.prototype
,也就是person.__proto__ === Person.prototype
;- 而
Person.prototype
是一个object
,就是一个对象,那么它的原型就是对象构造函数的prototype
,也就是Object.prototype
,即Person.prototype.__proto__ === Object.prototype
,person.__proto__.__proto__ === Object.prototype
;- 而
Object.prototype.__proto__
是null
,原型链到此为止。 从上面可以看出,person
有原型,而person
的原型有原型,依次类推,这就是原型链。
可以试着调用一下原型链上toString
函数:
console.log('person.toString():', person.toString())
// person.toString(): [object Object]
执行成功了 !!
虽然person
本身没有toString
函数,但是上游的某个原型上有这个函数;person
可以通过原型链,访问到这个函数。
使用代码判断一下toString
在原型链的位置:
let flag = false
let prototype = person
let path = 'person'
while(!flag && prototype) {
flag = prototype.hasOwnProperty('toString')
if (!flag) {
prototype = prototype.__proto__
path += '.__proto__'
}
}
console.log('flag:', flag)
console.log('prototype:', prototype)
console.log('path:', path)
可以看到,
toString
位于person
的原型的原型上,即person.__proto__.__proto__
,person
可以通过这条原型链访问toString
函数。
3. Object.toString
与 Object.prototype.toString
的区别
回归初心,为什么是Object.prototype.toString
而不是Object.toString
呢?
- 因为
Object
本身没有toString
方法,所以当调用Object.toString
时,会去原型链上查找; - 又因为
Object
是一个构造函数,所以它的原型是Function.prototype
,即Object.__proto__ === Function.prototype
,而Function.prototype
中刚好有toString
方法,所以直接调用了这个方法; - 但是因为
Function.prototype.toString
是用于处理函数的,当调用者不是函数时,无法处理,报错。通过代码进行判断,可以确定
Object.toString
调用的就是Function.prototype.toString
。
注意:
Object.__proto__
和Object.prototype
是完全不同的:
Object.__proto__
指向本身的原型,而Object
就是一个函数,它的原型就是 构造函数 的prototype
,也就是Function.prototype
。- 而
Object.prototype
,虽然名称是prototype
,但它不是Object
原型,只是Object
函数的一个属性而已,只是这个属性比较特殊。- 总结起来就一句话,如果需要访问某个数据或者对象的原型时(包括函数),只能通过
__proto__
获取,本身的prototype
不是自己的原型。
总结
null
和undefine
没有原型;prototype
一般只有函数拥有(箭头函数除外);- 在
js
中,基本所有的对象和数据类型都是由对应构造函数创建的,比如数字的构造函数是Number
,所有数字的原型都指向Number.prototype
。 - 而且,各种各样构造函数和类
class
本身也是由构造函数(Function
)创建的,即原型都是Function.prototype
,包括Function
自身也是:
转载自:https://juejin.cn/post/7117961909478883341