JavaScript進階之模擬new Object過程

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

寫在前面的話

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

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

JavaScript新建對象的過程

在ES5中定義一個函數來建立對象,以下:git

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的過程主要由下面四步組成:github

  1. 新生成一個空對象
  2. 將空對象連接到原型中
  3. 綁定this
  4. 返回新對象

下面跟着我按照這個思路來建立對象:數組

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

咱們使用如上所示的函數來模擬new關鍵字。markdown

首先第一步新建一個對象:app

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

如今已經建立並返回一個對象,固然如今打印出來確定是一個普通的對象,畢竟流程尚未走完,咱們接着往下看。函數

第二步連接到原型中:oop

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");
複製代碼

打印結果實現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));
複製代碼

效果以下:

咱們執行一下咱們構建的函數效果以下:
發現不一致,因此咱們要處理第三步綁定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");
複製代碼

效果以下:

完美!

如今咱們思考一下這裏的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({});
複製代碼

打印以下:

咱們看到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);
複製代碼

結果以下

不是數組。咱們展開發現他跟數組很像,查一下資料發現這個對象是類數組。裏面沒有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");
複製代碼

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

相關文章
相關標籤/搜索