起初,什麼都沒有。
造物主說:沒有東西本身也是一種東西啊,於是就有了null:
現在我們要造點兒東西出來。但是沒有原料怎麼辦?
有一個聲音說:不是有null嘛?
另一個聲音說:可是null代表無啊。
造物主說:那就無中生有吧!
於是:
JavaScript中的1號物件產生了,不妨把它叫做No. 1。
這個No. 1對象可不得了,它是真正的萬物始祖。它擁有的性質,是所有的物件都有的。
__proto__是什麼呢?是「生」的意思,或叫做繼承。
既然已經有了一個對象,剩下就好辦了,因為一生二,二生三,三生萬物嘛。
不過造物主很懶,他不想一個一個地親手製造物件。於是他做了一台能夠製造物件的機器:
他為這台機器取了一個名字:Object。
這台機器並不能憑空造出對象,它需要一個模板對象,依照這個模板對象來製造對象。很自然的,它把目前僅有的No. 1物件當作模板。圖中的prototype就代表機器的模板物件。
機器要如何啟動呢?透過new命令。你對著機器喊一聲:“new!”,對象就造出來了。
機器的產生,實現了物件的大量化自動化生產,解放了造物主的雙手。於是造物主忙別的去了。
如果機器只是按照模板的樣子,機械地複製出一模一樣的對象,那就太笨了。
人類的後代在繼承了父輩的性狀的基礎上,可以產生父輩沒有的性狀。同樣地,機器在製造物件時,除了繼承模板物件的屬性外,還可以新增新的屬性。這使得JavaScript世界越來越多元。
比如說,有一天Object機器製造一個對象,它有一個特殊的屬性,叫做flag,屬性值是10。用圖形表示是這樣的:
寫成程式碼就是:
var obj = new Object({ flag: 10 });
轟轟烈烈的創造運動開始了…
一天天過去了,造物主來視察工作。看到Object製造好了很多對象,他非常高興。
同時他也發現:根據「物以類聚」的原則,這些物件可以分成很多類別。聰明的造物主想,我何不多做幾台機器,讓每一台機器專門負責製造某一類物件呢?於是,他動手造出了幾台機器並給它們取了名字。它們分別是:
String:用來製造表示一段文字的物件。
Number:用來製造表示一個數字的物件。
Boolean:用來製造表示是與非的物件。
Array:用來製造有序佇列物件。
Date:用來製造表示一個日期的物件。
Error:用來製造表示一個錯誤的物件。
……
多台機器齊開動,各司其責,造物運動進入了一個新的階段……
造物主又開始思考了:雖然機器是用來製造物件的,但是機器本身其實也是一種特殊物件啊。現在有了這麼多機器,我得好好總結它們的共同特徵,把它們也納入物件體系。
於是,造物主基於No. 1對象,造出了一個No. 2對象,用它來表示所有機器的共同特徵。換句話說,把它當作所有機器的原型物件。
(註:__proto__寫起來太麻煩了,後面我們用[p]來代替)
當然了,和Object一樣,這些機器也需要各自有一個模板對象,也就是它們的prototype屬性指向的那個對象。顯然它們的模板物件應該是繼承自No. 1物件的,即
这张图显示了JavaScript世界中那些最基本的机器本身的原型链,以及它们的模板对象的原型链。不过看起来太复杂了,所以后面我们就不再把它们完整地画出来了。
造物主高兴地想:这下可好了,我造出了Object机器,实现了对象制造的自动化。然后又造出了String、Number等机器,实现了特定类别的对象制造的自动化。但是,为啥总感觉似乎还缺点什么呢?
对啦,还缺少一台制造机器的机器啊!
很快,万能的造物主就把它造了出来,并把它命名为Function。有了Function机器后,就可以实现自动化地制造机器了。
让我们来观察一下Function:
首先,Function是一台机器,所以它的原型对象也是No. 2对象。
其次,Function又是一台制造机器的机器,所以它的模板对象也是No. 2对象。
所以我们得到了Function的一个非常特别的性质:
Function.__proto__ === Function.prototype
哇,太奇妙了!
不要奇怪,这个性质不过是”Function是一台制造机器的机器“这个事实的必然结果。
从这张图中,我们发现:所有的函数(包括Function)的原型都是No. 2对象,而同时Function.prototype也是No. 2对象。这说明了:
从逻辑上,我们可以认为所有机器(包括Function自己)都是由Function制造出来的。
同时,如果再仔细瞧瞧,你会发现:
Object作为一个机器可以看做是有由Function制造出来的,而Function作为一个对象可以看做是由Object制造出来的。
这就是JavaScript世界的“鸡生蛋,蛋生鸡”问题。那么到底是谁生了谁呢?Whatever!
就像前面所说,机器用来制造某一类对象。正因如此,机器可以作为这类对象的标志,即面向对象语言中类(class)的概念。所以机器又被称为构造函数。在ES6引入class关键字之前,我们常常把构造函数叫做类。
然而,除了作为构造函数来制造对象外,函数通常还有另一个功能:做一件事情。正是有了这个功能,JavaScript的世界才由静变动,变得生机勃勃。
比如说,我们现在用Function机器制造了鸟类(即用来造鸟的机器):
function Bird(color) { this.color = color; }
然后,对着造鸟机说:“new!”,于是造鸟机发动起来,制造一个红色的鸟:
var redBird = new Bird('#FF0000');
如果现在我们想让鸟飞起来,该怎么办呢?我们需要再次用Function制造出一台机器,不过这台机器不是用来制造对象的,而是用来做事儿的,即“让鸟飞起来”这件事情:
// 这是一台通过晃动鸟的翅膀,让鸟飞起来的简陋的机器。 function makeBirdFly(bird) { shakeBirdWing(bird); }
我们知道,让一台制造对象的机器发动,只需要对它喊“new”即可;那么怎样让一台做事情的机器发动呢?更简单,对它咳嗽一声就行了。咳咳咳,
makeBirdFly(redBird);
于是红鸟飞了起来,世界充满了生机。
从上面的Bird和makeBirdFly的定义可以看出:实际上,制造对象的机器和做事情的机器没什么明显区别,不同的只是它们的使用方式。在两种情况下,它们分别被叫做构造函数和普通函数。
说明1:function xxx语法可以看成new Function的等价形式。
说明2:用户自定义的函数通常既可以作为普通函数使用,又可以作为构造函数来制造对象。ES6新增的class语法定义的函数只能作为构造函数,ES6新增的=>语法定义的箭头函数只能作为普通函数。
造物主对目前的世界还是不太满意,因为几乎所有的机器的模板对象都是No. 2,这使得JavaScript世界看起来有点扁。
于是造物主再次研究世界万物的分类问题。他发现有些对象会动、还会吃东西,于是把它们叫做动物,然后造了一台Animal机器来制造它们。他进一步发现,即使都是动物,也还是可以进一步分类,比如有些会飞、有些会游,他分别把它们叫做鸟类、鱼类。于是他想,我何不单独造几台机器,专门用来制造某一类动物呢。于是它造出了Bird、Fish等机器。
接下来,在选择这些机器的模板对象时碰到一个问题:如果还像之前那样直接复制一个No. 1对象作为Bird、Fish的模板,那么结果就是这样的:
这样可不好。首先没体现出鸟类、鱼类跟动物的关系,其次它们的模板对象存了重复的东西,这可是一种浪费啊。怎么办呢?简单,让Bird和Fish的模板对象继承自Animal的模板对象就好了。就是说
Bird.prototype.__proto__ === Animal.prototype Fish.prototype.__proto__ === Animal.prototype
于是:
用同样的方法,造物主造出了一个立体得多的JavaScript世界。
然而这样还不够。虽然那些纯对象现在充满了层次感,但是那些机器对象之间的关系还是扁平的:
那又该怎么办呢?其实用类似的办法就行了:
为了更方便地做到这一点,造物主发明了class关键字。
经过一番折腾,JavaScript世界发生了大变化。变得丰富多彩,同时变得很复杂。用一张图再也没法画出它的全貌,只能画出冰山一角:
JavaScript的世界还在不断进化中……
以上就是JavaScript世界万物诞生记的详细介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!