清明節(jié)來了,希望大家不要太悲傷!沒有對(duì)象的趕緊出對(duì)象,俗話說的好,萬物皆對(duì)象,JavaScript 中,為什么還要通過 new 來產(chǎn)生對(duì)象? 帶著這個(gè)問題,我們一步步來分析和理解 new 的一些特性:
1、認(rèn)識(shí) new 操作符
創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比寧鄉(xiāng)網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式寧鄉(xiāng)網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋寧鄉(xiāng)地區(qū)。費(fèi)用合理售后完善,十載實(shí)體公司更值得信賴。
console.log(
Animal.name, //Animal
Animal.color //back
);
Animal.say(); //Animal.say is not a function
代碼解讀如下:
L1-3: 創(chuàng)建了一個(gè)函數(shù)Animal,并在其 this 上定義了屬性:name,name的值是函數(shù)被執(zhí)行時(shí)的形參。
L4 : 在 Animal 對(duì)象(Animal本身是一個(gè)函數(shù)對(duì)象)上定義了一個(gè)靜態(tài)屬性:color,并賦值“black”
L5-7:在 Animal 函數(shù)的原型對(duì)象 prototype 上定義了一個(gè) say() 方法,say方法輸出了 this 的 name 值。
L8: 通過 new 關(guān)鍵字創(chuàng)建了一個(gè)新對(duì)象 cat
L10-14: cat 對(duì)象嘗試訪問 name 和 color 屬性,并調(diào)用 say 方法。
L16-20: Animal 對(duì)象嘗試訪問 name 和 color 屬性,并調(diào)用 say 方法。
2、剖析 new 的內(nèi)部原理
第8行代碼是關(guān)鍵:
var cat = new Animal("cat");
Animal 本身是一個(gè)普通函數(shù),但當(dāng)通過new來創(chuàng)建對(duì)象時(shí),Animal 就是構(gòu)造函數(shù)。
JS引擎執(zhí)行這句代碼時(shí),在內(nèi)部做了很多工作,用偽代碼模擬其內(nèi)部流程如下:
new Animal("cat") = {
var obj = {};
obj.__proto__ = Animal.prototype;
var result = Animal.call(obj,"cat");
return typeof result === 'object'? result : obj;
}
將上述流程分為 4 個(gè)步驟來理解:
【1】創(chuàng)建一個(gè)空對(duì)象 obj;
【2】把 obj 的proto指向構(gòu)造函數(shù) Animal 的原型對(duì)象 prototype,此時(shí)便建立了 obj 對(duì)象的原型鏈:obj->Animal.prototype->Object.prototype->null
【3】在 obj 對(duì)象的執(zhí)行環(huán)境調(diào)用 Animal 函數(shù)并傳遞參數(shù) “ cat ” 。 相當(dāng)于 var result = obj.Animal("cat")。
當(dāng)這句執(zhí)行完之后,obj 便產(chǎn)生了屬性 name 并賦值為 "cat"。關(guān)于 call 的用法請(qǐng)參考:深入理解 call、apply 和 bind
【4】考察第 3 步的返回值,如果無返回值 或者 返回一個(gè)非對(duì)象值,則將 obj 作為新對(duì)象返回;否則會(huì)將 result 作為新對(duì)象返回。
根據(jù)以上過程,我們發(fā)現(xiàn) cat 其實(shí)就是【4】的返回值,因此我們對(duì) cat 對(duì)象的認(rèn)知就多了一些:
cat的原型鏈?zhǔn)牵篶at->Animal.prototype->Object.prototype->null
cat上新增了一個(gè)屬性:name
分析完了 cat 的產(chǎn)生過程,我們?cè)俜治鲆幌螺敵鼋Y(jié)果:
cat.name : 在【3】中,obj 對(duì)象就產(chǎn)生了 name 屬性。因此 cat.name 就是這里的 obj.name
cat.color: cat 對(duì)象先查找自身的 color,沒有找到便會(huì)沿著原型鏈查找,在上述例子中,我們僅在 Animal 對(duì)象上定義了 color,并沒有在其原型鏈上定義,因此找不到。
cat.say: cat會(huì)先查找自身的 say 方法,沒有找到便會(huì)沿著原型鏈查找,在上述例子中,我們?cè)?Animal 的 prototype 上定義了say,因此在原型鏈上找到了say 方法。
另外,在 say 方法中還訪問 this.name,這里的 this 指的是其調(diào)用者 obj,因此輸出的是 obj.name 的值。
對(duì)于Animal來說,它本身也是一個(gè)對(duì)象,因此它在訪問屬性和方法時(shí)也遵守上述查找規(guī)則,所以:
Animal.color -> " black "
Animal.name -> " Animal "
Animal.say() -> Animal.say is not a function
需要注意的是,Animal 先查找自身的 name,找到了 name,但這個(gè) name 并不是我們定義的 name,而是函數(shù)對(duì)象內(nèi)置的屬性,一般情況下,函數(shù)對(duì)象在產(chǎn)生時(shí)會(huì)內(nèi)置 name 屬性并將函數(shù)名作為賦值(僅函數(shù)對(duì)象)。
另外,Animal 在自身沒有找到 say() 方法,也會(huì)沿著其原型鏈查找,Animal 的原型鏈如下所示:
Animal.proto
function(){}
Animal.proto== Function.prototype
true
Function.prototype.proto== Object.prototype
true
Object.prototype.proto
null
Animal 的原型鏈: Animal->Function.prototype->Object.prototype->null
由于 Animal 的原型鏈上也沒有定義 say 方法,因此返回異常提示。
3、探索 new 的真正意義
對(duì) new 運(yùn)算符有了較深入的理解之后,我們?cè)倩氐介_篇提到的問題:在JavaScript 中,萬物皆對(duì)象,為什么還要通過 new 來產(chǎn)生對(duì)象?
要弄明白這個(gè)問題,我們首先要搞清楚 cat 和 Animal 的關(guān)系:
【1】cat 繼承了 Animal 對(duì)象
通過上面的分析我們發(fā)現(xiàn), cat 通過原型鏈繼承了 Animal 中的部分屬性,因此我們可以簡(jiǎn)單的認(rèn)為:Animal 和 cat 是繼承關(guān)系。
【2】cat 是 Animal 的實(shí)例
cat 是通過 new 產(chǎn)生的對(duì)象,那么 cat 到底是不是 Animal 的實(shí)例對(duì)象? 我們先來了解一下JS是如何來定義實(shí)例對(duì)象:
A instanceof B
如果上述表達(dá)式為 true,JavaScript 認(rèn)為 A 是 B 的實(shí)例對(duì)象,我們用這個(gè)方法來判斷一下cat 和 Animal
cat instanceof Animal; //true
從結(jié)果看,cat 確實(shí)是 Animal 實(shí)例,要想更加證實(shí)這個(gè)結(jié)果,我們?cè)賮砹私庖幌?instanceof 的內(nèi)部原理:
var L = A.proto;
var R = B.prototype;
if(L === R)
return true;
如果 A 的proto等價(jià)于 B 的 prototype,就返回true
在 new 的執(zhí)行過程【2】中,cat 的 proto指向了Animal 的 prototype,所以 cat 和 Animal 符合 instanceof 的判斷結(jié)果。
因此,通過 new 創(chuàng)建的 對(duì)象 和 構(gòu)造函數(shù) 之間建立了一條原型鏈,原型鏈的建立,讓原本孤立的對(duì)象有了依賴關(guān)系和繼承能力,讓JavaScript 對(duì)象能以更合適的方式來映射真實(shí)世界里的對(duì)象,這是面向?qū)ο蟮谋举|(zhì)。
4、實(shí)戰(zhàn)演練
下面是一個(gè)經(jīng)典例子,涉及 new 、this、以及 原型鏈 相關(guān)問題,請(qǐng)看代碼:
function Foo(){
getName = function(){
console.log(1)
}
return this;
}
Foo.getName = function(){
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
var getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
//output : ?
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();