function和object
在JavaScript中,有两个比较特殊的数据类型,一个是function
,另一个是object
。它们的共同点是都为引用类型,即变量名对应的值是一个指向其对象的指针。而区别是,function类型的数据都有一个prototype
属性,原因是每个function类型的对象都是由Function()
这个构造函数创建的实例。我们姑且称其为显式原型。 而object类型都是由Object()
这个构造函数创建的实例,它们都有一个__proto__
属性,我们暂且称之为隐式原型。
小结
在这里简单归纳一些function和object的性质:
- 所有object类型的数据都拥有
__proto__
属性 - 只有function类型的数据都拥有
prototype
属性 - 由于function类型的数据也是object类型,因此function类型的数据也拥有
__proto__
属性 至于它们之间的关系我们将继续讨论。
关于prototype和__proto__
prototype
我们知道若想创建一个函数(function)必须通过function
关键字来创建。而function
关键字的原理则是通过new
关键字调用Function()
这个函数构建出一个实例。所以我们熟知的创建函数的三种方式
// 第一种写法
var fun = new Function('arg1', 'arg2', ..., 'body')
// 第二种写法
function fun(arg1, arg2, ...){
// do something
}
// 第三种写法
var fun = function('arg1, arg2, ...'){
// do somthing
}
这三种写法得到的结果是相同的(但解析器解析方式不同,这里不展开来说),都是创建了一个变量fun,其值为指向一个Function()
的实例的指针。
只有function类型的数据拥有prototype
属性,它的值是一个地址值,默认指向一个空对象(object),而上述的那个“ 对象(object)”就称为这个function的原型对象。
__proto__
我们知道若想创建一个对象(object),必须通过new
关键字调用“某个函数(function)”将其实例化。而这个函数就称为这个对象的构造函数。每个对象(object)都是有某个构造函数实例化创建而来的,在每个对象的内部都有一个constructor
属性,其属性值就是一个指向这个对象构造函数的指针。 所有object类型的数据都拥有__proto__
属性,它的值也是一个地址值,并且与它的构造函数的prototype的值一致。
小结
到这里我们知道,构造函数的prototype
和实例对象的__proto__
指向的其实是同一个对象,即构造函数的原型对象。
Function和Object的原型
理解了原型和原型对象后,我们需要了解两个特殊的函数的原型,即Object
和Function
。由于函数也是object类型的数据,因此它们都具有prototype
和__proto__
属性,下面分别讨论。
(请注意不要混淆,首字母大写的Object和Function是两个函数,而首字母小写的object和function代表的是两种数据类型。)
Function的原型
Function的原型对象,这里用Function.prototype
来表示。Function()
既然是function,同样也是一个object类型的数据,因此Function()
也拥有__proto__
属性,但其值为指向Function.prototype
这个对象的指针。
我们知道,一个对象的__proto__
属性应该与它的构造函数的prototype
值相等。然而Function.__proto__
却与Function.prototype
的值相等,照此理解Function的构造函数就是它自己本身。事实也正是如此,我们可以通过打印Function.constructor
查看Function的构造函数。
输入
console.log(Function.constructor)
控制台输出
ƒ Function() { [native code] }
至此我们知道,Function()函数的构造函数是它本身,因此Function.prototype
和Function.__proto__
指向的是同一个对象即图中右侧的Function.prototype
。
Object的原型
Object.prototype
Object的原型对象,这里用Object.prototype
来表示。Object.prototype
是一个object类型的数据,也就是说,Object.prototype
是由Object的实例。通过之前的总结我们知道,object类型的数据都拥有__proto__
属性。因此Object.prototype
即Object的原型对象也拥有__proto__
属性,并且其属性值为null。也就是Object.prototype.__proto__ === null
(注意这一点与其他object不同,它的__proto__
属性值并非为其构造函数Object()的prototype
的值,而是null)
Object.__proto__
Object是一个函数,因此它的构造函数为Function。因此Object.__proto__
应该与它构造函数的prototype属性一致,因此指向Function.prototype
。(图中未显示)
图中o1,o2是由Object函数创建的任意实例,它们的__proto__
属性与它们的构造函数的原型对象,也就是Object.prototype
一致,指向的都是图中右侧的圆角矩形Object.prototype
这个对象。而Object.prototype
的__proto__
属性的属性值为null。
小结
每个function类型的数据同时也是object类型的数据,它们同时拥有prototype
和__proto__
属性。其中两个特殊的函数Function和bject的prototype
和__proto__
属性分别由以下特点
- Function的
prototype
和__proto__
属性相同都指向同一个对象,即Function的原型对象Function.prototype
- Object的
prototype
指向Object的原型对象,而Object的原型对象的__proto__指向null - Object是一个函数,它的
__proto__
属性指向Function.prototype
原型链
原型如何发挥作用
了解了原型和原型对象,下面介绍它的用途。我们知道每个对象都可以调用toString方法,然而我们并没有编写这段代码,那么它在哪呢?答案是在原型对象中。
当要需要读取一个对象的属性或调用方法时:
- 首先会在对象内部查找对应的属性名或方法名,若查找到则返回
- 若没查找到,则会通过该对象的
__proto__
属性访问其构造函数的原型对象,在其构造函数的原型对象中查找,若查找到则返回 - 若没找到则继续通过原型对象的
__proto__
属性访问,直至某个对象的__proto__
属性值为null,则返回undefined
以上就是当访问一个对象的属性或方法的过程,而通过__proto__
的指向连接起来的多个原型对象就被称为原型链。
了解了上面调用方法流程之后就能解答开始提出的问题了,toString方法我们没有编写却可以调用,因此它一定在Object的原型链上。输入
console.log(Object.prototype)
输出
我们发现toString方法在Object的原型对象中,所以就可以解释为什么所有对象都可以调用toString方法了。
原型链的特点
所有的对象都由某个构造函数通过new关键字构造,而所有函数又是由Function构造的实例。因此我们创建的对象都可以通过原型链访问到Function.prototype
。再进一步,Function.prototype
又可以通过__proto__
访问到Object.prototype
,所以说Object.prototype
在所有对象的原型链上,因此Object.prototype
上的方法,所有对象都可以调用。
下面是引用自www.mollypages.org/ 的原型链的结构图完整版
原型链如何解释一些问题
instanceof
我们通常用instanceof
判断一个对象是否为某个函数的实例。
在控制台输入以下内容
Function instanceof Object
true
Object instanceof Function
true
instanceof是一个二元运算符,如:A instanceof B
. 其中,A必须是一个合法的JavaScript对象,B必须是一个合法的JavaScript函数 (function). 判断过程如下:
如果函数B在对象A的原型链 (prototype chain) 中被发现,那么instanceof操作符将返回true,否则返回false。
这就可以解释为什么上面两个表达式Function instanceof Object
和Object instanceof Function
的值均为true了。通过Function.__proto__.__proto__.constructor
可以访问到Object,而通过Object.__proto__.constructor
可以访问到Function,于是两个看似矛盾的表达式的返回值都是true。
Function和Object谁更本源
下面这四个表达式结果均为true
根据上面instanceof的解释,我在学习时产生了一个疑问,究竟是Function先出现构造了Object还是Object先出现构造了Function?这看起来似乎是一个鸡生蛋蛋生鸡的问题。这部分的讨论参见下面的帖子www.zhihu.com/question/31…
总结
原型链是js中比较重要的概念,但只要慢慢理清结构还是比较清晰的。下面是一些总结
- object类型的数据具有
__proto__
属性,其值指向构造函数的原型对象 - function类型的数据同时具有
__proto__
属性和prototype
属性,其中:__proto__
属性指向其构造函数也就是Function
的原型对象。prototype
属性指向其本身的原型对象
- 特殊的两个函数Object和Function:
Object.prototype
指向Object的原型对象,Object.__proto__
指向Function的原型对象Function.prototype
和Function.__proto__
都指向Function的原型对象Object.prototype.__proto__
的值为null,也就是Object的原型对象的__proto__属性值为null
- 访问实例对象属性或方法的顺序:首先查找自身,有则返回,若没有则访问当前对象的
__proto__
属性指向的对象重复此过程直到某个对象的__proto__
属性为null- 注:访问元素属性或方法时只通过不断查找对象的
__proto__
属性,并不会查找prototype
属性
- 注:访问元素属性或方法时只通过不断查找对象的
本文仅为学习过程中的心得与理解,若有不同意见欢迎评论探讨指正!!!
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:juejin.cn/post/707456…
参考资料
[1]blog.csdn.net/liuao107329…
[2]www.cnblogs.com/objectorl/a…
[3]www.zhihu.com/question/31…