我們在使用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);複製程式碼
是不是看的不是很明白呢??彆著急,我帶你一點點深入!!
這裡要看明白,首先需要認識apply,說到apply,還有一個call,他們就是同父異母的兄弟,還有人會說這個 Array.prototype.slice.call這個是什麼鬼?
那好吧,我們就先做點鋪墊工作:
apply()、call()都是函式物件的一個方法,他們的作用都是改變函式的呼叫物件,也可以說改變this指向,先來看一下apply、call的用法吧,其原理我也會在本篇文章後面實現。- apply方法:
- 語法:apply([thisObj[,argArray]])
- 定義:應用某一物件的一個方法,用另一個物件替換當前物件。 說明:apply的第一個引數thisObj和call方法的一樣,第二個引數argArray為一個傳引數組。thisObj如果未傳,那麼 Global 物件被用作 thisObj。
- call方法:
- 語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
- 定義:呼叫一個物件的一個方法,以另一個物件替換當前物件。 說明: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方法的第一個引數,我們再看看他們第一個引數後面的引數怎麼回事?
通過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複製程式碼
優點:解決了建立多個物件的時候的重複程式碼問題。
缺點:不能解決物件識別問題,也就是不知道一個物件的型別,上面的instanceof 說明問題。
ECMAScript中的建構函式可用來建立特定型別的物件。像Object和Array這樣的原生建構函式,在執行時會自動出現在執行環境中,我們可以建立自定義的建構函式,從而定義自定義物件型別的屬性和方法。下面就是使用建構函式模式將前面的例子重寫如下:
- 建構函式模式
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()中相同的部分外,還存在以下不同之處:
- 沒有顯示的建立物件;
- 直接將屬性和方法賦給了this物件;
- 沒有return語句。
注意: 通過new例項化的物件,我們就可以明確知道了其型別,
要建立Person的新例項,必須使用new操作符,那麼new的過程中都經歷了那幾個步驟呢:
- 建立一個新物件;
- 將建構函式的作用域賦值給新物件(因此this就指向了這個新物件);
- 執行建構函式中的程式碼(為這個新物件新增屬性);
- 返回新物件;
我們嘗試自己用程式碼來實現一下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)複製程式碼
不知道大家看明白了沒有,歡迎留言交流~~~~
碼字不易,如果對你有幫助的話,不忙點個贊再走~~~~~