bind apply call 都应用于改变this的指向,三者的区别在于:
bind重新绑定了函数的this,不会立即执行函数
apply和call重新绑定了函数的this,并且会立即执行该函数
手写bind
根据MDN,bind()方法创建一个新的函数, 当这个新函数被调用时其this置为提供的值,其参数列表前几项置为创建时指定的参数序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
bind()函数会创建一个新绑定函数, 绑定函数与被调函数具有相同的函数体(在 ECMAScript 5 中)。调用绑定函数通常会导致执行包装函数,绑定函数也可以使用new运算符构造:这样做就好像已经构造了目标函数一样。此时提供的this值将被忽略,而前置参数将提供给模拟函数
总的来说,bind有以下三个功能点:
- 改变原函数的
this指向,即绑定新的上下文,返回原函数的拷贝 - 当绑定函数被调用时,
bind的额外参数将置于实参之前传递给被绑定的方法 - 一个绑定函数也能使用
new操作符创建对象,这种行为就像把原函数当成构造器,thisArg参数无效。但new操作符修改this指向的优先级更高。
了解了bind的功能之后,我们来实现一个myBind
Function.prototype.myBind = function (context) {
// 判断调用者是否为函数
if(typeof this !== 'function'){
throw new TypeError('Error')
}
// 获得context的参数
const args = Array.from(arguments).slice(1)
// 保留当前的this
const _this = this
// 因为绑定this后不直接调用,所以myBind的返回一个函数
// 这样即可在需要调用时,再调用函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
// 如果当前函数的this指向的是构造函数(F)中的this 则判定为new 操作
if(this instanceof F){
// 对于 new 的情况来说,不会被任何方式改变 this
return new _this(...args,...arguments)
}else{
// 这里采用了apply
return _this.apply(context,args.concat(...arguments))
}
}
}
// 普通函数
function print(){
console.log(this.name);
}
let obj = { name:'XXXX' }
// ...args 为 1, 2, 3
let F = print.myBind(obj,1,2,3)
F() // console.log ('XXXX')
let obj1 = new F(ssss)
// new 的方式调用 bind 参数输出换做 [...arguments]
// ...arguments 为 ssss
console.log(obj1) // 返回print对象
// 对象调用
const object ={ a: 'xxxx' }
let F1 = object.myBind(obj, 1,2,3)
F1()
/*
"TypeError: object.myBind is not a function
*/
虽然实现了bind,但是目前的方案依旧使用了apply,需要实现一次myApply
当new的时候,发生了什么
实际中的new F()其实相当于创建了F()一个新实例,使用的构造函数是F(),它只为新对象定义了【默认的】属性和方法。
新的对象,其实是_this构造出的,它的prototype指向的是_this
手写apply
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
// 不传参默认为 window
context = context || window
context.fn = this
let result
// 判断是否有参数传入
if(arguments[1]){
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
// 删除函数
delete context.fn
// 返回执行结果
return result
}
function print(age,age2,age3){
console.log('name:'+this.name+" "
+ 'age1:' + age1 + ", age2: "+ age2+", age3: "+age3);
}
let obj = { name: 'XXXX' }
print.myApply(obj,[1,2,3])
// "name:XXXX age1:1, age2: 2, age3: 3"
手写call
call和apply的区别在于传参的方式
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
// 不传参默认为 window
context = context || window
context.fn = this
const args = Array.from(arguments).slice(1)
let result
result = context.fn(...args)
// 删除函数
delete context.fn
// 返回执行结果
return result
}
不采用apply实现bind
实现了myApply,我们就可以将之前myBind中的apply方法进行替代,不过如果不能使用ES 6中的方法展开args的话,我们可以考虑使用eval()
eval()函数可计算某个字符串,并执行其中的的 JavaScript 代码。
eval('function Test(a,b,c,d){console.log(a,b,c,d)};Test(1,2,3,4)')
// 等价于执行
function Test(a,b,c,d){
console.log(a,b,c,d)
};
Test(1,2,3,4)
↓↓如法炮制↓↓
let fnStr = 'context.fn('
for (let i = 0; i < args.length; i++) {
fnStr += i == args.length - 1 ? args[i] : args[i] + ',';
}
fnStr += ')'
/得到"context.fn(arg1,arg2,arg3...)"这个字符串,最后用eval执行
eval(fnStr)
全手写版myBind
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
// 不传参默认为 window
context = context || window
context.fn = this
let result
// 判断是否有参数传入
if(arguments[1]){
const args = arguments[1]
let fnStr = 'context.fn('
for (let i = 0; i < args.length; i++) {
fnStr += i === args.length - 1 ? args[i] : args[i] + ',';
}
fnStr += ')'
//得到"context.fn(arg1,arg2,arg3...)"这个字符串,最后用eval执行
result = eval(fnStr)
}else{
result = context.fn()
}
// 删除函数
delete context.fn
// 返回执行结果
return result
}
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const args = Arrary.from(arguments).slice(1)
const _this = this
return function F () {
if (this istanceof F){
// 这里也可以拿eval拼
return new _this(...args, ...arguments)
} else {
// 这里也可以拿eval拼
return _this.myApply(context, args.concat(...arguments))
}
}
}