JavaScript深刻系列第十二篇,經過new的模擬實現,帶你們揭開使用new得到構造函數實例的真相git
一句話介紹 new:github
new 運算符建立一個用戶定義的對象類型的實例或具備構造函數的內置對象類型之一數組
也許有點難懂,咱們在模擬 new 以前,先看看 new 實現了哪些功能。瀏覽器
舉個例子:閉包
// Otaku 御宅族,簡稱宅 function Otaku (name, age) { this.name = name; this.age = age; this.habit = 'Games'; } // 由於缺少鍛鍊的緣故,身體強度讓人擔心 Otaku.prototype.strength = 60; Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); } var person = new Otaku('Kevin', '18'); console.log(person.name) // Kevin console.log(person.habit) // Games console.log(person.strength) // 60 person.sayYourName(); // I am Kevin
從這個例子中,咱們能夠看到,實例 person 能夠:app
訪問到 Otaku 構造函數裏的屬性函數
訪問到 Otaku.prototype 中的屬性測試
接下來,咱們能夠嘗試着模擬一下了。this
由於 new 是關鍵字,因此沒法像 bind 函數同樣直接覆蓋,因此咱們寫一個函數,命名爲 objectFactory,來模擬 new 的效果。用的時候是這樣的:prototype
function Otaku () { …… } // 使用 new var person = new Otaku(……); // 使用 objectFactory var person = objectFactory(Otaku, ……)
分析:
由於 new 的結果是一個新對象,因此在模擬實現的時候,咱們也要創建一個新對象,假設這個對象叫 obj,由於 obj 會具備 Otaku 構造函數裏的屬性,想一想經典繼承的例子,咱們能夠使用 Otaku.apply(obj, arguments)來給 obj 添加新的屬性。
在 JavaScript 深刻系列第一篇中,咱們便講了原型與原型鏈,咱們知道實例的 __proto__ 屬性會指向構造函數的 prototype,也正是由於創建起這樣的關係,實例能夠訪問原型上的屬性。
如今,咱們能夠嘗試着寫初版了:
// 初版代碼 function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj; };
在這一版中,咱們:
用new Object() 的方式新建了一個對象 obj
取出第一個參數,就是咱們要傳入的構造函數。此外由於 shift 會修改原數組,因此 arguments 會被去除第一個參數
將 obj 的原型指向構造函數,這樣 obj 就能夠訪問到構造函數原型中的屬性
使用 apply,改變構造函數 this 的指向到新建的對象,這樣 obj 就能夠訪問到構造函數中的屬性
返回 obj
更多關於:
原型與原型鏈,能夠看《JavaScript深刻之從原型到原型鏈》
apply,能夠看《JavaScript深刻之call和apply的模擬實現》
經典繼承,能夠看《JavaScript深刻之繼承》
複製如下的代碼,到瀏覽器中,咱們能夠作一下測試:
function Otaku (name, age) { this.name = name; this.age = age; this.habit = 'Games'; } Otaku.prototype.strength = 60; Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); } function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj; }; var person = objectFactory(Otaku, 'Kevin', '18') console.log(person.name) // Kevin console.log(person.habit) // Games console.log(person.strength) // 60 person.sayYourName(); // I am Kevin
[]~( ̄▽ ̄)~**
接下來咱們再來看一種狀況,假如構造函數有返回值,舉個例子:
function Otaku (name, age) { this.strength = 60; this.age = age; return { name: name, habit: 'Games' } } var person = new Otaku('Kevin', '18'); console.log(person.name) // Kevin console.log(person.habit) // Games console.log(person.strength) // undefined console.log(person.age) // undefined
在這個例子中,構造函數返回了一個對象,在實例 person 中只能訪問返回的對象中的屬性。
並且還要注意一點,在這裏咱們是返回了一個對象,假如咱們只是返回一個基本類型的值呢?
再舉個例子:
function Otaku (name, age) { this.strength = 60; this.age = age; return 'handsome boy'; } var person = new Otaku('Kevin', '18'); console.log(person.name) // undefined console.log(person.habit) // undefined console.log(person.strength) // 60 console.log(person.age) // 18
結果徹底顛倒過來,此次儘管有返回值,可是至關於沒有返回值進行處理。
因此咱們還須要判斷返回的值是否是一個對象,若是是一個對象,咱們就返回這個對象,若是沒有,咱們該返回什麼就返回什麼。
再來看第二版的代碼,也是最後一版的代碼:
// 第二版的代碼 function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; var ret = Constructor.apply(obj, arguments); return typeof ret === 'object' ? ret : obj; };
《JavaScript深刻之call和apply的模擬實現》
JavaScript深刻系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript深刻系列預計寫十五篇左右,旨在幫你們捋順JavaScript底層知識,重點講解如原型、做用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。