必須讓你一看就能明白系列之———JavaScript 中 new 實例化對象的實現原理?

咱們在使用new操做符建立新實例的時候,其內部也就那麼四個步驟,網上不少文章也把這四步都講清楚了,而且代碼也貼出來了,可是仍是有朋友們看不懂,我就想着怎麼讓朋友們能一看就能明白。

先貼出一個網上的例子:數組

function New(func) { 
    var res = {}; 
    if (func.prototype !== null) { 
        res.__proto__ = func.prototype; 
    } 
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)); 
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { 
        return ret; 
    } 
    return res; 
} 
var obj = New(A, 1, 2); 
// equals to 
var obj = new A(1, 2);複製代碼

是否是看的不是很明白呢??彆着急,我帶你一點點深刻!!bash


這裏要看明白,首先須要認識apply,說到apply,還有一個call,他們就是同父異母的兄弟,還有人會說這個 Array.prototype.slice.call這個是什麼鬼?


那好吧,咱們就先作點鋪墊工做:
apply()、call()都是函數對象的一個方法,他們的做用都是改變函數的調用對象,也能夠說改變this指向,先來看一下apply、call的用法吧,其原理我也會在本篇文章後面實現。
  • apply方法:
  1. 語法:apply([thisObj[,argArray]])
  2. 定義:應用某一對象的一個方法,用另外一個對象替換當前對象。 說明:apply的第一個參數thisObj和call方法的同樣,第二個參數argArray爲一個傳參數組。thisObj若是未傳,那麼 Global 對象被用做 thisObj。
  • call方法:
  1. 語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
  2. 定義:調用一個對象的一個方法,以另外一個對象替換當前對象。 說明:call 方法能夠用來代替另外一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。若是沒有提供 thisObj 參數,那麼 Global 對象被用做 thisObj。 arg1 … argN爲被調用方法的傳參。
文字有時候便於理解,可是有時候不如代碼來的實在,其實最好的方式就是將文字和代碼結合起來一點點理解消化,也就能深入記住了。
來看代碼吧:

var name = 'globalName'; //定義一個全局name

var obj = {
	name: 'objName'
}

var foo = {
	name: 'fooName',
	getName: function() {
		return this.name;
	}
}

console.log(foo.getName())             //  fooName
console.log(foo.getName.apply(obj));   //  objName
console.log(foo.getName.apply())       //  globalName
console.log(foo.getName.apply(window)) //  globalName

console.log(foo.getName())             //  fooName
console.log(foo.getName.call(obj));    //  objName
console.log(foo.getName.call())        //  globalName
console.log(foo.getName.call(window))  //  globalName
複製代碼

foo.getName()  
 //這裏foo調用本身的方法,返回自身name屬性值fooName,若是你們對於this指向還不清楚,請自行補課複製代碼

foo.getName.apply(obj) 
//這裏經過使用apply方法切換getName函數執行的上下文環境,將this指向了obj,因此輸出了objName,有一種借殼生蛋的做用複製代碼

foo.getName.apply()
//這裏在調用apply並無傳入須要指向的參數,默認全局window對象複製代碼

foo.getName.apply(window)
//這裏顯示的傳入window對象,將this指向了window,輸出了globalName複製代碼

相信你們已經明白了apply的用法,call也是一樣的道理,這裏咱們只用到了apply和call方法的第一個參數,咱們再看看他們第一個參數後面的參數怎麼回事?app

經過apply和call實現數組追加:函數

var arr1 = [1,2,3,4];
Array.prototype.push.apply(arr1, [5,6,7]);  //調用數組原型上的push方法,至關因而arr1借用了push方法實現尾部追加元素,第二個元素是以數組形式
console.log(arr1)  //[1, 2, 3, 4, 5, 6, 7]複製代碼

var arr1 = [1,2,3,4];
Array.prototype.push.call(arr1,5,6,7); //而call方法是已參數列表形式傳入追加的元素
console.log(arr1)  //[1, 2, 3, 4, 5, 6, 7]複製代碼

以上就是apply和call的使用方法和區別了,要想更好的理解調用new實例化對象具體作了什麼,咱們還須要瞭解一下JavaScript建立對象的幾種方式,經過比對咱們來發現其中的原理。
  • 工廠模式

function createPerson(name,age,job) {    
    var obj = new Object(); //建立一個對象    
    obj.name = name; //給對象添加屬性 和方法    
    obj.age = age;    
    obj.job = job;    
    obj.sayName = function() {        
        console.log(this.name)    
    }    
    return obj; //返回這個對象
}

var person1 = createPerson("lili", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

console.log(person1 instanceof createPerson)  //false
console.log(person2 instanceof createPerson)  //false複製代碼

優勢:解決了建立多個對象的時候的重複代碼問題。ui

缺點:不能解決對象識別問題,也就是不知道一個對象的類型,上面的instanceof 說明問題。this


ECMAScript中的構造函數可用來建立特定類型的對象。像Object和Array這樣的原生構造函數,在運行時會自動出如今運行環境中,咱們能夠建立自定義的構造函數,從而定義自定義對象類型的屬性和方法。下面就是使用構造函數模式將前面的例子重寫以下:spa

  • 構造函數模式

function Person(name,age,job) {    
    this.name = name;    
    this.age = age;    
    this.job = job;    
    this.sayName = function () {        
        console.log(this.name);    
    }
}
var person1 = new Person("lili", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1 instanceof Person); // true 這裏能夠判斷其屬於Person類型的實例對象了
console.log(person2 instanceof Person); // true複製代碼

咱們能夠注意到,Person()中的代碼除了與createPerson()中相同的部分外,還存在如下不一樣之處:prototype

  1. 沒有顯示的建立對象;
  2. 直接將屬性和方法賦給了this對象;
  3. 沒有return語句。

注意: 經過new實例化的對象,咱們就能夠明確知道了其類型,code

要建立Person的新實例,必須使用new操做符,那麼new的過程當中都經歷了那幾個步驟呢:對象

  1. 建立一個新對象;
  2. 將構造函數的做用域賦值給新對象(所以this就指向了這個新對象);
  3. 執行構造函數中的代碼(爲這個新對象添加屬性);
  4. 返回新對象;


咱們嘗試本身用代碼來實現一下new過程吧!!!

function New(Person,name,age,job) { //Person是上面那個構造函數   
    //1.建立一個對象,    
    var obj = {};    
    //2.將構造函數的做用域賦給新對象,所以this就指向了這個新對象,這裏咱們將obj的__proto__指向了Person的prototype,由於通用new出來的實例的__proto__屬性都指向構造函數的原型(prototype) 
    obj.__proto__ = Person.prototype;
    //執行構造函數Person中的代碼,這裏經過apply將做用域切換爲當前obj,這裏的arguments是New方法傳入的參數,經過slice去掉第一個參數,傳入剩下的參數,    
    var ret = Person.apply(obj,Array.prototype.slice.call(arguments,1));
    // 若是ret是對象或者是函數,就返回,若是不是就返回obj;
    if((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {        
        return ret;    
    }
    return obj;
}
var o = New(Person,'jiji',1,'mother');
console.log(o)複製代碼

不知道你們看明白了沒有,歡迎留言交流~~~~

碼字不易,若是對你有幫助的話,不忙點個贊再走~~~~~

相關文章
相關標籤/搜索