定義
new 運算子建立一個使用者定義的物件型別的例項或具有建構函式的內建物件的例項。 ——(來自於MDN)
舉個栗子
function Car(color) {
this.color = color;
}
Car.prototype.start = function() {
console.log(this.color + " car start");
}
var car = new Car("black");
car.color; // 訪問建構函式裡的屬性
// black
car.start(); // 訪問原型裡的屬性
// black car start
複製程式碼
可以看出 new
建立的例項有以下 2 個特性
- 1、訪問到建構函式裡的屬性
- 2、訪問到原型裡的屬性
注意點
ES6新增 symbol
型別,不可以使用 new Symbol()
,因為 symbol
是基本資料型別,每個從Symbol()
返回的 symbol
值都是唯一的。
Number("123"); // 123
String(123); // "123"
Boolean(123); // true
Symbol(123); // Symbol(123)
new Number("123"); // Number {123}
new String(123); // String {"123"}
new Boolean(true); // Boolean {true}
new Symbol(123); // Symbol is not a constructor
複製程式碼
模擬實現
當程式碼 new Foo(...)
執行時,會發生以下事情:
- 一個繼承自
Foo.prototype
的新物件被建立。 - 使用指定的引數呼叫建構函式
Foo
,並將this
繫結到新建立的物件。new Foo
等同於new Foo()
,也就是沒有指定引數列表,Foo
不帶任何引數呼叫的情況。 - 由建構函式返回的物件就是
new
表示式的結果。如果建構函式沒有顯式返回一個物件,則使用步驟1建立的物件。
模擬實現第一步
new
是關鍵詞,不可以直接覆蓋。這裡使用 create
來模擬實現 new
的效果。
new
返回一個新物件,通過 obj.__proto__ = Con.prototype
繼承建構函式的原型,同時通過 Con.apply(obj, arguments)
呼叫父建構函式實現繼承,獲取建構函式上的屬性(【進階3-3期】)。
實現程式碼如下
// 第一版
function create() {
// 建立一個空的物件
var obj = new Object(),
// 獲得建構函式,arguments中去除第一個引數
Con = [].shift.call(arguments);
// 連結到原型,obj 可以訪問到建構函式原型中的屬性
obj.__proto__ = Con.prototype;
// 繫結 this 實現繼承,obj 可以訪問到建構函式中的屬性
Con.apply(obj, arguments);
// 返回物件
return obj;
};
複製程式碼
測試一下
// 測試用例
function Car(color) {
this.color = color;
}
Car.prototype.start = function() {
console.log(this.color + " car start");
}
var car = create(Car, "black");
car.color;
// black
car.start();
// black car start
複製程式碼
完美!
不熟悉 apply / call
的點選檢視:【進階3-3期】深度解析 call 和 apply 原理、使用場景及實現
不熟悉繼承的點選檢視:JavaScript常用八種繼承方案
模擬實現第二步
上面的程式碼已經實現了 80%,現在繼續優化。
建構函式返回值有如下三種情況:
- 1、返回一個物件
- 2、沒有
return
,即返回undefined
- 3、返回
undefined
以外的基本型別
情況1:返回一個物件
function Car(color, name) {
this.color = color;
return {
name: name
}
}
var car = new Car("black", "BMW");
car.color;
// undefined
car.name;
// "BMW"
複製程式碼
例項 car
中只能訪問到返回物件中的屬性。
情況2:沒有 return
,即返回 undefined
function Car(color, name) {
this.color = color;
}
var car = new Car("black", "BMW");
car.color;
// black
car.name;
// undefined
複製程式碼
例項 car
中只能訪問到建構函式中的屬性,和情況1完全相反。
情況3:返回undefined
以外的基本型別
function Car(color, name) {
this.color = color;
return "new car";
}
var car = new Car("black", "BMW");
car.color;
// black
car.name;
// undefined
複製程式碼
例項 car
中只能訪問到建構函式中的屬性,和情況1完全相反,結果相當於沒有返回值。
所以需要判斷下返回的值是不是一個物件,如果是物件則返回這個物件,不然返回新建立的 obj
物件。
所以實現程式碼如下:
// 第二版
function create() {
// 建立一個空的物件
var obj = new Object(),
// 獲得建構函式,arguments中去除第一個引數
Con = [].shift.call(arguments);
// 連結到原型,obj 可以訪問到建構函式原型中的屬性
obj.__proto__ = Con.prototype;
// 繫結 this 實現繼承,obj 可以訪問到建構函式中的屬性
var ret = Con.apply(obj, arguments);
// 優先返回建構函式返回的物件
return ret instanceof Object ? ret : obj;
};
複製程式碼
【進階3-4期】思考題解
問題:用 JS 實現一個無限累加的函式 add
,示例如下:
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10
// 以此類推
複製程式碼
實現:
function add(a) {
function sum(b) { // 使用閉包
a = a + b; // 累加
return sum;
}
sum.toString = function() { // 重寫toString()方法
return a;
}
return sum; // 返回一個函式
}
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10
複製程式碼
我們知道列印函式時會自動呼叫 toString()
方法,函式 add(a)
返回一個閉包 sum(b)
,函式 sum()
中累加計算 a = a + b
,只需要重寫sum.toString()
方法返回變數 a
就OK了。
參考
進階系列目錄
- 【進階1期】 呼叫堆疊
- 【進階2期】 作用域閉包
- 【進階3期】 this全面解析
- 【進階4期】 深淺拷貝原理
- 【進階5期】 原型Prototype
- 【進階6期】 高階函式
- 【進階7期】 事件機制
- 【進階8期】 Event Loop原理
- 【進階9期】 Promise原理
- 【進階10期】Async/Await原理
- 【進階11期】防抖/節流原理
- 【進階12期】模組化詳解
- 【進階13期】ES6重難點
- 【進階14期】計算機網路概述
- 【進階15期】瀏覽器渲染原理
- 【進階16期】webpack配置
- 【進階17期】webpack原理
- 【進階18期】前端監控
- 【進階19期】跨域和安全
- 【進階20期】效能優化
- 【進階21期】VirtualDom原理
- 【進階22期】Diff演算法
- 【進階23期】MVVM雙向繫結
- 【進階24期】Vuex原理
- 【進階25期】Redux原理
- 【進階26期】路由原理
- 【進階27期】VueRouter原始碼解析
- 【進階28期】ReactRouter原始碼解析
交流
進階系列文章彙總如下,內有優質前端資料,覺得不錯點個star。
我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!