【進階3-5期】深度解析 new 原理及模擬實現

木易楊說發表於2018-12-13

定義

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(...) 執行時,會發生以下事情:

  1. 一個繼承自 Foo.prototype 的新物件被建立。
  2. 使用指定的引數呼叫建構函式 Foo ,並將 this 繫結到新建立的物件。new Foo 等同於 new Foo(),也就是沒有指定引數列表,Foo 不帶任何引數呼叫的情況。
  3. 由建構函式返回的物件就是 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了。

參考

JavaScript 深入之 new 的模擬實現

MDN 之 new 運算子

MDN 之 Symbol

javascript 函式 add(1)(2)(3)(4) 實現無限極累加

進階系列目錄

  • 【進階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。

github.com/yygmind/blo…

我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!

【進階3-5期】深度解析 new 原理及模擬實現

相關文章