前言
在學(xué)習(xí)javascript的過(guò)程中,不可避免的會(huì)遇到new操作符,這次就來(lái)好好刨根問(wèn)底一下,也算是加深理解和記憶了。
什么是new操作符?
mdn中是這么定義new操作符的:
new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對(duì)象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象的實(shí)例。
在這句話里我們來(lái)看一個(gè)關(guān)鍵詞:具有構(gòu)造函數(shù)。這是個(gè)什么意思呢?我們先通過(guò)幾個(gè)例子來(lái)看一下:
//例1let animal1=function(){this.name=1};let animal=new animal1; //這里不帶()相當(dāng)于不傳參數(shù)//=>animal1 {name: 1}//例2let testobj={}let t1=new testobj;//=>uncaught typeerror: testobj is not a constructor復(fù)制代碼我們可以看到,例1成功的執(zhí)行了new語(yǔ)句,創(chuàng)建出了實(shí)例。例2在new一個(gè){}對(duì)象時(shí)報(bào)錯(cuò)typeerror: testobj is not a constructor,指出目標(biāo)不是一個(gè)constructor。為什么普通的對(duì)象就不能執(zhí)行new操作符呢?在ecma規(guī)范里有相關(guān)的介紹:
if type(argument) is not object, return false.
if argument has a [[construct]] internal method, return true.
return false.
意思就是:
構(gòu)造函數(shù)首先得是一個(gè)對(duì)象,否則不滿足條件其次,對(duì)象必須擁有[[construct]]內(nèi)部方法,才可以作為構(gòu)造函數(shù)
我們這里的{}就是一個(gè)對(duì)象,滿足第一個(gè)條件,那么顯然,肯定是因?yàn)閧}沒(méi)有[[construct]]這個(gè)內(nèi)部方法,所以無(wú)法使用new操作符進(jìn)行構(gòu)造了。
那么我們已經(jīng)搞定了new操作符的可操作對(duì)象,是不是可以去看看它的作用了呢?答案是:no!我們?cè)賮?lái)看一個(gè)例子:
//例3let testobj={ fn(){ console.log("構(gòu)造成功!") }}let t3=new testobj.fn;//=>uncaught typeerror: testobj.fn is not a constructor復(fù)制代碼what?為什么剛剛還能成功構(gòu)造的函數(shù),作為方法就不行了呢?其實(shí)在mdn中也有直接介紹:
methods cannot be constructors! they will throw a typeerror if you try to instantiate them.
意思就是,方法不能是構(gòu)造函數(shù),如果嘗試創(chuàng)建一個(gè)方法的實(shí)例,就會(huì)拋出類型錯(cuò)誤。這樣說(shuō)就懂了,但是還沒(méi)完,這個(gè)說(shuō)法沒(méi)有完全解釋清楚原理,我們?cè)倏磦€(gè)例子:
//例4const example = { fn: function() { console.log(this); }, arrow: () => { console.log(this); }, shorthand() { console.log(this); }};new example.fn(); // fn {}new example.arrow(); // uncaught typeerror: example.arrow is not a constructornew example.shorthand(); // uncaught typeerror: example.shorthand is not a constructor復(fù)制代碼對(duì)照這個(gè)例子,我們?cè)趀cma規(guī)范查閱,發(fā)現(xiàn)所有的函數(shù)在創(chuàng)建時(shí)都取決于functioncreate函數(shù):
functioncreate (kind, parameterlist, body, scope, strict, prototype)
if the prototype argument was not passed, then let prototype be the intrinsic object %functionprototype%.if "kind" is not normal, let allockind be "non-constructor".
這個(gè)函數(shù)的定義可以看出
只有當(dāng)類型為normal的函數(shù)被創(chuàng)建時(shí),它才是可構(gòu)造的函數(shù),否則他就是不可構(gòu)造的。
在我們這個(gè)例子中,arrow的類型為arrow,而shorthand的類型是method,因此都不屬于可構(gòu)造的函數(shù),這也解釋了例3所說(shuō)的"方法不能作為構(gòu)造函數(shù)"。
搞清楚了new操作符可以操作的目標(biāo),終于可以神清氣爽的來(lái)看看它的作用了(不容易呀tat)。
new操作符實(shí)現(xiàn)了什么?
我們舉一個(gè)簡(jiǎn)單的例子來(lái)具體看看它的作用:
function animal(name){ this.name=name; console.log("create animal");}let animal=new animal("大黃"); //create animalconsole.log(animal.name); //大黃animal.prototype.say=function(){ console.log("myname is:" this.name);}animal.say(); //myname is:大黃復(fù)制代碼我們從這個(gè)例子來(lái)分析一下,首先我們看這一句:
let animal=new animal("大黃");復(fù)制代碼可以看到,執(zhí)行new操作符后,我們得到了一個(gè)animal對(duì)象,那么我們就知道,new操作符肯定要?jiǎng)?chuàng)建一個(gè)對(duì)象,并將這個(gè)對(duì)象返回。再看這段代碼:
function animal(name){ this.name=name; console.log("create animal");}復(fù)制代碼同時(shí)我們看到結(jié)果,確實(shí)輸出了create animal,我們就知道,animal函數(shù)體在這個(gè)過(guò)程中被執(zhí)行了,同時(shí)傳入了參數(shù),所以才執(zhí)行了我們的輸出語(yǔ)句。但我們的函數(shù)體里還有一句this.name=name體現(xiàn)在哪里呢?就是這一句:
console.log(animal.name); //大黃復(fù)制代碼執(zhí)行完函數(shù)體后,我們發(fā)現(xiàn)返回對(duì)象的name值就是我們賦值給this的值,那么不難判斷,在這個(gè)過(guò)程中,this的值指向了新創(chuàng)建的對(duì)象。最后還有一段:
animal.prototype.say=function(){ console.log("myname is:" this.name);}animal.say(); //myname is:大黃復(fù)制代碼animal對(duì)象調(diào)用的是animal函數(shù)原型上的方法,說(shuō)明animal在animal對(duì)象的原型鏈上,那么在哪一層呢?我們驗(yàn)證一下:
animal.__proto__===animal.prototype; //true復(fù)制代碼那我們就知道了,animal的__proto__直接指向了animal的prototype。
除此之外,如果我們?cè)跇?gòu)造函數(shù)的函數(shù)體里返回一個(gè)值,看看會(huì)怎么樣:
function animal(name){ this.name=name; return 1;}new animal("test"); //animal {name: "test"}復(fù)制代碼可以看到,直接無(wú)視了返回值,那我們返回一個(gè)對(duì)象試試:
function animal(name){ this.name=name; return {};}new animal("test"); //{}復(fù)制代碼我們發(fā)現(xiàn)返回的實(shí)例對(duì)象被我們的返回值覆蓋了,到這里大致了解了new操作符的核心功能,我們做一個(gè)小結(jié)。
小結(jié)
new操作符的作用:
創(chuàng)建一個(gè)新對(duì)象,將this綁定到新創(chuàng)建的對(duì)象使用傳入的參數(shù)調(diào)用構(gòu)造函數(shù)將創(chuàng)建的對(duì)象的_proto__指向構(gòu)造函數(shù)的prototype如果構(gòu)造函數(shù)沒(méi)有顯式返回一個(gè)對(duì)象,則返回創(chuàng)建的新對(duì)象,否則返回顯式返回的對(duì)象(如上文的{})模擬實(shí)現(xiàn)一個(gè)new操作符
說(shuō)了這么多理論的,最后我們親自動(dòng)手來(lái)實(shí)現(xiàn)一個(gè)new操作符吧~
var _mynew = function (constructor, ...args) { // 1. 創(chuàng)建一個(gè)新對(duì)象obj const obj = {}; //2. 將this綁定到新對(duì)象上,并使用傳入的參數(shù)調(diào)用函數(shù) //這里是為了拿到第一個(gè)參數(shù),就是傳入的構(gòu)造函數(shù) // let constructor = array.prototype.shift.call(arguments); //綁定this的同時(shí)調(diào)用函數(shù),...將參數(shù)展開(kāi)傳入 let re