JS手寫bind之處理new的情況詳解
大家好,我是前端西瓜哥。
之前寫了一篇關(guān)于 JS 中 bind 方法的實(shí)現(xiàn)的文章,并給出了實(shí)現(xiàn):
Function.prototype.myBind = function(thisArg, ...prefixArgs) {
const fn = this;
return function(...args) {
return fn.call(thisArg, ...prefixArgs, ...args);
}
}
但沒有處理 通過 new 創(chuàng)建實(shí)例 的情況。
因?yàn)楹苌贂龅浇o bind 返回的函數(shù)做 new 操作的場景,所以我沒去考慮這種特殊情況。
但面試中還是會涉及到的,我們還是實(shí)現(xiàn)一下兼容 new 操作的 bind 寫法,順便學(xué)習(xí)一下 new 操作符。
因?yàn)榇嬖谝欢ㄉ舷挛?,在閱讀本文前,建議先閱讀前一篇文章:《前端面試題:手寫 bind》。
new
我們先學(xué)習(xí)一下 new 操作符。
new 用于通過函數(shù)來創(chuàng)建一個(gè)對象實(shí)例,在很多語言中都能看到。
JS 的函數(shù),除了可以是普通函數(shù),比如:
function sum(a, b) {
return a + b;
}
還可以是構(gòu)造函數(shù),只需要在構(gòu)造時(shí)在它前面加一個(gè) new:
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = new Person('前端西瓜哥', 100)
// Person {name: '前端西瓜哥', age: 100}
new 創(chuàng)建一個(gè)新對象,做了下面幾件事:
- 創(chuàng)建一個(gè)空對象
{}; - 空對象的原型屬性
__proto__指向構(gòu)造函數(shù)的原型對象Person.prototype; - 函數(shù)中的 this 設(shè)置為這個(gè)空對象;
- 如果該函數(shù)不返回一個(gè)對象,就返回這個(gè) this,否則返回這個(gè)對象。
判斷函數(shù)是否通過 new 被調(diào)用
怎么判斷一個(gè)函數(shù)正在被 new 操作符調(diào)用?
答案是 使用 instanceof 判斷 this 是否為當(dāng)前函數(shù)的實(shí)例,即 this instanceof Fn 為 true,表示在通過 new 構(gòu)建實(shí)例。
我們看一個(gè)例子:
function Person() {
if (this instanceof Person) {
console.log('通過 new 構(gòu)建實(shí)例');
} else {
console.log('普通調(diào)用')
}
}
Person() // 輸出:普通調(diào)用
new Person() // 輸出:通過 new 構(gòu)建實(shí)例
在 Vuejs 的源碼,你會看到下面代碼,這里也用到了這個(gè)技巧。
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
你在開發(fā)環(huán)境如果不通過 new 來使用 Vue 對象,會在控制臺提示你要通過 new 來調(diào)用 Vue。
new 和 bind
如果我們 new 的是 Function.prototype.bind 返回的新函數(shù),會發(fā)生什么事情?
function Person(name, age) {
this.name = name;
this.age = age;
}
const BoundPerson = Person.bind(null, '前端西瓜哥');
const boundPerson = new BoundPerson(100);
// Person {name: '前端西瓜哥', age: 100}
boundPerson.__proto__ === Person.prototype
// true
結(jié)果等價(jià)于直接去 new 原始函數(shù)。
不同的是,仍舊可以進(jìn)行參數(shù)的預(yù)置??梢钥吹?,構(gòu)造函數(shù)的第一個(gè)參數(shù),在調(diào)用 bind 的時(shí)候就提前設(shè)置為 '前端西瓜哥'。
實(shí)現(xiàn)完整的 bind
完整實(shí)現(xiàn)如下:
Function.prototype.myBind = function(thisArg, ...prefixArgs) {
const fn = this;
const boundFn = function(...args) {
// 通過 new 使用當(dāng)前函數(shù)
if (this instanceof boundFn) {
return new fn(...prefixArgs, ...args);
}
// 普通的方法調(diào)用當(dāng)前函數(shù)
return fn.call(thisArg, ...prefixArgs, ...args);
}
boundFn.prototype = fn.prototype;
return boundFn;
}
這里我通過 this instanceof boundFn 來判斷是否用了 new,如果是,就直接 new 原始函數(shù)然后返回,記得帶上 bind 預(yù)置好的參數(shù)。
其他保持原樣(具體見上文《前端面試題:手寫 bind》)。
boundFn.prototype = fn.prototype; 這個(gè)可寫可不寫,只是讓 bind 返回的新函數(shù)的 prototype 指向原函數(shù)的 prototype。
如果是原生 bind 返回的函數(shù),它是沒有 protoype 屬性的,可以認(rèn)為它是一種特別的函數(shù),而我們實(shí)現(xiàn)的 bind 返回的卻是一個(gè)普通函數(shù),所以并不能完全模擬的。
如果你 追求完美的實(shí)現(xiàn),可以研讀一下 Function.prototype.bind 的標(biāo)準(zhǔn):
然后再看看知名的 core.js 庫中對 bind 的實(shí)現(xiàn):
其中核心實(shí)現(xiàn)為:
// `Function.prototype.bind` method implementation
// https://tc39.es/ecma262/#sec-function.prototype.bind
module.exports = Function.bind || function bind(that /* , ...args */) {
var F = aCallable(this);
var Prototype = F.prototype;
var partArgs = arraySlice(arguments, 1);
var boundFunction = function bound(/* args... */) {
var args = concat(partArgs, arraySlice(arguments));
return this instanceof boundFunction ? construct(F, args.length, args) : F.apply(that, args);
};
if (isObject(Prototype)) boundFunction.prototype = Prototype;
return boundFunction;
};
這里有更多的細(xì)節(jié):
- 這里判斷了 this 是否為函數(shù)類型,不是函數(shù)會報(bào)錯(cuò);
- F.prototype 需要是一個(gè)對象或函數(shù),才能賦值給新函數(shù);
- 使用了普通函數(shù)和 arguments,這是為了兼容 ES5。
結(jié)尾
手寫 bind,現(xiàn)在想來并不簡單,需要掌握好很多知識點(diǎn):
- bind 的詳盡用法:包括改變 this、預(yù)置參數(shù)、new 的表現(xiàn);
- 閉包的使用:保存一些私有變量;
- 通過原型鏈的方式(
this instanceof boundFn)判斷是否通過 new 調(diào)用當(dāng)前函數(shù); - 使用 call 在執(zhí)行時(shí)改變函數(shù)的 this 指向。
到此這篇關(guān)于JS手寫bind之處理new的情況詳解的文章就介紹到這了,更多相關(guān)JS處理new內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js實(shí)現(xiàn)圓形顯示鼠標(biāo)單擊位置
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)圓形顯示鼠標(biāo)單擊位置,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
Javascript highcharts 餅圖顯示數(shù)量和百分比實(shí)例代碼
這篇文章主要介紹了Javascript highcharts 餅圖顯示數(shù)量和百分比實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,需要的朋友可以參考下2016-12-12
原生javascript實(shí)現(xiàn)圖片輪播切換效果
這篇文章主要為大家詳細(xì)介紹了原生javascript實(shí)現(xiàn)圖片輪播切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
JS解析json數(shù)據(jù)并將json字符串轉(zhuǎn)化為數(shù)組的實(shí)現(xiàn)方法
json數(shù)據(jù)在ajax實(shí)現(xiàn)異步交互時(shí)起到了很重要的作用,他可以返回請求的數(shù)據(jù),然后利用客戶端的js進(jìn)行解析,這一點(diǎn)體現(xiàn)出js的強(qiáng)大,本文介紹JS解析json數(shù)據(jù)并將json字符串轉(zhuǎn)化為數(shù)組的實(shí)現(xiàn)方法,需要了解的朋友可以參考下2012-12-12
解決echarts的多個(gè)折現(xiàn)數(shù)據(jù)出現(xiàn)坐標(biāo)和值對不上的問題
這篇文章主要介紹了解決echarts的多個(gè)折現(xiàn)數(shù)據(jù)出現(xiàn)坐標(biāo)和值對不上的問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
JavaScript 替換Html標(biāo)簽實(shí)現(xiàn)代碼
這種技術(shù)被廣泛應(yīng)用于表單驗(yàn)證,語法高亮和危險(xiǎn)字符過濾中。一段話如果很長,如果不想像下面那樣替換,我們得想些辦法了。2009-10-10
JavaScript獲取當(dāng)前時(shí)間戳5種方法匯總
很多時(shí)候我們都把時(shí)間戳作為id值,下面這篇文章主要給大家介紹了關(guān)于JavaScript獲取當(dāng)前時(shí)間戳的5種方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
在前端中Proj4.js使用簡單介紹及進(jìn)階應(yīng)用
這篇文章主要介紹了在前端中Proj4.js使用簡單介紹及進(jìn)階應(yīng)用的相關(guān)資料,Proj4是一個(gè)用于處理JavaScript里的幾何圖形和坐標(biāo)計(jì)算的庫,它提供了多邊形、點(diǎn)、線等幾何對象的計(jì)算功能,需要的朋友可以參考下2025-01-01
在頁面中輸出當(dāng)前客戶端時(shí)間javascript實(shí)例代碼
這篇文章主要介紹了在頁面中輸出當(dāng)前客戶端時(shí)間javascript實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03

