阅读 404

浅谈apply、call、bind及其ES6手写实现

apply、call、bind

Function.prototype上包含apply()call()bind()方法。

也就是说每个函数都包含这三个由原型上获得的方法。

apply

apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是数组,
这个数组也可以是类数组,比如arguments对象。
用法不赘述,只讲它的手写实现。

首先我们要搞清楚apply的作用是什么:
它改变了函数中的this指针指向

先看看它的参数,

  1. 一个运行函数的作用域
  • 这个作用域,就是用来改变我原本函数内的this指针, 如果我传入一个obj作为第一个参数,那么就是把this指针指向了obj
  1. 一个数组或arguments对象
  • 这个数组或者arguments对象,就是要传入原本函数的参数,是什么就填什么。

那么重点就要落到这个作用域了,我们要怎么改变函数中的this呢,那就是让它成为这个作用域(也就是传入的对象)的方法然后重新调用

比如传入的是对象我们作为context,在函数中this原本指向的是函数本身,
那我们为context增加一个和函数本身一样的方法再重新去调用,这时候函数里的this就会变成context了

同时要对参数进行处理 把 (obj, [argument1, argument2, ...])
也就是apply()方法的arguments,去掉第一个参数obj形成新的数组,
然后把这个数组直接传入临时函数作为参数就好了

重点代码:
context[fn] = this;
const args = [...arguments].slice(1);
const result = context[fn] (...args);

下面是完整代码:

Function.prototype.myApply = function(context){

    if(typeof context !== 'object'){
        //作用域对象应为一个object
        throw new TypeError('TypeError, Expected for object');
    }

    //防止传入对象为null或无传入对象
    context = context || window;

    //使用ES6的Symbol确保不会重写作用域的其他属性
    const fn = Symbol();

    //把当前函数传址给context[fn]指针
    context[fn] = this;

    //获取本应传入原函数的参数
    const args = [...arguments].slice(1);    //ES6的扩展运算符可以使arguments这样的类数组转换成数组

    //从目的作用域中调用函数并获取返回值
    const result = context[fn](args);
    
    //删除这个临时指针
    delete context[fn];
    
    return result;
}
复制代码

call

call()apply()相似,都是改变函数的作用域(也就是this所指向),
但是不同的是它必须把传递给函数的参数逐个列举出来

比如apply()是这样用的

  • exampleFunction.apply(obj, [argument1, argument2, ...])
  • exampleFunction.apply(obj, arguments)

call()是这样的

  • exampleFunction.call(obj, argument1, argument2, ...)

实现也很简单,把 (obj, argument1, argument2, ...) 也就是bind()方法的arguments,去掉第一个参数obj形成新的数组,
然后把这个数组展开传入临时函数作为参数就好了

重点代码:
context[fn] = this;
const args = [...arguments].slice(1);
const result = context[fn] (...args);

下面是完整代码:

Function.prototype.myCall = function(context){

    if(typeof context !== 'object'){
        //作用域对象应为一个object
        throw new TypeError('TypeError, Expected for object');
    }

    //防止传入对象为null或无传入对象
    context = context || window;

    //使用ES6的Symbol确保不会重写作用域的其他属性
    const fn = Symbol();

    //把当前函数传址给context[fn]指针
    context[fn] = this;

    //获取本应传入原函数的参数
    const args = [...arguments].slice(1);    //ES6的扩展运算符可以使arguments这样的类数组转换成数组

    //从目的作用域中调用函数并获取返回值
    const result = context[fn](...args);    //还是用ES6的扩展运算符展开数组
    
    //删除这个临时指针
    delete context[fn];
    
    return result;
}
复制代码

bind

bind()的作用更强大

  1. 它接受一个作用域,并且将函数绑定在这个作用域上,返回一个新的函数
  2. 接受多个参数
  3. 支持柯里化形式传参 如fn(1)(2)

下面是完整代码:

Function.prototype.myBind = function(context) {
	
	//保存函数本身
	let fn = this;
	
	// 可以支持柯里化传参,保存参数
	let arg = [...arguments].slice(1)
	
	// 返回一个函数
	return function() {
		
		//将原参数与返回函数的新参数拼接在一起
		//支持柯里化形式传参
		let newArg = arg.concat([...arguments])
		
		//返回函数
		return fn.myApply(context, newArg)
	}
}
复制代码
  • 重用了上面写的myApply()方法
  • 将第一个参数和第二个参数拼接在了一起
  • 这里的柯里化形式传参只支持分两步传参

这是笔者学习别人经验结合自己想法总结的,可能漏洞百出

如有错误请指正,笔者会及时修改