new操作符到底干了什么?
前幾天無意間發現了剛開始學JavaScript時在知乎寫的一些回答,有一個就是講new操作符到底干了什么。從現在的視角看我當時的回答雖然是正確的,但是在對原理的剖析和細節的理解上還相去甚遠。所以借此機會,就想重新梳理一下這一年多來對new操作符理解的變化與成長。
模擬new操作符
第一次去了解new操作符,是在看《JS高級程序設計(第三版)》這本書時,P145是這樣寫到的。
要創建Person的新實例,必須使用new操作符。以這種方式調用構造函數實際上會經歷以下4個步驟:
盡管書中是這樣描述,但是并沒有給出實踐的代碼,所以我就按照這個步驟自己去實踐模擬一個new操作符。代碼如下:
var mockNew = function (constructor) {var o = {} // 創建一個新對象constructor.apply(o, Array.prototype.slice.call(arguments, 1)) // 賦作用域 執行代碼return o // 返回新對象 } 復制代碼然后我們用這個模擬new操作符的函數來創造一個對象試試。
var Person = function (name, age) {this.name = namethis.age = age }Person.prototype.sayName = function () {console.log(this.name) }var person1 = mockNew(Person, 'MeloGuo', 22)console.log(person1.name) // 'MeloGuo' console.log(person1.age) // 22 person1.sayName() // Uncaught TypeError: person1.sayName is not a function 復制代碼看起來我的模擬函數雖然能訪問實例中的屬性,但是卻不能訪問sayName方法,而且當我使用instanceof操作符檢測時卻得到了這樣的結果。
console.log(person1 instanceof Person) // false console.log(person1 instanceof Object) // true 復制代碼改進的模擬函數
可見person1并不是Person的實例。當時的我還不知道問題出在哪里,直到學習到原型鏈時我才直到mockNew函數缺少了一個步驟,即綁定構造函數原型。所以person1實例是無法訪問到Person原型中的sayName方法,同時instanceof操作符的結果也為false。因為instanceof操作符是用來檢測一個對象在其原型鏈中是否存在一個構造函數的prototype屬性的,而person1的原型鏈中并不存在Person.prototype,所以返回值為false。因此,我們改造mockNew函數如下:
var mockNew = function (constructor) {var o = {}o.__proto__ = constructor.prototype // 綁定構造函數原型,但是生產代碼中千萬別用.__proto__constructor.apply(o, Array.prototype.slice.call(arguments, 1))return o } 復制代碼這時我們再創建實例,然后使用instanceof操作符檢測一下,同時調用下sayName方法。
var person1 = mockNew(Person, 'MeloGuo', 22)console.log(person1 instanceof Person) // true console.log(person1 instanceof Object) // true person1.sayName() // 'MeloGuo' 復制代碼這時看似mockNew函數已經完全模擬了new操作符了,但是當我們嘗試下面這種情況時,又出現了問題。
function Person (name) {this.name = namereturn { age: 22 } }var person1 = new Person('MeloGuo') var person2 = mockNew(Person, 'MeloGuo')console.log(person1) // {age: 22} console.log(person2) // Person {name: "MeloGuo"} console.log(person1 instanceof Person) // false console.log(person2 instanceof Person) // true 復制代碼什么!!!用new操作符調用的Person構造函數并沒有按照預期返回帶有name屬性并且在Person.prototype上的對象,而是返回了我們手動return的帶有age屬性的對象,但是我們的mockNew函數是正常返回了。所以我們的mockNew函數中肯定又丟失了一些細節,為了弄清楚,只好硬著頭皮去讀ECMA-262規范了。看到規范中的steps后才恍然大悟了new操作符的整個執行流程。
完善模擬函數
簡單來說我們在返回對象前缺失了判斷返回值類型的步驟。
- 如果構造函數的返回值是值類型,那么就丟棄掉,依然返回構造函數創建的實例。
- 如果構造函數的返回值是引用類型,那么就返回這個引用類型,丟棄構造函數創建的實例。
注:如果沒有顯示return,那么相當于隱式返回了undefined,則丟棄它。
吸取了規范中的內容,并且加入ES6語法后,我們新的mockNew函數如下:
function mockNew (constructor, ...args) {const isPrimitive = result => {// 如果result為值類型則返回true// 如果result為引用類型則返回false }const o = Object.create(constructor.prototype)const result = constructor.apply(o, args)return isPrimitive(result) ? o : result } 復制代碼這時的mockNew函數可以說是較好的模擬了new操作符的功能。
總結
其實new操作符就是一個語法糖。在傳統的面向類的語言中,“構造函數”是類中的一些特殊方法,使用new初始化類時會調用類中的構造函數。而JavaScript中的new其實是用來告訴函數,我要以“構造”的方式調用你了,從而向mockNew函數中的流程一樣,得到我們的實例。所以本質上來說JS是沒有所謂構造函數的,有的應該是構造調用。這樣的稱呼能讓我們更清楚認識JS中的new操作符。
轉載于:https://juejin.im/post/5b4af862e51d4519984118d1
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的new操作符到底干了什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于PHP7的提供数据管理工具框架Mel
- 下一篇: mpvue生命周期初探