【js 基礎系列】沒有那麼簡單的 new 操作符

如果我說你對 new 操作符可能不是很瞭解。很多人可能會很不屑,不就是 new 一個構造函數嗎,來我給你背一下 new 的過程,巴拉巴拉巴拉……

然鵝 new 操作符真的那麼簡單嗎?我們看下面的幾個問題:

  1. new 一個構造函數發生了什麼?
  2. 如果我們的構造函數是一個箭頭函數,得到的結果是什麼?
  3. 如果構造函數 return 一個普通對象,new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?
  4. 如果構造函數 return 一個基本類型的值,此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?
  5. 如果構造函數 return 一個Array 類型的值,此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?
  6. 如果構造函數 return 一個基本包裝類型的值(Number、String、Boolean),此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?
  7. 如果構造函數 return null 呢,此時 new 此構造函數得到的實例對象又是什麼?隱式原型指向什麼?

是不是有點懷疑自己了。

如果你可以全部正確回答,那可以關掉本篇博客去看其他知識點了,因爲你對 new 已經瞭解的很透徹了,但是如果你一知半解,請繼續往下看,看完本文之後相信你對 new 會有一個更深入的瞭解。

首先第一個問題,new 一個構造函數發生了什麼?

Q:new 一個構造函數發生了什麼?

老生常談,很多人都可以說的很詳細。以下是 MDN 給出的答案

  1. 創建一個空的簡單JavaScript對象(即{});
  2. 鏈接該對象(即設置該對象的構造函數)到另一個對象 ;
  3. 將步驟1新創建的對象作爲this的上下文 ;
  4. 如果該函數沒有返回對象,則返回this。

我們用代碼分解一下以上步驟

// 示例代碼
function Parent (name) {
	this.name = name
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

上面的代碼看起來很簡單,其實卻做了很多的隱式操作,const people = new Parent('張三') 其實相當於做了如下操作

// 首先會創建一個對象,並且將此對象的隱式原型對象(__proto__)指向原型函數的顯示原型對象(prototype)
const obj = Object.create(Parent.prototype)

// 將新創建的對象作爲 this 的上下文
this = obj

// 執行構造函數中的代碼
this.name = name

// 如果構造函數返回一個對象,則得到一個對象,否則得到我們隱式創建的對象
if (此構造函數返回一個對象) {
	return 這個對象
} else {
	return obj
}

如果我們的構造函數是一個箭頭函數呢?

Q:如果我們的構造函數是一個箭頭函數,得到的結果是什麼?

// 示例代碼
const Parent = (name) => {
	this.name = name
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

首先提示不可以給 undefined 設置 getName
在這裏插入圖片描述
好,那我們去掉原型的設置
在這裏插入圖片描述
結果提示 Parent 不是一個構造函數
???,why?

我們看一下普通函數和箭頭函數的區別
在這裏插入圖片描述
在這裏插入圖片描述
可以看到箭頭函數是沒有顯示原型的,同樣我們可以在 MDN 中找到答案。

箭頭函數不能用作構造器,和 new一起用會拋出錯誤。
箭頭函數沒有prototype屬性。
箭頭函數不會創建自己的this,它只會從自己的作用域鏈的上一層繼承this。
由於 箭頭函數沒有自己的this指針,通過 call() 或 apply() 方法調用一個函數時,只能傳遞參數(不能綁定this),他們的第一個參數會被忽略。
等 ……

所以我們的第二個問題也有了答案

A:箭頭函數是不可以做爲構造函數的,因爲箭頭函數沒有 this,且與普通函數相比缺少了 prototype,和 new 操作符一起使用會報錯。

那如果我們的構造函數 return 一個普通對象呢?

Q:如果構造函數 return 一個普通對象,new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?

// 示例代碼
function Parent (name) {
	this.name = name
	return { like: '遊戲', name: '李四' }
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

如上示例,我們看一下 people 是什麼
在這裏插入圖片描述
爲什麼會這樣呢?觀察 MDN 文檔中 new 的過程

如果該函數 沒有返回對象 ,則返回this。

所以這個問題我們也有了答案

A:如果構造函數返回一個對象,得到的實例對象就是我們構造函數 return 的實例對象本身,實例對象的原型也和構造函數的原型一致。

如果構造函數 return 一個基本類型

Q:如果構造函數 return 一個基本類型的值,此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?

// 示例代碼
function Parent (name) {
	this.name = name
	return `李四愛玩遊戲`
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

此時,我們得到的實例對象又是什麼樣子的呢?
可能有些同學已經猜到了,我們會得到構造函數隱式創建的對象。
在這裏插入圖片描述
還是可以在那句話中找到線索

如果該函數 沒有返回對象 ,則返回this。

我們得到結論:

A:如果構造函數 return 的值是一個基本類型,那我們得到的實例對象是構造函數隱式創建的對象。隱式原型指向構造函數的顯示原型。

如果構造函數 return 一個 Array 類型

Q:如果構造函數 return 一個 Array 類型的值,此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?

// 示例代碼
function Parent (name) {
	this.name = name
	return [ '李四', '遊戲' ]
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

我們在控制檯輸出一下結果,看實例對象是什麼
在這裏插入圖片描述
這裏不難理解爲什麼會得到數組,因爲 Array 本身就是 Object 的子集,JS 裏數組是用 Object 實現的。

A:如果構造函數 return 一個數組,實例對象就是我們 return 的數組,隱式原型指向也與其相同。

如果構造函數 return 一個基本包裝類型的值

Q:如果構造函數 return 一個基本包裝類型的值(Number、String、Boolean),此時 new 此構造函數得到的實例對象是什麼?隱式原型指向什麼?

// 示例代碼
function Parent (name) {
	this.name = name
	return new String(`李四愛玩遊戲`)
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

看到這裏相信大家都已經知道答案了,得到的實例對象就是我們 return 的值

在這裏插入圖片描述
也不難理解,基本包裝類型也是 Object 的子集,也是一個對象。也不難推導出,其他的像 Date、BigInt 等也是與此相同。

A:如果構造函數 return 一個基本包裝類型,實例對象就是我們 return 的值,隱式原型指向也與其相同。

如果構造函數 return null

Q:如果構造函數 return null 呢,此時 new 此構造函數得到的實例對象又是什麼?隱式原型指向什麼?

// 示例代碼
function Parent (name) {
	this.name = name
	return null
}
Parent.prototype.getName = function() {
	return this.name
}

const people = new Parent('張三')

這裏爲什麼會單獨把 null 拿出來呢?因爲 null 在 js 裏的位置很尷尬,它是一種基本類型的值,但是 typeof null 的結果卻是 object,所以爲了更清晰,我們單獨測試一下他的結果,如下
在這裏插入圖片描述
可以看到 typeof 的檢測結果對其本質沒有影響,還是和基本類型得到的結果一致
A:如果構造函數 return 的值是 null ,typeof 不影響其結果,和基本類型得到的結果相同

總結

到此我們把所有的情況都測試了一遍,是不是對 new 有了更清晰的認識呢

最後我們回顧上面的問題,發現其實可以總結爲三種情況

  1. 箭頭函數是特殊的函數,不可以使用 new 操作符。
  2. 如果構造函數返回基本類型的值,則我們得到的實例對象是構造函數隱式創建的對象,隱式原型指向構造函數的顯式原型。這裏我們觀察沒有 return 值的構造函數,發現其實就相當於 return undefined,所以也可以歸類爲返回基本類型得值
  3. 如果構造函數返回的是一個對象類型的值(包括 Object 的子集,Array、Number 等)則實例對象是我們 return 的值,且隱式原型的指向與 return 的值的隱式原型相同

到此,我們詳細解析了 new 一個構造函數的過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章