JavaScript進階之模擬new Object過程

EdwardXuan發表於2019-02-21

原文:zhehuaxuan.github.io/2019/02/21/…
作者:zhehuaxuan

寫在前面的話

前端的入門相對簡單,相對於其他方向天花板可能會相對較低。但是在市場上一個優秀的前端依舊是很搶手的。能夠站在金字塔上的人往往寥寥無幾。

目前前端也已經一年半了,在公司的知識棧相對落後,就業形勢不容樂觀,所以有必要自己琢磨,往中高階前端進階。後續我將推出《JavaScript進階系列》,一方面是一個監督自己學習的一個過程,另一方面也會給看到的童鞋一些啟發。

JavaScript新建物件的過程

在ES5中定義一個函式來建立物件,如下:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    return name;
}
var person = new Person("xuan");
console.log(person.name);//輸出:xuan
console.log(person.getName());//輸出:xuan
複製程式碼

我們看到當我們新建一個物件,我們就可以訪問構造器中的指向this的屬性,還可以訪問原型中的屬性。我們不妨把JavaScript呼叫new的過程主要由下面四步組成:

  1. 新生成一個空物件
  2. 將空物件連結到原型中
  3. 繫結this
  4. 返回新物件

下面跟著我按照這個思路來建立物件:

function create(){
    //Todo
}
person = create(Person,"xuan");//create(ObjectName,...arguments)
複製程式碼

我們使用如上所示的函式來模擬new關鍵字。

首先第一步新建一個物件:

function create(){
    var obj = new Object();
    return obj;
}
person = create(Person,"xuan");
複製程式碼

現在已經建立並返回一個物件,當然現在列印出來肯定是一個普通的物件,畢竟流程還沒有走完,我們接著往下看。

第二步連結到原型中:

function create(){
    var obj = new Object();
    var constructor = [].shift.call(arguments);
    console.log(constructor);
    console.log(arguments);
    obj.__proto__ = constructor.prototype;
    return obj;
}

person = create(Person,"xuan");
複製程式碼

image-20190221235358202

現在把建構函式和引數都列印出來了。沒問題!

第三步繫結this,如下:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  constructor.apply(obj, arguments);
  console.log(obj);  
  return obj;
}
person = create(Person,"xuan");
複製程式碼

JavaScript進階之模擬new Object過程

列印結果實現new物件的效果。

現在改一下建構函式程式碼:

function Person(name){
    this.name = name;
    return {
        name:"abc"
    }
}
var person = new Person("xuan");
console.log(person);
console.log(Object.prototype.toString.call(person));
複製程式碼

效果如下:

JavaScript進階之模擬new Object過程
我們執行一下我們構建的函式效果如下:
JavaScript進階之模擬new Object過程
發現不一致,所以我們要處理第三步繫結this中apply函式的返回值:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  //constructor.apply(obj, arguments);
  let res = constructor.apply(obj, arguments);
  if(res){
     return res;
  }else{
     return obj;
  }
}
person = create(Person,"xuan");
複製程式碼

效果如下:

JavaScript進階之模擬new Object過程
完美!

現在我們思考一下這裡的res返回值有三種情況:undefined,基本型別,物件。

如果res是undefined時,返回obj;

如果res是基本型別我們也返回obj;

如果res是物件我們返回res物件;

綜合一下:

如果返回的res物件是Object型別那麼返回res,否則返回obj。當然其他的判斷條件也是可以的。最後程式碼優化如下:

function create() {
  let obj = new Object();
  let constructor = [].shift.call(arguments)
  obj.__proto__ = constructor.prototype
  //constructor.apply(obj, arguments);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");
複製程式碼

幾個問題

現在的程式碼已經完美了麼?我們先來提幾個問題。

  1. new Object()建立的物件純淨麼?
  2. 為啥使用[].shift.call()來進行引數分割?arguments是一個陣列麼?

new Object()建立的物件純淨麼?

首先什麼是純淨?我們定義一個物件的__proto__屬性為空的物件是一個純淨的物件。

在第二步的時候中已經改變的obj的原型鏈,所以無論它前面的原型鏈是咋樣的都無所謂,但是為了保證物件的純淨性,我們有必要引出Object.create(),該方法建立一個新物件,使用現有的物件來提供新建立的物件的__proto__。我們來看一下:

var person1 = Object.create({});
複製程式碼

列印如下:

JavaScript進階之模擬new Object過程

我們看到person1的__proto__指向了{}物件,所以我們在上述程式碼中直接修改如下:

function create() {
  let constructor = [].shift.call(arguments);
  let obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");
複製程式碼

為啥使用[].shift.call()來進行引數分割?arguments是一個陣列麼?

首先我們知道arguments是函式傳入的引數,那麼這個引數是陣列麼?我們列印一下便知:

console.log(arguments);
console.log(Object.prototype.toString.call(arguments));
console.log(arguments instanceof Array);
複製程式碼

結果如下

JavaScript進階之模擬new Object過程

不是陣列。我們展開發現他跟陣列很像,查一下資料發現這個物件是類陣列。裡面沒有shift函式,直接呼叫shift會報錯。我們使用使用Array.from(arguments)將arguments轉成陣列,然後在呼叫shift函式也是一種思路。但是在這裡我們使用apply最適合。所以下述程式碼是模擬new Object()的最優程式碼:

function create() {
  let constructor = [].shift.call(arguments);
  let obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, arguments);
  return res instanceof Object?res:obj;
}
person = create(Person,"xuan");
複製程式碼

還有更優的實現方法,請大佬們不吝拍磚!

相關文章