您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)javascript中new操作符如何使用的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。
相信很多才接觸前端的小伙伴甚至工作幾年的前端小伙伴對new這個操作符的了解還停留在一知半解的地步,比較模糊。
就比如前不久接觸到一個入職兩年的前端小伙伴,他告訴我new是用來創(chuàng)建對象的,無可厚非,可能很多人都會這么答!
那這么答到底是錯很是對呢?
下面我們?nèi)鎭碛懻撘幌逻@個問題:
我們要拿到一個對象,有很多方式,其中最常見的一種便是對象字面量:
var obj = {}
但是從語法上來看,這就是一個賦值語句,是把對面字面量賦值給了obj這個變量(這樣說或許不是很準確,其實這里是得到了一個對象的實例?。。?/p>
很多時候,我們說要創(chuàng)建一個對象,很多小伙伴雙手一摸鍵盤,啪啪幾下就敲出了這句代碼。
上面說了,這句話其實只是得到了一個對象的實例,那這句代碼到底還能不能和創(chuàng)建對象畫上等號呢?我們繼續(xù)往下看。
要拿到一個對象的實例,還有一種和對象字面量等價的做法就是構(gòu)造函數(shù):
var obj = new Object()
這句代碼一敲出來,相信小伙伴們對剛才我說的obj
只是一個實例對象沒有異議了吧!那很多小伙伴又會問了:這不就是new
了一個新對象出來嘛!
沒錯,這確實是new了一個新對象出來,因為javascript之中,萬物解釋對象,obj是一個對象,而且是通過new運算符得到的,所以說很多小伙伴就肯定的說:new就是用來創(chuàng)建對象的!
這就不難解釋很多人把創(chuàng)建對象和實例化對象混為一談!!
我們在換個思路看看:既然js一切皆為對象,那為什么還需要創(chuàng)建對象呢?本身就是對象,我們何來創(chuàng)建一說?那我們可不可以把這是一種繼承
呢?
說了這么多,相信不少伙伴已經(jīng)看暈了,但是我們的目的就是一個:理清new是來做繼承的而不是所謂的創(chuàng)建對象??!
那繼承得到的實例對象有什么特點呢?
下面是一段經(jīng)典的繼承,通過這段代碼來熱熱身,好戲馬上開始:
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '漢' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } var person = new Person('小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
現(xiàn)在我們來解決第一個問題:我們可以通過什么方式實現(xiàn)訪問到構(gòu)造函數(shù)里面的屬性呢?答案是call
或apply
function Parent() { this.name = ['A', 'B'] } function Child() { Parent.call(this) } var child = new Child() console.log(child.name) // ['A', 'B'] child.name.push('C') console.log(child.name) // ['A', 'B', 'C']
第一個問題解決了,那我們又來解決第二個:那又怎么訪問原型鏈上的屬性呢?答案是__proto__
現(xiàn)在我們把上面那段熱身代碼稍加改造,不使用new來創(chuàng)建實例:
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '漢' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } // var person = new Person('小明', 25) var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say() function New() { var obj = {} Constructor = [].shift.call(arguments) // 獲取arguments第一個參數(shù):構(gòu)造函數(shù) // 注意:此時的arguments參數(shù)在shift()方法的截取后只剩下兩個元素 obj.__proto__ = Constructor.prototype // 把構(gòu)造函數(shù)的原型賦值給obj對象 Constructor.apply(obj, arguments) // 改變夠著函數(shù)指針,指向obj,這是剛才上面說到的訪問構(gòu)造函數(shù)里面的屬性和方法的方式 return obj }
以上代碼中的New函數(shù),就是new操作符的實現(xiàn)
主要步驟:
可能很多小伙伴看到這里覺得new不就是做了這些事情嗎,然而~~
然而我們卻忽略了一點,js里面的函數(shù)是有返回值的,即使構(gòu)造函數(shù)也不例外。
如果我們在構(gòu)造函數(shù)里面返回一個對象或一個基本值,上面的New函數(shù)會怎樣?
我們再來看一段代碼:
function Person(name, age) { this.name = name this.age = age this.gender = '男' return { name: name, gender: '男' } } Person.prototype.nation = '漢' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } var person = new Person('小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
執(zhí)行代碼,發(fā)現(xiàn)只有name
和gender
這兩個字段如期輸出,age
、nation
為undefined,say()
報錯。
改一下代碼構(gòu)造函數(shù)的代碼:
function Person(name, age) { this.name = name this.age = age this.gender = '男' // return { // name: name, // gender: '男' // } return 1 } // ...
執(zhí)行一下代碼,發(fā)現(xiàn)所有字段終于如期輸出。
這里做個小結(jié):
那我們現(xiàn)在來考慮下New函數(shù)要怎么改才能實現(xiàn)上面總結(jié)的兩點功能呢?繼續(xù)往下看:
function Person(name, age) { // ... } function New() { var obj = {} Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj return typeof result === 'object' ? result : obj } var person = New(Person, '小明', 25) console.log(person.name) // ...
執(zhí)行此代碼,發(fā)現(xiàn)已經(jīng)實現(xiàn)了上面總結(jié)的兩點。
解決方案:使用變量接收構(gòu)造函數(shù)的返回值,然后在New函數(shù)里面判斷一下返回值類型,根據(jù)不同類型返回不同的值。
看到這里。又有小伙伴說,這下new已經(jīng)完全實現(xiàn)了吧???!答案肯定是否定的,下面我們繼續(xù)看一段代碼:
function Person(name, age) { this.name = name this.age = age this.gender = '男' // 返回引用類型 // return { // name: name, // gender: '男' // } // 返回基本類型 // return 1 // 例外 return null }
再執(zhí)行代碼,發(fā)現(xiàn)又出問題了?。?!
那為什么會出現(xiàn)這個問題呢?
剛才不是總結(jié)了返回基本類型時構(gòu)造函數(shù)不受影響嗎,而null就是基本類型???
此時心里一萬頭草泥馬在奔騰啊有木有?。?!
解惑:null是基本類型沒錯,但是使用操作符typeof后我們不難發(fā)現(xiàn):
typeof null === 'object' // true
特例:typeof null
返回為'object'
,因為特殊值null
被認為是一個空的對象引用
。
明白了這一點,那問題就好解決了:
function Person(name, age) { // ... } function New() { var obj = {} Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) // ...
解決方案:判斷一下構(gòu)造函數(shù)返回值result,如果result是一個引用(引用類型和null),就返回result,但如果此時result為false(null),就使用操作符||
之后的obj
好了,到現(xiàn)在應(yīng)該又有小伙伴發(fā)問了,這下New函數(shù)是徹徹底底實現(xiàn)了吧?。?!
答案是,離完成不遠了??!
別急,在功能上,New函數(shù)基本完成了,但是在代碼嚴謹度上,我們還需要做一點工作,繼續(xù)往下看:
這里,我們在文章開篇做的鋪墊要派上用場了:
var obj = {}
實際上等價于
var obj = new Object()
前面說了,以上兩段代碼其實只是獲取了object對象的一個實例。再者,我們本來就是要實現(xiàn)new,但是我們在實現(xiàn)new的過程中卻使用了new
!
這個問題把我們引入到了到底是先有雞還是先有蛋的問題上!
這里,我們就要考慮到ECMAScript底層的API了————Object.create(null)
這句代碼的意思才是真真切切地創(chuàng)建
了一個對象??!
function Person(name, age) { // ... } function New() { // var obj = {} // var obj = new Object() var obj = Object.create(null) Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) // 這樣改了之后,以下兩句先注釋掉,原因后面再討論 // console.log(person.nation) // person.say()
好了好了,小伙伴常常舒了一口氣,這樣總算完成了??!
但是,這樣寫,新的問題又來了。
小伙伴:啥?還有完沒完?
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '漢' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } function New() { // var obj = {} // var obj = new Object() var obj = Object.create(null) Constructor = [].shift.call(arguments) obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) // 這里解開剛才的注釋 console.log(person.nation) person.say()
別急,我們執(zhí)行一下修改后的代碼,發(fā)現(xiàn)原型鏈上的屬性nation
和方法say()
報錯,這又是為什么呢?
從上圖我們可以清除地看到,Object.create(null)
創(chuàng)建的對象是沒有原型鏈的,而后兩個對象則是擁有__proto__
屬性,擁有原型鏈,這也證明了后兩個對象是通過繼承得來的。
那既然通過Object.create(null)
創(chuàng)建的對象沒有原型鏈(原型鏈斷了),那我們在創(chuàng)建對象的時候把原型鏈加上不就行了,那怎么加呢?
function Person(name, age) { this.name = name this.age = age this.gender = '男' } Person.prototype.nation = '漢' Person.prototype.say = function() { console.log(`My name is ${this.age}`) } function New() { Constructor = [].shift.call(arguments) // var obj = {} // var obj = new Object() // var obj = Object.create(null) var obj = Object.create(Constructor.prototype) // obj.__proto__ = Constructor.prototype // Constructor.apply(obj, arguments) var result = Constructor.apply(obj, arguments) // return obj // return typeof result === 'object' ? result : obj return typeof result === 'object' ? result || obj : obj } var person = New(Person, '小明', 25) console.log(person.name) console.log(person.age) console.log(person.gender) console.log(person.nation) person.say()
這樣創(chuàng)建的對象就擁有了它初始的原型鏈了,這個原型鏈是我們傳進來的構(gòu)造函數(shù)賦予它的。
也就是說,我們在創(chuàng)建新對象的時候,就為它指定了原型鏈了,新創(chuàng)建的對象繼承自傳進來的構(gòu)造函數(shù)!
現(xiàn)在,我們來梳理下最終的New函數(shù)做了什么事,也就是本文討論的結(jié)果————new操作符到底做了什么?
Constructor
;Constructor
的原型鏈結(jié)合Object.create
來創(chuàng)建
一個對象,此時新對象的原型鏈為Constructor
函數(shù)的原型對象;(結(jié)合我們上面討論的,要訪問原型鏈上面的屬性和方法,要使用實例對象的__proto__屬性)Constructor
函數(shù)的this指向,指向新創(chuàng)建的實例對象,然后call
方法再調(diào)用Constructor
函數(shù),為新對象賦予屬性和方法;(結(jié)合我們上面討論的,要訪問構(gòu)造函數(shù)的屬性和方法,要使用call或apply)Constructor
函數(shù)的一個實例對象。現(xiàn)在我,我們來回答文章開始時提出的問題,new是用來創(chuàng)建對象的嗎?
現(xiàn)在我們可以勇敢的回答,new是用來做繼承的,而創(chuàng)建對象的其實是Object.create(null)。
在new操作符的作用下,我們使用新創(chuàng)建的對象去繼承了他的構(gòu)造函數(shù)上的屬性和方法、以及他的原型鏈上的屬性和方法!
寫在最后:
補充一點關(guān)于原型鏈的知識:
- JavaScript中的函數(shù)也是對象,而且對象除了使用字面量定義外,都需要通過函數(shù)來創(chuàng)建對象
- prototype屬性可以給函數(shù)和對象添加可共享(繼承)的方法、屬性,而__proto__是查找某函數(shù)或?qū)ο蟮脑玩湻绞?/li>
- prototype和__proto__都指向原型對象
- 任意一個函數(shù)(包括構(gòu)造函數(shù))都有一個prototype屬性,指向該函數(shù)的原型對象
- 任意一個實例化的對象,都有一個__proto__屬性,指向構(gòu)造函數(shù)的原型對象。
感謝各位的閱讀!關(guān)于javascript中new操作符如何使用就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。