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