詳解 JS 中 new 調用函數原理

JavaScript 中常常使用構造函數建立對象(經過 new 操做符調用一個函數),那在使用 new 調用一個函數的時候到底發生了什麼?先看幾個例子,再解釋背後發生了什麼。html

1)看三個例子

1.1 無 return 語句

構造函數最後沒有 return 語句,這也是使用構造函數時默認狀況,最後會返回一個新對象,以下:函數

function Foo(age) {
  this.age = age;
}

var o = new Foo(111);
console.log(o);

這是常見的使用構造函數建立對象的過程,打印出來的是 {age: 111}ui

1.2 return 對象類型數據

構造函數最後 return 對象類型數據:this

function Foo(age) {
  this.age = age;

  return { type: "我是顯式返回的" };
}

var o = new Foo(222);
console.log(o);

打印出來的是 {type: '我是顯式返回的'},也就是說,return 以前的工做都白作了,最後返回 return 後面的對象。prototype

1.3 return 基本類型數據

那是否是隻要構造函數體內最後有 return,返回都是 return 後面的數據呢?code

咱們看下返回基本類型數據的狀況:htm

function Foo(age) {
  this.age = age;

  return 1;
}

var o = new Foo(333);
console.log(o);

打印出來的是 {age: 333},和沒有 return 時效果同樣。跟預期不同,背後你原理看下面分析。對象

2)背後原理

2.1 非箭頭函數的狀況

當使用 new 操做符建立對象是,ES5 官方文檔在 函數定義 一節中作了以下定義 13.2.2 [[Construct]]繼承

When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:ip

  1. Let obj be a newly created native ECMAScript object.
  2. Set all the internal methods of obj as specified in 8.12.
  3. Set the [[Class]] internal property of obj to Object.
  4. Set the [[Extensible]] internal property of obj to true.
  5. Let proto be the value of calling the [[Get]] internal property of F with argument "prototype".
  6. If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
  7. If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
  8. Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
  9. If Type(result) is Object then return result.
  10. Return obj.

看第 八、9 步:

8)調用函數 F,將其返回值賦給 result;其中, F 執行時的實參爲傳遞給 [[Construct]](即 F 自己) 的參數, F 內部 this 指向 obj
9)若是 resultObject 類型,返回 result

這也就解釋了若是構造函數顯式返回對象類型,則直接返回這個對象,而不是返回最開始建立的對象。

最後在看第 10 步:

10)若是 F 返回的不是對象類型(第 9 步不成立),則返回建立的對象 obj

若是構造函數沒有顯式返回對象類型(顯式返回基本數據類型或者直接不返回),則返回最開始建立的對象。

2.2 箭頭函數的狀況

那若是構造函數是箭頭函數怎麼辦?

箭頭函數中沒有 [[Construct]] 方法,不能使用 new 調用,會報錯。

NOTICE:其中 [[Construct]] 就是指構造函數自己。

相關規範在 ES6 的官方文檔 中有提,但自從 ES6 以來的官方文檔巨難懂,在此不作表述。

3)new 調用函數完整過程

3.1 中文描述及相關代碼分析

除了箭頭函數以外的任何函數,均可以使用 new 進行調用,背後發生了什麼,上節英文講述的很清楚了,再用中文描述以下:

1)建立 ECMAScript 原生對象 obj
2)給 obj 設置原生對象的內部屬性;(和原型屬性不一樣,內部屬性表示爲 [[PropertyName]],兩個方括號包裹屬性名,而且屬性名大寫,好比常見 [[Prototype]][[Constructor]]
3)設置 obj 的內部屬性 [[Class]]Object
4)設置 obj 的內部屬性 [[Extensible]]true
5)將 proto 的值設置爲 Fprototype 屬性值;
6)若是 proto 是對象類型,則設置 obj 的內部屬性 [[Prototype]] 值爲 proto;(進行原型鏈關聯,實現繼承的關鍵
7)若是 proto 是不對象類型,則設置 obj 的內部屬性 [[Prototype]] 值爲內建構造函數 Objectprototype 值;(函數 prototype 屬性能夠被改寫,若是改爲非對象類型,obj[[Prototype]] 就指向 Object 的原型對象)
8)9)10)見上節分析。(決定返回什麼)

對於第 7 步的狀況,見下面代碼:

function Foo(name) {
  this.name = name;
}

var o1 = new Foo("xiaoming");
console.log(o1.__proto__ === Foo.prototype); // true

// 重寫構造函數原型屬性爲非對象類型,實例內部 [[Prototype]] 屬性指向 Object 原型對象
// 由於實例是一個對象類型的數據,默認會繼承內建對象的原型,
// 若是構造函數的原型不知足造成原型鏈的要求,那就跳過直接和內建對象原型關聯
Foo.prototype = 1;
var o2 = new Foo("xiaohong");
console.log(o2.__proto__ === Foo.prototype); // false
console.log(o2.__proto__ === Object.prototype); // true

3.2 更簡潔的語言描述

若執行 new Foo(),過程以下:

1)建立新對象 o
2)給新對象的內部屬性賦值,關鍵是給[[Prototype]]屬性賦值,構造原型鏈(若是構造函數的原型是 Object 類型,則指向構造函數的原型;否則指向 Object 對象的原型);
3)執行函數 Foo,執行過程當中內部 this 指向新建立的對象 o
4)若是 Foo 內部顯式返回對象類型數據,則,返回該數據,執行結束;否則返回新建立的對象 o

4)幾點說明

4.1 判斷是不是 Object 類型

關於一個數據是不是 Object 類型,能夠經過 instanceof 操做符進行判斷:若是 x instanceof Object 返回 true,則 xObject 類型。

由上可知,null instanceof Object 返回 false,因此 null 不是 Object 類型,儘管typeof null 返回 "Object"。

4.2 instanceof 原理

instanceof 的工做原理是:在表達式 x instanceof Foo 中,若是 Foo 的原型(即 Foo.prototype)出如今 x 的原型鏈中,則返回 true,否則,返回 false

由於函數的原型能夠被改寫,因此會出如今 x 經過 Foo new 出來以後徹底改寫 Foo 的原型 x instanceof Foo 返回 false 的狀況。由於實例建立以後重寫構造函數原型,實例指向的原型已經不是構造函數的新的原型了,見下面代碼:

const Foo = function() {};

const o = new Foo();

o instanceof Foo; // true

// 重寫 Foo 原型
Foo.prototype = {};
o instanceof Foo; // false

參考資料

What values can a constructor return to avoid returning this?
[[Construct]] internal method

相關文章
相關標籤/搜索