手寫:javascript中的關鍵字new

簡單介紹一下new

new再熟悉不過了,new後面跟著構造函數,可以創建對象,這個對象的原型指向構造函數的原型對象,說起來可能有點繞,直接看程式碼吧

function Person(name, age){
    this.name = name;
    this.age = age;
}

let person1 = new Person("張三", 22);

console.log(person1.__proto__ === Person.prototype); // true
console.log(person1 instanceof Person); // true

而new後面的構造函數,也可以這樣調用

let person2 = new Person;

其實這樣寫就等同於,不傳任何參數調用構造函數

let person2 = new Person();

列印person2可以看到

Person { name: undefined, age: undefined }

一般情況下,構造函數是不定義返回值的,那我們試一試,如果構造函數定義返回值,會怎麼樣,首先,返回一個字元串

function Person(name, age){
    this.name = name;
    this.age = age;

    return "person"
}

let person1 = new Person("張三", 22);
let person2 = new Person;

console.log(person1); // Person { name: '張三', age: 22 }
console.log(person2); // Person { name: undefined, age: undefined }
console.log(person1.__proto__ === Person.prototype); // true
console.log(person1 instanceof Person) // true

看來構造函數返回字元串沒有影響,那返回一個對象呢

function Person(name, age){
    this.name = name;
    this.age = age;

    return {
        id : "person"
    }
}

let person1 = new Person("張三", 22);
let person2 = new Person;

console.log(person1); // { id: 'person' }
console.log(person2); // { id: 'person' }
console.log(person1.__proto__ === Person.prototype); // false
console.log(person1 instanceof Person) // false

可以看到,如果返回一個對象,那麼通過new關鍵字創建的對象則為構造返回的值,且也不繼承於構造的原型對象

手寫

了解的差不多了,開始手寫
首先定義一個函數名為myNew,參數接收一個function,也就是構造函數,

function myNew(Constructor){
    if(typeof Constructor !== "function"){
        throw "參數Constructor必須是方法(function)"
    }
}

ok,緊接著需要創建一個新的對象,並且這個對象的原型指向的是構造函數的原型對象
所以

function myNew(Constructor){
    if(typeof Constructor !== "function"){
        throw "參數Constructor必須是方法(function)"
    }
    let obj = {};
    obj.__proto__ = Constructor.prototype;
}

接著,因為執行構造函數的是,需要傳參數,並且在構造函數內部會為this賦值,所以我們需要執行構造函數,並且把構造函數內的上下文指向到obj

function myNew(Constructor){
    if(typeof Constructor !== "function"){
        throw "參數Constructor必須是方法(function)"
    }
    let obj = {};
    obj.__proto__ = Constructor.prototype;
    // 獲取參數
    let params = Array.from(arguments);
    // 把參數第一位,也就是構造函數移除
    params.shift();
    // 執行構造函數,並且吧上下文只設置為obj
    Constructor.apply(obj, params);

    return obj
}

但是我們需要前面提到了,有時候構造函數是可能有返回值的,所以最終程式碼為

最終源碼

function myNew(Constructor){
    if(typeof Constructor !== "function"){
        throw "參數Constructor必須是方法(function)"
    }
    let obj = {};
    obj.__proto__ = Constructor.prototype;
    // 獲取參數
    let params = Array.from(arguments);
    // 把參數第一位,也就是構造函數移除
    params.shift();
    // 執行構造函數,並且吧上下文只設置為obj
    let result = Constructor.apply(obj, params);

    // 判斷構造函數執行返回是不是object,是的話返回這個對象
    return typeof result === "object" ? result : obj
}

我們以之前的例子來驗證一下

function Person(name, age){
    this.name = name;
    this.age = age;

    return "person"
}

let person1 = myNew(Person, "張三", 22);
let person2 = myNew(Person);

console.log(person1); // Person { name: '張三', age: 22 }
console.log(person2); // Person { name: undefined, age: undefined }
console.log(person1.__proto__ === Person.prototype); // true
console.log(person1 instanceof Person) // true

構造有返回值的時候

function Person(name, age){
    this.name = name;
    this.age = age;

    return {
        id : "person"
    }
}

let person1 = myNew(Person, "張三", 22);
let person2 = myNew(Person);

console.log(person1); // { id: 'person' }
console.log(person2); // { id: 'person' }
console.log(person1.__proto__ === Person.prototype); // false
console.log(person1 instanceof Person) // false

與前面完全一致