Object 和 Function 便是 JavaScript 自带的两个典型的「函数对象」。而函数对象就是一个纯函数,所谓的函数对象,其实就是使用 JavaScript 在模拟类。
那么,什么是普通对象,什么又是函数对象呢?
先创建三个 Function 和 Object 的实例。
function fn1() {};
var fn2 = function() {};
var fn3 = new Function('getName', 'console.log("Frankie")');
var obj1 = {};
var obj2 = new Object();
var obj3 = new fn1();
打印以下结果,可以得到:
console.log(typeof Object); // function
console.log(typeof Function); // function
console.log(typeof obj1); // object
console.log(typeof obj2); // object
console.log(typeof obj3); // object
console.log(typeof fn1); // function
console.log(typeof fn2); // function
console.log(typeof fn3); // function
在上述的例子中,obj1、obj2、obj3 为 普通对象(均为 Object 的实例),而 fn1、fn2、fn3 为 函数对象(均是 Function 的实例)。
如何区分呢?记住这句话就行了:
所有 Function 的实例都是函数对象,而其他的都是普通对象。
说到这里,细心的同学会发表一个疑问。文中开头,我们提到 Object 和 Function 均是 函数对象,而这里又说:所有的 Function 的实例都是 函数对象,难道 Function 也是 Function 的实例吗?(先留下疑问)
引入:普通对象与函数对象
在 JavaScript 中,一直有一种说法,万物皆对象。
事实上,在 JavaScript 中,对象也是有区别的,我们可以将其划分为「普通对象」和「函数对象」。
Object
和Function
便是 JavaScript 自带的两个典型的「函数对象」。而函数对象就是一个纯函数,所谓的函数对象,其实就是使用 JavaScript 在模拟类。那么,什么是普通对象,什么又是函数对象呢?
先创建三个
Function
和Object
的实例。打印以下结果,可以得到:
在上述的例子中,
obj1
、obj2
、obj3
为普通对象
(均为 Object 的实例),而fn1
、fn2
、fn3
为函数对象
(均是Function
的实例)。如何区分呢?记住这句话就行了:
所有 Function 的实例都是函数对象,而其他的都是普通对象。
说到这里,细心的同学会发表一个疑问。文中开头,我们提到
Object
和Function
均是函数对象
,而这里又说:所有的Function
的实例都是函数对象
,难道Function
也是Function
的实例吗?(先留下疑问)从图中可以看出,对象本身的实现还是要依靠构造函数,那
原型链
到底是用来干嘛的呢?众所周知,作为一门面向对象的语言,必定具有以下特征:
原型链最大的目的就是为了实现继承。
进阶:prototype 与 __proto__
原型链究竟是如何实现继承的呢?
首先,我们要引入介绍两兄弟:
prototype
和__proto__
,这是在 JavaScript 中无处不在的两个变量,然而,这两个变量并不是在所有的对象上都存在。首先,我们先给出以下结论:
prototype
属性。prototype
和__proto__
都是 JavaScript 在定义一个函数或对象时自动创建的预定义属性。也就是说,下面代码成立:
看起来很酷,结论瞬间被证明,感觉是不是很爽,那么问题来了:既然
fn
是一个函数对象,那么fn.prototype.__proto__
到底等于什么?这是我尝试去解决这个问题的过程:
typeof
得到fn.prototype
的类型"object"
;"object"
,那fn.prototype
岂不是Object
的实例?我们验证一下:接下来,如果要你快速地写出,在创建一个函数时,JavaScript 对该函数原型的初始化代码,你是不是也能快速地写出:
到这里,你是否有一丝恍然大悟的感觉?
此外,因为普通对象就是通过函数对象实例化(
new
)得到的,而一个实例不可能再次进行实例化,也就不会让另一个对象的__proto__
指向它的prototype
,隐藏本节一开始提到的「普通对象没有prototype
属性」的结论似乎非常好理解了。从上述的分析,我们还可以看出,fn.prototype
就是一个普通的对象,它也不存在prototype
属性。再回顾下上一节,我们还遗留了一个疑问:难道
Function
也是Function
的实例?是时候去掉「应该」让它成立了:
重点:原型链
上一节,我们详解了
prototype
和__proto__
。实际上,这两兄弟主要就是为了构造原型链而存在的。先上一段代码:
解析一下执行细节:
Person
,要注意前面已经提到,此时Person.prototype
已经被自动创建,它包含constructor
和__proto__
这两个属性;Person.prototype
增加一个方法getName()
;Person.prototype
增加一个方法getAge()
;Person
创建一个person
实例,值得注意的是,一个构造函数在实例化时,一定会自动执行该构造函数。person
应该是:结合上一节的经验,以下等式成立:
person
中找不到getName()
和getAge()
这两个方法,就会继续朝着原型链上查找,也就是通过__proto__
向上查找,于是很快在person.__proto__
中,即Person.prototype
中找到了这两个方法,于是停止查找并执行得到结果。这便是 JavaScript 的原型继承。准确的说,JavaScript 的原型继承是通过
__proto__
并借助prototype
来实现的。于是,我们可以作以下总结:
__proto__
指向Function.prototype
;prototype
指向instance.__proto__
;__proto__
指向Object.prototype
;prototype
属性;obj.__proto__
,也就是访问该对象的构造函数的原型objCtr.prototype
,若仍然找不到,会继续查找objCtr.prototype.__proto__
,像依次地查找下去。若在某一刻,找到该属性,则会立刻返回值并停止对原型链的搜索,若找不到,则返回undefined
。为了检验你对上述的理解,请分析下面两个问题:
Person.__proto__
和Person.prototype.__proto__
分别指向何处?终极:原型链图
上一节,我们实际上还遗留了一个疑问:原型链如果一个搜索下去,如果找不到,那何时停止呢?也就是说,原型链的尽头是哪里?
我们可以快速地利用下面的代码验证:
很显然,上述输出
undefined
。下面简述查找过程:原来路的尽头是一场空。
最后,再回过头来看看上一节的那演示代码:
我们来画一个原型链图,或者说,将其整个原型链图画出来?请看下图:
画完这张图,基本上之前所有的疑问都可以解答了。
与其说万物皆对象,万物皆空似乎更加形象。
调料:constructor
前面已经有所提及,但只有原型对象才具有
constructor
这个属性,constructor
用来指向引用它的函数对象。这是一种循环引用。当然你也可以在上一节的原型链图中画上去,这里就不赘述了。
补充 JavaScript中的 六大内置(函数)对象的原型继承
通过前文的论述,结合相应的代码验证,整理出以下原型链图:
由此可见,我们更加强化了这两个关掉:
总结
来几个总结:
若
A
通过new
创建了B
,则B.__proto__ = A.prototype
。__proto__
是原型链查找的起点;执行
B.a
,若在B
中找不到a
,则会在B.__proto__
中,也就是A.prototype
中查找,若A.prototype
中仍然没有,则会继续向上查找,最终,一定会找到Object.prototype
,倘若还找不到,因为Object.prototype.__proto__
指向null
,因此会返回undefined
;为什么万物皆空,还是那句话,原型链的顶端,一定有
Object.prototype.__proto__
等于null
。最后抛出一个问题:
如何用 JavaScript 实现类的继承?