模擬實現js的bind方法
阿新 • • 發佈:2020-08-11
目錄
var obj = {}; console.log(obj); console.log(typeof Function.prototype.bind); // function console.log(typeof Function.prototype.bind()); // function console.log(Function.prototype.bind.name); // bind console.log(Function.prototype.bind().name); // bound
bind是什麼
-
a.
bind
是Function
原型鏈中Function.prototype
的一個函式,每個函式都可以呼叫它。 -
b.
bind
本身是一個函式名為bind
的函式,返回值也是函式,函式名是bound
。(console出來為bound
加上一個空格)。
let obj = { name: "yato" } function original(a,b){ console.log(this.name) console.log([a,b]) return false } let bound = original.bind(obj, 1) let boundInvoke = bound(2) // 'yato', Array(2)[1,2] console.log(boundInvoke) // false console.log(original.bind.name) // bind console.log(original.bind.length) // 1 console.log(original.bind().length) // 2 返回original函式形參個數 console.log(bound.name) // 'bound original' console.log((function(){}).bind().name) // 'bound ' console.log((function(){}).bind().length) // 0
進一步理解bind
-
a. 呼叫
bind
的函式中的this
指向bind()
函式的第一個引數。 -
b. 函式
bind()
時傳遞的引數被bind接受處理,bind()
完畢之後,程式呼叫返回的函式(bound)時,傳遞的引數也接收處理了,也就是在bind()
內部合併處理了。 -
c. 並且
bind()
後的函式的name為bound+空格+呼叫bind的函式名
。如果呼叫函式為匿名函式,則名字為bound+空格
-
d.
bind
後的返回值函式,執行後返回值時原函式(original)
的返回值(上例中的false) -
e.
bind
函式的形參(即函式的length
)是1
。bind
後返回的bound函式形參不定
original
)形參個數決定。
根據上面的兩個例子,模擬實現一個簡單版的bindFn
Function.prototype.bindFn = function bindFake(thisArg){
if(typeof this !== 'function'){
throw new TypeError(this + 'must be a function')
}
// 儲存函式本身
let self = this
// 去除thisArg的其他引數,轉成陣列
let args = [].slice.call(arguments, 1)
let bound = function(){
// bind 返回的函式,也就是bound,在程式中被呼叫時傳遞的引數轉成陣列
let boundArg = [].slice.call(arguments);
// apply修改this指向,把兩個函式的引數合併傳給self函式,返回執行結果
return self.apply(thisArg, args.concat(boundArg))
}
return bound
}
// Test
let obj = {
name: 'yato'
}
function original(a, b){
console.log(this.name)
console.log([a,b])
}
let bound = original.bindFn(obj, 1)
bound(2); // 'yato', [1,2]
但是函式是可以使用new
來例項化的。
let obj = {name : 'yato'}
function original(a, b){
console.log('this : ', this)
console.log('typeof this : ', typeof this)
this.name = b
console.log('name: ', this.name)
console.log('this: ', this)
console.log([a,b])
}
let bound = original.bind(obj, 1)
let newBoundInvoke = new bound(2)
console.log('newBoundInvoke: ', newBoundInvoke)
// this : original {}
// typeof this : object
// name: 2
// this: original { name: 2 }
// [ 1, 2 ]
// newBoundInvoke: original { name: 2 }
分析例子可以得出結論
-
a. 從例子中可以看出
this
指向了new bound()
生成的物件 -
b. new bound() 的返回值是以original原函式構造器生成的新物件。original原函式的this指向的就是這個新物件。
-
c.簡要剖析下new做了什麼
- 建立一個全新的空物件
- 對這個物件指向原型連結(
instance.__proto__ = Class.prototype
),其實Class.prototype
就是constructor
- 生成的新物件會繫結到函式呼叫的this
- 通過new建立的每個物件最終被
[[prototype]]
連結這個函式的prototype
上(參考2) - 如果函式沒有返回物件型別
Object
(包含Function
,Array
,Date
,RegExg
,Error
),那麼new表示式
中的函式呼叫會自動返回這個新的物件
所以相當於在new呼叫時,bind的返回值函式bound內部要實現new的操作
// 第二版 實現new呼叫
Function.prototype.bindFn = function bindFake(thisArg){
if(typeof this !== 'function'){
throw new TypeError(this + ' must be a function')
}
// 儲存呼叫bind的函式本身的引用
let self = this
// 去除thisArg引數,其他轉成陣列
let args = [].slice.call(arguments, 1)
let bound = function(){
let boundArgs = [].slice.call(arguments)
let finalArgs = args.concat(boundArgs)
// new 呼叫時,其實this instanceof bound 判斷不是很準確。es6
// new.target就是解決這一問題的
if(this instanceof bound){
// 這裡是實現上文描述的 new 的第 1, 2, 4 步
// 1.建立一個全新的物件
// 2.並且執行[[Prototype]]連結
// 4.通過`new`建立的每個物件將最終被`[[Prototype]]`連結到這個函式的`prototype`物件上。
// self可能是ES6的箭頭函式,沒有prototype,所以就沒必要再指向做prototype操作。
if(self.prototype){
function Empty(){}
Empty.prototype = self.prototype
bound.prototype = new Empty()
}
// 這裡實現的時上文描述的第三步
// 3.生成的新物件會繫結到函式呼叫的this
let result = self.apply(this, finalArgs);
// 這裡是實現上文描述的 new 的第 5 步
// 5.如果函式沒有返回物件型別`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, // `Error`),
// 那麼`new`表示式中的函式呼叫會自動返回這個新的物件。
let isObject = typeof result === 'object' && result !== null
let isFunction = typeof result === 'function'
if(isObject || isFunction)
return result
return this
}else{
// apply修改this指向,把兩個函式的引數合併傳給self函式,並執行self函式,返回執行結果
return self.apply(thisArg, finalArgs)
}
}
return bound
}
// Test
let obj = {name : 'yato'}
function original(a, b){
console.log('this : ', this)
console.log('typeof this : ', typeof this)
this.name = b
console.log('name: ', this.name)
console.log('this: ', this)
console.log([a,b])
}
let bound = original.bindFn(obj, 1)
let newBoundInvoke = new bound(2)
console.log('newBoundInvoke: ', newBoundInvoke)
// this : bound {}
// typeof this : object
// name: 2
// this: bound { name: 2 }
// [ 1, 2 ]
// newBoundInvoke: bound { name: 2 }
總結
-
bind
是Function
原型鏈中Function.prototype
的一個屬性,它是一個函式,修改this指向
,合併引數傳遞給原函式,返回值是一個新的函式
。
-
bind
返回的函式可以通過new
呼叫,這是提供的this引數被忽略
,指向了new生成的全新物件。bind()
內部模擬實現了new
操作符