深入聊一聊JS中new的原理與實(shí)現(xiàn)
定義
new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對(duì)象類(lèi)型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象的實(shí)例。
使用new [constructor]的方式來(lái)創(chuàng)建一個(gè)對(duì)象實(shí)例,但構(gòu)造函數(shù)的差異會(huì)導(dǎo)致創(chuàng)建的實(shí)例不同。
構(gòu)造函數(shù)體不同
構(gòu)造函數(shù)也是函數(shù),其唯一的區(qū)別就是調(diào)用方式不同,任何函數(shù)只要使用 new 操作符調(diào)用就是構(gòu)造函數(shù),而不使用 new 操作符調(diào)用的函數(shù)就是普通函數(shù)。
因此構(gòu)造函數(shù)也可以帶有返回值,但是這會(huì)導(dǎo)致new的結(jié)果不同。
無(wú)返回值
function Person(name) { this.name = name; } let obj = new Person("Jalenl"); console.log(obj);
顯然,打印的是{name:'Jalenl'}
返回對(duì)象
function Person(age) { this.age = age; return { name: "Jalenl" }; } let obj = new Person(18); console.log(obj);
打印的是{name:'Jalenl'},也就是說(shuō)return之前的定義都被覆蓋了。這里return的是一個(gè)對(duì)象,那返回的是個(gè)基本類(lèi)型呢?
返回非對(duì)象
function Person(age) { this.age = age; return 1; } let obj = new Person(18); console.log(obj);
返回{age:21},這么說(shuō)return失效了,跟沒(méi)有return一樣的結(jié)果,那如果沒(méi)有this綁定內(nèi)部屬性,再返回基本數(shù)據(jù)類(lèi)型呢?
沒(méi)有屬性綁定+返回非對(duì)象
function Person(){ return 1 } new Person()
返回的是一個(gè)空對(duì)象{},意料之中。
綜上,只有構(gòu)造函數(shù)return返回的是一個(gè)對(duì)象類(lèi)型時(shí),才能改變初始結(jié)果。
構(gòu)造函數(shù)類(lèi)型不同
構(gòu)造函數(shù)為普通函數(shù)
ECMA-262 3rd. Edition Specification中的說(shuō)明了對(duì)象實(shí)例的創(chuàng)建過(guò)程:
13.2.2 [[Construct]]
When the [[Construct]] property for a Function object F is called, the following steps are taken:
- Create a new native ECMAScript object.
- Set the [[Class]] property of Result(1) to "Object".
- Get the value of the prototype property of F.
- If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3).
- If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1.
- Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values.
- If Type(Result(6)) is Object then return Result(6).
- Return Result(1).
總結(jié)下來(lái)就是:
- 在內(nèi)存中創(chuàng)建一個(gè)新對(duì)象。
- 這個(gè)新對(duì)象內(nèi)部的[[Prototype]]特性被賦值為構(gòu)造函數(shù)的 prototype 屬性。
- 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個(gè)新對(duì)象(即 this 指向新對(duì)象)。
- 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對(duì)象添加屬性)。
- 如果構(gòu)造函數(shù)返回對(duì)象,則返回該對(duì)象;否則,返回剛創(chuàng)建的新對(duì)象(空對(duì)象)。
第五步就已經(jīng)說(shuō)明了構(gòu)造函數(shù)不同導(dǎo)致new結(jié)果不同的原因。
以下摘自MDN的解釋?zhuān)?/p>
當(dāng)代碼 new Foo(…) 執(zhí)行時(shí),會(huì)發(fā)生以下事情:
- 一個(gè)繼承自 Foo.prototype 的新對(duì)象被創(chuàng)建。
- 使用指定的參數(shù)調(diào)用構(gòu)造函數(shù) Foo,并將 this 綁定到新創(chuàng)建的對(duì)象。new Foo 等同于 new Foo(),也就是沒(méi)有指定參數(shù)列表,F(xiàn)oo 不帶任何參數(shù)調(diào)用的情況。
- 由構(gòu)造函數(shù)返回的對(duì)象就是 new 表達(dá)式的結(jié)果。如果構(gòu)造函數(shù)沒(méi)有顯式返回一個(gè)對(duì)象,則使用步驟1創(chuàng)建的對(duì)象。(一般情況下,構(gòu)造函數(shù)不返回值,但是用戶可以選擇主動(dòng)返回對(duì)象,來(lái)覆蓋正常的對(duì)象創(chuàng)建步驟)
構(gòu)造函數(shù)為箭頭函數(shù)
普通函數(shù)創(chuàng)建時(shí),引擎會(huì)按照特定的規(guī)則為這個(gè)函數(shù)創(chuàng)建一個(gè)prototype屬性(指向原型對(duì)象)。默認(rèn)情況下,所有原型對(duì)象自動(dòng)獲得一個(gè)名為 constructor 的屬性,指回與之關(guān)聯(lián)的構(gòu)造函數(shù)。
function Person(){ this.age = 18; } Person.prototype /** { constructor: ƒ Foo() __proto__: Object } **/
創(chuàng)建箭頭函數(shù)時(shí),引擎不會(huì)為其創(chuàng)建prototype屬性,箭頭函數(shù)沒(méi)有constructor供new調(diào)用,因此使用new調(diào)用箭頭函數(shù)會(huì)報(bào)錯(cuò)!
const Person = ()=>{} new Person()//TypeError: Foo is not a constructor
手寫(xiě)new
綜上,熟悉了new的工作原理后,我們可以自己實(shí)現(xiàn)一個(gè)低配版的new,實(shí)現(xiàn)的關(guān)鍵是:
- 讓實(shí)例可以訪問(wèn)到私有屬性;
- 讓實(shí)例可以訪問(wèn)構(gòu)造函數(shù)原型(constructor.prototype)所在原型鏈上的屬性;
- 構(gòu)造函數(shù)返回的最后結(jié)果是引用數(shù)據(jù)類(lèi)型。
function _new(constructor, ...args) { // 構(gòu)造函數(shù)類(lèi)型合法判斷 if(typeof constructor !== 'function') { throw new Error('constructor must be a function'); } // 新建空對(duì)象實(shí)例 let obj = new Object(); // 將構(gòu)造函數(shù)的原型綁定到新創(chuàng)的對(duì)象實(shí)例上 obj.__proto__ = Object.create(constructor.prototype); // 調(diào)用構(gòu)造函數(shù)并判斷返回值 let res = constructor.apply(obj, args); let isObject = typeof res === 'object' && res !== null; let isFunction = typeof res === 'function'; // 如果有返回值且返回值是對(duì)象類(lèi)型,那么就將它作為返回值,否則就返回之前新建的對(duì)象 return isObject || isFunction ? res : obj; };
這個(gè)低配版new實(shí)現(xiàn)可以用來(lái)創(chuàng)建自定義類(lèi)的實(shí)例,但不支持內(nèi)置對(duì)象,畢竟new屬于操作符,底層實(shí)現(xiàn)更加復(fù)雜。
總結(jié)
到此這篇關(guān)于JS中new的原理與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)JS中new原理與實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析四種常見(jiàn)的Javascript聲明循環(huán)變量的書(shū)寫(xiě)方式
這篇文章主要介紹了四種常見(jiàn)的聲明循環(huán)變量的書(shū)寫(xiě)方式,對(duì)其進(jìn)行簡(jiǎn)單的分析和比較,需要的朋友可以參考下2015-10-10JavaScript中幾個(gè)重要的屬性(this、constructor、prototype)介紹
this表示當(dāng)前對(duì)象,如果在全局作用范圍內(nèi)使用this,則指代當(dāng)前頁(yè)面對(duì)象window,prototype本質(zhì)上還是一個(gè)JavaScript對(duì)象,constructor始終指向創(chuàng)建當(dāng)前對(duì)象的構(gòu)造函數(shù)2013-05-05在JavaScript中處理時(shí)間之getHours()方法的使用
這篇文章主要介紹了在JavaScript中處理時(shí)間之getHours()方法的使用,是JS入門(mén)學(xué)些中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06JavaScript 頁(yè)面坐標(biāo)相關(guān)知識(shí)整理
對(duì)于頁(yè)面的一些坐標(biāo)與位置分析,一般需要控制層的位置的朋友有幫助。需要的朋友可以參考下。2010-01-01JavaScript While 循環(huán)基礎(chǔ)教程
只要指定條件為 true,循環(huán)就可以一直執(zhí)行代碼,2007-04-04實(shí)現(xiàn)網(wǎng)頁(yè)頁(yè)面跳轉(zhuǎn)的幾種方法(meta標(biāo)簽、js實(shí)現(xiàn)、php實(shí)現(xiàn))
今天總結(jié)了幾種頁(yè)面跳轉(zhuǎn)的方法,分別是用meta標(biāo)簽實(shí)現(xiàn)、用javascript實(shí)現(xiàn)、用php實(shí)現(xiàn),下面就來(lái)一一分享一下吧。2014-05-05