欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS?new操作原理及手寫函數(shù)模擬實現(xiàn)示例

 更新時間:2022年07月06日 11:09:36   作者:種花家的進階  
這篇文章主要為大家介紹了JS?new操作原理及手寫函數(shù)模擬實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

我們知道,在 ES6 之前(ES5),JavaScript 中類的表現(xiàn)形式就是構(gòu)造函數(shù)。

JavaScript 中的構(gòu)造函數(shù)是怎么樣的?

  • 構(gòu)造函數(shù)也是一個普通的函數(shù),從表現(xiàn)形式上看,和普通的函數(shù)沒有任何區(qū)別(除了按照慣例,構(gòu)造函數(shù)名稱的首字母通常會大寫);
  • 但如果一個普通函數(shù)被 new 操作符調(diào)用了,那么這個函數(shù)就叫做構(gòu)造函數(shù);

原理

如果一個函數(shù)被 new 操作符調(diào)用了,那么它會執(zhí)行如下操作:

  • 在內(nèi)存中創(chuàng)建一個新對象;
  • 將這個新對象內(nèi)部的 [[Prototype]] 屬性賦值為構(gòu)造函數(shù)的 prototype 屬性;
  • 將構(gòu)造函數(shù)內(nèi)部的 this 賦值為這個新對象(即 this 指向新對象);
  • 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性);
  • 如果構(gòu)造函數(shù)返回了非空對象,則返回該對象;否則,返回剛創(chuàng)建的新對象;

手寫函數(shù)模擬 new

下面,我們就根據(jù)上面的原理,嘗試自己手寫一個函數(shù),模擬實現(xiàn) new 操作符的功能。

v1 基本實現(xiàn)

我們先用 ES5 的語法來進行實現(xiàn):

function useNewOperator() {
  var constructor = arguments[0]
  var args = [].slice.call(arguments, 1)
  // 1. 在內(nèi)存中創(chuàng)建一個新對象
  var obj = {}
  // 2. 這個新對象內(nèi)部的 [[Prototype]] 指針被賦值為構(gòu)造函數(shù)(constructor)的 prototype 屬性
  obj.__proto__ = constructor.prototype
  // 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個新對象(即 this 指向新對象)
  // 4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性)
  var res = constructor.apply(obj, args)
  // 5. 如果構(gòu)造函數(shù)返回非空對象,則返回該對象;否則,返回剛才創(chuàng)建的新對象
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}
// 測試
function Person(name, age) {
  this.name = name
  this.age = age
  // return undefined
  // return null
  // return {}
  // return 123
  // return ''
  // return String(123)
  // return new String(123)
  // return { name: 'wy' }
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj

v2 考慮參數(shù)類型

上面的基本實現(xiàn)能跑但還存在問題,即沒有考慮傳入第一個參數(shù)是否為函數(shù)類型,如果第一個參數(shù)傳入的不是函數(shù),那么在執(zhí)行 constructor.apply(obj, args) 這行代碼調(diào)用 constructor() 時就會報錯了。所以我們需要加上判斷,如果第一個參數(shù)傳入的不是一個函數(shù),就直接拋出異常:

function useNewOperator() {
  var constructor = arguments[0]
+
+  if (typeof constructor !== 'function') {
+    throw new TypeError('the first argument to useNewOperator function must be a function')
+  }
+
  var args = [].slice.call(arguments, 1)
  // 1. 在內(nèi)存中創(chuàng)建一個新對象
  var obj = {}
  // 2. 這個新對象內(nèi)部的 [[Prototype]] 指針被賦值為構(gòu)造函數(shù)(constructor)的 prototype 屬性
  obj.__proto__ = constructor.prototype
  // 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個新對象(即 this 指向新對象)
  // 4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性)
  var res = constructor.apply(obj, args)
  // 5. 如果構(gòu)造函數(shù)返回非空對象,則返回該對象;否則,返回剛才創(chuàng)建的新對象
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}
// 測試
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
const obj = {}
const p2 = useNewOperator(obj, 'zhj', 20) // Uncaught TypeError: the first argument to useNewOperator function must be a function
console.log(p2);
p2.sayName()

v3 Object.prototype.__proto__ 的替代方案

前面我們在將新對象內(nèi)部的 [[Prototype]] 屬性賦值為構(gòu)造函數(shù)的 prototype 屬性時,是通過給 obj 上的 __proto__ 屬性賦值實現(xiàn)的(相當(dāng)于使用了 Object.prototype.__proto__),雖然可以,但不推薦使用 Object.prototype.__proto__,更推薦使用 Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf(參考鏈接:developer.mozilla.org/zh-CN/docs/… )。所以我們做如下修改:

function useNewOperator() {
  var constructor = arguments[0]
  if (typeof constructor !== 'function') {
    throw new TypeError('the first argument to useNewOperator function must be a function')
  }
  var args = [].slice.call(arguments, 1)
  // 1. 在內(nèi)存中創(chuàng)建一個新對象
  var obj = {}
  // 2. 這個新對象內(nèi)部的 [[Prototype]] 指針被賦值為構(gòu)造函數(shù)(constructor)的 prototype 屬性
+ Object.setPrototypeOf(obj, constructor.prototype)
  // 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個新對象(即 this 指向新對象)
  // 4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性)
  var res = constructor.apply(obj, args)
  // 5. 如果構(gòu)造函數(shù)返回非空對象,則返回該對象;否則,返回剛才創(chuàng)建的新對象
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}
// 測試
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj

或者我們還可以使用 Object.create() 直接指定原型來創(chuàng)建新對象:

function useNewOperator() {
  var constructor = arguments[0]
  if (typeof constructor !== 'function') {
    throw new TypeError('the first argument to useNewOperator function must be a function')
  }
  var args = [].slice.call(arguments, 1)
  // 1. 在內(nèi)存中創(chuàng)建一個新對象
- var obj = {}
-
  // 2. 這個新對象內(nèi)部的 [[Prototype]] 指針被賦值為構(gòu)造函數(shù)(constructor)的 prototype 屬性
+ var obj = Object.create(constructor.prototype)
  // 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個新對象(即 this 指向新對象)
  // 4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性)
  var res = constructor.apply(obj, args)
  // 5. 如果構(gòu)造函數(shù)返回非空對象,則返回該對象;否則,返回剛才創(chuàng)建的新對象
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}
// 測試
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj

v4 使用 ES6 語法實現(xiàn)

下面,我們再來使用 ES6 語法(剩余參數(shù)(rest parameters)、const)進行實現(xiàn):

function useNewOperator(constructor, ...args) {
  if (typeof constructor !== 'function') {
    throw new TypeError('the first argument to useNewOperator function must be a function')
  }
  // 1. 在內(nèi)存中創(chuàng)建一個新對象
  const obj = {}
  // 2. 這個新對象內(nèi)部的 [[Prototype]] 指針被賦值為構(gòu)造函數(shù)(constructor)的 prototype 屬性
  Object.setPrototypeOf(obj, constructor.prototype)
  // 或者使用 Object.create() 直接指定原型創(chuàng)建新對象
  // const obj = Object.create(constructor.prototype)
  // 3. 構(gòu)造函數(shù)內(nèi)部的 this 被賦值為這個新對象(即 this 指向新對象)
  // 4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼(給新對象添加屬性)
  const res = constructor.apply(obj, args)
  // 5. 如果構(gòu)造函數(shù)返回非空對象,則返回該對象;否則,返回剛才創(chuàng)建的新對象
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}
// 測試
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj

v5 考慮 ES6 的 new.target 檢測

最后,還有一個點需要考慮,就是 ES6 新增的 new.target 屬性,在通過使用 new 操作符被調(diào)用的構(gòu)造方法或函數(shù)中,new.target 會返回一個指向構(gòu)造方法或函數(shù)的引用(參考鏈接:developer.mozilla.org/en-US/docs/… )。所以我們可以使用 new.target 來檢測函數(shù)或構(gòu)造方法是否是通過 new 操作符被調(diào)用的。那么我們還需要在自己實現(xiàn)的 useNewOperator 函數(shù)中添加相應(yīng)的代碼:

無注釋版本

function useNewOperator(constructor, ...args) {
  if (typeof constructor !== 'function') {
    throw new TypeError('the first argument to useNewOperator function must be a function')
  }
  useNewOperator.target = constructor
  const obj = {}
  Object.setPrototypeOf(obj, constructor.prototype)
  // const obj = Object.create(constructor.prototype)
  const res = constructor.apply(obj, args)
  if (res != null && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}

以上,就是關(guān)于 new 操作背后的原理,以及手寫函數(shù)模擬實現(xiàn) new 操作過程的所有內(nèi)容啦,更多關(guān)于JS new操作手寫函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Phaser.js實現(xiàn)簡單的跑酷游戲附源碼下載

    Phaser.js實現(xiàn)簡單的跑酷游戲附源碼下載

    這篇文章主要介紹了Phaser.js實現(xiàn)簡單的跑酷游戲附源碼下載,需要的朋友可以參考下
    2018-10-10
  • JS如何實現(xiàn)一個單文件組件

    JS如何實現(xiàn)一個單文件組件

    這篇文章主要介紹了JS如何實現(xiàn)一個單文件組件,對單文件組件感興趣的同學(xué),可以參考下
    2021-05-05
  • javascript 自動轉(zhuǎn)到命名錨記

    javascript 自動轉(zhuǎn)到命名錨記

    javascript 自動轉(zhuǎn)到命名錨記,方面業(yè)內(nèi)控制導(dǎo)航等信息
    2009-01-01
  • js字符串轉(zhuǎn)換為對象格式的三種方法總結(jié)

    js字符串轉(zhuǎn)換為對象格式的三種方法總結(jié)

    關(guān)于js里面的字符串轉(zhuǎn)對象,又或者是對象轉(zhuǎn)為字符串,都是平時開發(fā)應(yīng)用是經(jīng)常用到的知識點,下面這篇文章主要給大家介紹了關(guān)于js字符串轉(zhuǎn)換為對象格式的三種方法,需要的朋友可以參考下
    2022-12-12
  • 純js實現(xiàn)背景圖片切換效果代碼

    純js實現(xiàn)背景圖片切換效果代碼

    現(xiàn)在純粹用js的人越來越少了,更多的人喜歡jquery,可使我還是喜歡javascript,下面給出一個用javascript寫的背景切換的小例子,各位大蝦不要罵,只是寫給自己看的,也供js事件的產(chǎn)考依據(jù)。
    2010-11-11
  • js遍歷對象key和value實戰(zhàn)舉例

    js遍歷對象key和value實戰(zhàn)舉例

    這篇文章主要給大家介紹了關(guān)于js遍歷對象key和value的相關(guān)資料,隨著JavaScript在web應(yīng)用程序中的廣泛使用,遍歷對象的key和value成為了編寫復(fù)雜代碼所必需的技能,需要的朋友可以參考下
    2023-07-07
  • 微信小程序?qū)崿F(xiàn)書架小功能

    微信小程序?qū)崿F(xiàn)書架小功能

    這篇文章主要為大家詳細介紹了微信小程序?qū)崿F(xiàn)書架小功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • javascript的閉包介紹(司徒正美)

    javascript的閉包介紹(司徒正美)

    今天又在無憂看到閉包的使用了,整理一下閉包的東西。
    2011-09-09
  • onbeforeunload與onunload事件異同點總結(jié)

    onbeforeunload與onunload事件異同點總結(jié)

    本文對onbeforeunload與onunload事件的異同點、觸發(fā)于、可以用在哪些元素以及解決刷新頁面時不調(diào)用onbeforeunload等等,感興趣的朋友可以參考下哈
    2013-06-06
  • Webpack打包字體font-awesome的方法示例

    Webpack打包字體font-awesome的方法示例

    本篇文章主要介紹了Webpack打包字體font-awesome的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04

最新評論