js new一個對象的過程,實現一個簡單的new方法


對於大部分前端開發者而言,new一個構造函數或類得到對應實例,是非常普遍的操作了。下面的例子中分別通過構造函數與class類實現了一個簡單的創建實例的過程。

復制代碼
// ES5構造函數
let Parent = function (name, age) {
    this.name = name;
    this.age = age;
};
Parent.prototype.sayName = function () {
    console.log(this.name);
};
const child = new Parent('聽風是風', 26);
child.sayName() //'聽風是風'


//ES6 class類
class Parent {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayName() {
        console.log(this.name);
    }
};
const child = new Parent('echo', 26);
child.sayName() //echo
復制代碼

但new不應該像一個黑盒,我們除了知道結果,更應該明白過程究竟如何。那么那么這篇文章主要圍繞兩點展開,第一,js中new一個對象時到底發生了什么第二,知道了原理后我們通過js來實現一個簡單的new方法

一、new操作中發生了什么?

比較直觀的感覺,當我們new一個構造函數,得到的實例繼承了構造器的構造屬性(this.name這些)以及原型上的屬性

在《JavaScript模式》這本書中,new的過程說的比較直白,當我們new一個構造器,主要有三步:

• 創建一個空對象,將它的引用賦給 this,繼承函數的原型。
• 通過 this 將屬性和方法添加至這個對象
• 最后返回 this 指向的新對象,也就是實例(如果沒有手動返回其他的對象)

我們改寫上面的例子,大概就是這樣:

復制代碼
// ES5構造函數
let Parent = function (name, age) {
    //1.創建一個新對象,賦予this,這一步是隱性的,
    // let this = {};
    //2.給this指向的對象賦予構造屬性
    this.name = name;
    this.age = age;
    //3.如果沒有手動返回對象,則默認返回this指向的這個對象,也是隱性的
    // return this;
};
const child = new Parent();
復制代碼

這應該不難理解,你應該在工作中看過類似下述代碼中的操作,將this賦予一個新的變量(例如that),最后返回這個變量:

復制代碼
// ES5構造函數
let Parent = function (name, age) {
    let that = this;
    that.name = name;
    that.age = age;
    return that;
};
const child = new Parent('聽風是風', '26');
復制代碼

為什么要這么寫呢?我在前面說this的創建與返回是隱性的,但在工作中為了讓構造過程更易可見與更易維護,所以才有了上述使用that代替this,同時手動返回that的做法;這也驗證了隱性的這兩步確實是存在的。

但上述這個解釋我覺得不夠完美,它只描述了構造器屬性是如何塞給實例,沒說原型上的屬性是如何給實例繼承的。

我在winter大神的重學前端專欄中,看到了比較符合我心意的,同時也是符合原理的描述:

• 以構造器的prototype屬性為原型,創建新對象;
• 將this(也就是上一句中的新對象)和調用參數傳給構造器,執行;
• 如果構造器沒有手動返回對象,則返回第一步創建的新對象,如果有,則舍棄掉第一步創建的新對象,返回手動return的對象。

到這里不管怎么說,你都應該大概知道了new過程中會新建對象,此對象會繼承構造器的原型與原型上的屬性,最后它會被作為實例返回這樣一個過程。知道了原理,我們來手動實現一個簡單的new方法。

二、實現一個簡單的new方法

復制代碼
// 構造器函數
let Parent = function (name, age) {
    this.name = name;
    this.age = age;
};
Parent.prototype.sayName = function () {
    console.log(this.name);
};
//自己定義的new方法
let newMethod = function (Parent, ...rest) {
    // 1.以構造器的prototype屬性為原型,創建新對象;
    let child = Object.create(Parent.prototype);
    // 2.將this和調用參數傳給構造器執行
    let result = Parent.apply(child, rest);
    // 3.如果構造器沒有手動返回對象,則返回第一步的對象
    return typeof result  === 'object' ? result : child;
};
//創建實例,將構造函數Parent與形參作為參數傳入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';

//最后檢驗,與使用new的效果相同
child instanceof Parent//true
child.hasOwnProperty('name')//true
child.hasOwnProperty('age')//true
child.hasOwnProperty('sayName')//false
復制代碼

那么到這里就介紹完畢了。

new一個構造函數默認返回什么?調用構造函數不使用new能得到實例嗎?如果你對這些有興趣,可以閱讀博主這篇博客:

精讀JavaScript模式(三),new一個構造函數究竟發生了什么?

實例為什么能使用構造器prototype上的方法?繼承之間的關系又是怎么樣的?如果你對這些有興趣,可以閱讀博主這篇博客:

精讀JavaScript模式(八),JS類式繼承

ES6新增的class類怎么用?與傳統構造函數寫法有哪些區別?如何快速上手class類?那你可以閱讀博主這篇博客:

es6入門5--class類的基本用法

最后模擬實現new方法中,...rest是個什么參數?如果你對這個存疑,那怕是得了解下ES6中的取代arguments的rest參數,歡迎閱讀這篇:

es6入門3--箭頭函數與形參等屬性的拓展


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM