1. 程式人生 > 實用技巧 >模擬實現js的new

模擬實現js的new

目錄

new是什麼

一句話介紹newnew運算子建立一個使用者自定義的物件型別的例項,或者具有建構函式的內建物件型別之一。看下下面的程式碼來了解new操作符都做了什麼事情

// Class (constructor)
function Person(name,age){
    this.name = name
    this.age = age
    this.habit = 'watch tv'
}

// 每個函式都有prototype物件屬性
// 在類的原型上掛載屬性和方法,掛載載原型上,每個例項都可以呼叫,並且不會每個例項都掛載相同的屬性和方法
Person.prototype.strLength = 60
Person.prototype.sayName = function(){
    console.log('I am '+ this.name)
}

// 例項化物件
const person = new Person("yato", 50)
person.sayName();
console.log(person)

進一步理解new

從上面這個例子中,我們可以看到,例項person可以

  • 訪問到Person建構函式裡的屬性
  • 訪問到Person.prototype中的屬性

接下來模擬實現一個類似new的newFake,使用方式如下

function Person(arguments){
    // ...
}

// 使用new
let person = new Person(arguments)

// 使用newFake
let person = newFake(Person,arguments)

初步實現

function newFake(){
    let obj = Object.create({})

    let Constructor = [].shift.call(arguments)

    if(typeof Constructor !== 'function'){
        throw 'newOperator function the first param must be a function';
    }
        
    // 將新建物件的[[prototype]]屬性指向到建構函式的prototype屬性
    obj.__proto__ = Constructor.prototype

    // 修改this指向到obj
    Constructor.apply(obj, arguments)

    return obj
}

function Person(name,age){
    this.name = name
    this.age = age
    this.habit = 'watch tv'
}

// 每個函式都有prototype物件屬性
// 在類的原型上掛載屬性和方法,掛載載原型上,每個例項都可以呼叫,並且不會每個例項都掛載相同的屬性和方法
Person.prototype.strLength = 60
Person.prototype.sayName = function(){
    console.log('I am '+ this.name)
}

// 例項化物件
const person = newFake(Person,"yato", 50)
person.sayName();  // I am yato
console.log(person)  // Person { name: 'yato', age: 50, habit: 'watch tv' }

返回值處理

如果建構函式有返回值的情況

function Person(name,age){
    this.strLength = 60
    this.age = age
    
    return {
 	name: name,
        habit: 'game'
    }
}

let person = new Person('yato', 18)

console.log(person.name) // yato
console.log(person.habit) // game
console.log(person.strength) // undefined
console.log(person.age) // undefined

在這個例子中,建構函式返回了一個物件,在例項person中只能訪問返回物件中的屬性,而且還要注意一點,這裡我們是返回一個物件,假設我們只返回一個基本型別值呢,看下面的例子

function Person(name,age){
    this.strLength = 60
    this.age = age
    
    return 'good job'
}

let person = new Person('yato', 18)

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

結果和正常new例項,無返回值的時候表現是一樣的!可以得出結論:new操作最後一步,需要判斷一返回值是否是一個物件,如果是一個物件就返回這個物件,如果不是物件就返回我們new內部的例項物件

第二版的new實現

function newFake(){
    let obj = Object.create({})

    let Constructor = [].shift.call(arguments)

    if(typeof Constructor !== 'function'){
        throw 'newOperator function the first param must be a function';
    }
    
    // ES6 new.target 是指向建構函式
    newFake.target = Constructor;
        
    // 將新建物件的[[prototype]]屬性指向到建構函式的prototype屬性
    obj.__proto__ = Constructor.prototype

    // 修改this指向到obj
    let ret = Constructor.apply(obj, arguments)
    
    // 判斷返回值是否為物件 Object(包含Functoin, Array, Date, RegExg, Error)都會直接返回這些值。
    // 這些型別中合併起來只有Object和Function兩種型別 typeof null 也是'object'所以要不等於null,排除	 // null
    let isObject = typeof ret === 'object' && ret !== null;
    let isFunction = typeof ret === 'function';

    if(isObject || isFunction){
        return ret;
    }
    
    return obj
}

總結一下new做了什麼

  • 建立了一個全新的物件
  • 這個物件會被執行例項[[prototype]]屬性Classprototype物件屬性的連結(原型鏈)
  • 生成的新物件會成為建構函式的呼叫的this(修改this指向)
  • 通過new建立的每個例項物件最終將被[[prototype]]連結到建構函式的prototype物件上
  • 如果函式沒有返回物件型別(包含Function, Array,Date,RegExg,Error),那麼new表示式中的函式會自動返回這個新的物件