[手写系列]手写bind/call/apply

353 阅读4分钟

bind apply call 都应用于改变this的指向,三者的区别在于: bind重新绑定了函数的this,不会立即执行函数 applycall重新绑定了函数的this,并且会立即执行该函数

手写bind

根据MDNbind()方法创建一个新的函数, 当这个新函数被调用时其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

callapply的区别在于传参的方式

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))
        }
     }
}