搜尋
首頁web前端js教程new操作符的詳細用法介紹

new操作符的詳細用法介紹

Apr 13, 2019 am 10:51 AM
javascript

這篇文章帶給大家的內容是關於new操作符的詳細用法介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

相信很多才接觸前端的小夥伴甚至工作幾年的前端小夥伴對new這個操作符的了解還停留在一知半解的地步,比較模糊。

就例如前不久接觸到一個入職兩年的前端小伙伴,他告訴我new是用來創建對象的,無可厚非,可能很多人都會這麼答!

那這麼答到底是錯很是對呢?

下面我們全面來討論一下這個問題:

我們要拿到一個對象,有很多方式,其中最常見的一種便是對象字面量:

var obj = {}

但是從語法來看,這就是一個賦值語句,是把對面字面量賦值給了obj這個變數(這樣說或許不是很準確,其實這裡是得到了一個物件的實例!!)

很多時候,我們說要創建一個對象,很多小夥伴雙手一摸鍵盤,啪啪幾下就敲出了這句代碼。

上面說了,這句話其實只是得到了一個物件的實例,那這句程式碼到底還能不能和創建物件畫上等號呢?我們繼續往下看。

要拿到一個物件的實例,還有一個和物件字面量等價的做法就是建構子:

var obj = new Object()

這句程式碼一敲出來,相信小夥伴們對剛才我說的obj只是一個實例物件沒有異議了吧!那很多小夥伴又會問了:這不就是new了一個新物件出來嘛!

沒錯,這確實是new了一個新對像出來,因為javascript之中,萬物解釋對象,obj是一個對象,而且是透過new運算子得到的,所以說很多小夥伴就肯定的說:new就是用來創建物件的!

這就不難解釋很多人把創建對象和實例化對象混為一談!!

我們在換個思路看看:既然js一切皆為對象,那為什麼還需要創建對象呢?本身就是對象,我們何來創建一說?那我們可不可以把這是一種繼承呢?

說了這麼多,相信不少夥伴已經看暈了,但是我們的目的就是一個:理清new是來做繼承的而不是所謂的創建對象! !

那繼承得到的實例物件有什麼特色呢?

  1. 訪問建構子裡面的屬性
  2. 存取原型鏈上的屬性

下面是一段經典的繼承,透過這段程式碼來熱熱身,好戲馬上開始:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

var person = new Person('小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()
現在我們來解決第一個問題:我們可以透過什麼方式實現存取到建構函數裡面的屬性呢?答案是callapply
function Parent() {
  this.name = ['A', 'B']
}

function Child() {
  Parent.call(this)
}

var child = new Child()
console.log(child.name) // ['A', 'B']

child.name.push('C')
console.log(child.name) // ['A', 'B', 'C']
第一個問題解決了,那我們又來解決第二個:那又怎麼存取原型鏈上的屬性呢?答案是__proto__

現在我們把上面那段熱身程式碼稍加改造,不使用new來建立實例:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

// var person = new Person('小明', 25)
var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments) // 获取arguments第一个参数:构造函数
  // 注意:此时的arguments参数在shift()方法的截取后只剩下两个元素
  obj.__proto__ = Constructor.prototype // 把构造函数的原型赋值给obj对象
  Constructor.apply(obj, arguments) // 改变够着函数指针,指向obj,这是刚才上面说到的访问构造函数里面的属性和方法的方式
  return obj
}

以上程式碼中的New函數,就是new運算子的實作

主要步驟:

  1. 建立一個空物件
  2. 取得arguments第一個參數
  3. 將建構子的原型鏈賦給obj
  4. 使用apply改變建構子this指向,指向obj對象,其後,obj就可以存取到建構函式中的屬性以及原型上的屬性和方法了
  5. 返回obj物件

可能很多小夥伴看到這裡覺得new不就是做了這些事情嗎,然而~~

然而我們卻忽略了一點,js裡面的函數是有回傳值的,即使構造函數也不例外。

如果我們在建構函式裡面回傳一個物件或一個基本值,上面的New函式會怎麼樣?

我們再來看一段程式碼:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  return {
    name: name,
    gender: '男'
  }
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

var person = new Person('小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

執行程式碼,發現只有namegender這兩個欄位如期輸出,age nation為undefined,say()報錯。

改一下程式碼建構函式的程式碼:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  // return {
  //   name: name,
  //   gender: '男'
  // }
  return 1
}

// ...

執行程式碼,發現所有欄位終於如期輸出。

這裡做個小結:

  1. 當建構子回傳參考型別時,建構裡面的屬性不能使用,只能使用傳回的物件;
  2. 當建構子函數傳回基本型別時,和沒有回傳值的情況相同,建構函數不受影響。

那我們現在來考慮下New函數要怎麼改才能實現上面總結的兩點函數呢?繼續往下看:

function Person(name, age) {
  // ...
}

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  
  // return obj
  return typeof result === 'object' ? result : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
// ...

執行此程式碼,發現已經實現了上面總結的兩點。

解決方案:使用變數接收建構函式的回傳值,然後在New函數裡面判斷傳回值類型,依照不同型別傳回不同的值。

看到這裡。又有小夥伴說,這下new已經完全實現了吧? ! !答案肯定是否定的,下面我們繼續看一段程式碼:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  // 返回引用类型
  // return {
  //   name: name,
  //   gender: '男'
  // }
  
  // 返回基本类型
  // return 1
  
  // 例外
  return null
}

再執行程式碼,發現又出問題了! ! !

那為什麼會出現這個問題呢?

剛才不是總結了傳回基本型別時建構函式不受影響嗎,而null就是基本型別啊?

此時心裡一萬頭草泥馬在奔騰啊有木頭有! ! !

解惑:null是基本类型没错,但是使用操作符typeof后我们不难发现:

typeof null === 'object' // true

特例:typeof null返回为'object',因为特殊值null被认为是一个空的对象引用

明白了这一点,那问题就好解决了:

function Person(name, age) {
  // ...
}

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
// ...

解决方案:判断一下构造函数返回值result,如果result是一个引用(引用类型和null),就返回result,但如果此时result为false(null),就使用操作符||之后的obj

好了,到现在应该又有小伙伴发问了,这下New函数是彻彻底底实现了吧!!!

答案是,离完成不远了!!

别急,在功能上,New函数基本完成了,但是在代码严谨度上,我们还需要做一点工作,继续往下看:

这里,我们在文章开篇做的铺垫要派上用场了:

var obj = {}

实际上等价于

var obj = new Object()

前面说了,以上两段代码其实只是获取了object对象的一个实例。再者,我们本来就是要实现new,但是我们在实现new的过程中却使用了new

这个问题把我们引入到了到底是先有鸡还是先有蛋的问题上!

这里,我们就要考虑到ECMAScript底层的API了————Object.create(null)

这句代码的意思才是真真切切地创建了一个对象!!

function Person(name, age) {
  // ...
}

function New() {
  // var obj = {}
  // var obj = new Object()
  var obj = Object.create(null)
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
// 这样改了之后,以下两句先注释掉,原因后面再讨论
// console.log(person.nation)
// person.say()

好了好了,小伙伴常常舒了一口气,这样总算完成了!!

但是,这样写,新的问题又来了。

小伙伴:啥?还有完没完?

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

function New() {
  // var obj = {}
  // var obj = new Object()
  var obj = Object.create(null)
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
// 这里解开刚才的注释
console.log(person.nation)
person.say()

别急,我们执行一下修改后的代码,发现原型链上的属性nation和方法say()报错,这又是为什么呢?

new操作符的詳細用法介紹

从上图我们可以清除地看到,Object.create(null)创建的对象是没有原型链的,而后两个对象则是拥有__proto__属性,拥有原型链,这也证明了后两个对象是通过继承得来的。

那既然通过Object.create(null)创建的对象没有原型链(原型链断了),那我们在创建对象的时候把原型链加上不就行了,那怎么加呢?

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

function New() {
  Constructor = [].shift.call(arguments)
  
  // var obj = {}
  // var obj = new Object()
  // var obj = Object.create(null)
  var obj = Object.create(Constructor.prototype)
  
  // obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

这样创建的对象就拥有了它初始的原型链了,这个原型链是我们传进来的构造函数赋予它的。

也就是说,我们在创建新对象的时候,就为它指定了原型链了,新创建的对象继承自传进来的构造函数!

现在,我们来梳理下最终的New函数做了什么事,也就是本文讨论的结果————new操作符到底做了什么?

  1. 获取实参中的第一个参数(构造函数),就是调用New函数传进来的第一个参数,暂时记为Constructor
  2. 使用Constructor的原型链结合Object.create创建一个对象,此时新对象的原型链为Constructor函数的原型对象;(结合我们上面讨论的,要访问原型链上面的属性和方法,要使用实例对象的__proto__属性)
  3. 改变Constructor函数的this指向,指向新创建的实例对象,然后call方法再调用Constructor函数,为新对象赋予属性和方法;(结合我们上面讨论的,要访问构造函数的属性和方法,要使用call或apply)
  4. 返回新创建的对象,为Constructor函数的一个实例对象。

现在我,我们来回答文章开始时提出的问题,new是用来创建对象的吗?

现在我们可以勇敢的回答,new是用来做继承的,而创建对象的其实是Object.create(null)。
在new操作符的作用下,我们使用新创建的对象去继承了他的构造函数上的属性和方法、以及他的原型链上的属性和方法!

写在最后:

补充一点关于原型链的知识:

  1. JavaScript中的函数也是对象,而且对象除了使用字面量定义外,都需要通过函数来创建对象
  2. prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式
  3. prototype和__proto__都指向原型对象
  4. 任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象
  5. 任意一个实例化的对象,都有一个__proto__属性,指向构造函数的原型对象。

以上是new操作符的詳細用法介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

在Quartz中如何在任務開始前發送通知?在Quartz中如何在任務開始前發送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前發送任務通知在使用Quartz定時器進行任務調度時,任務的執行時間是由cron表達式設定的。現�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器