apply、call、bind
Function.prototype
上包含apply()
、call()
、bind()
方法。
也就是说每个函数都包含这三个由原型上获得的方法。
apply
apply()
方法接收两个参数:一个是在其中运行函数的作用域,另一个是数组,
这个数组也可以是类数组,比如arguments对象。
用法不赘述,只讲它的手写实现。
首先我们要搞清楚apply的作用是什么:
它改变了函数中的this指针指向
先看看它的参数,
一个运行函数的作用域
- 这个作用域,就是用来改变我原本函数内的this指针,
如果我传入一个obj作为第一个参数,那么就是把this指针指向了obj
- 这个作用域,就是用来改变我原本函数内的this指针,
一个数组或arguments对象
- 这个数组或者arguments对象,就是要传入原本函数的参数,是什么就填什么。
那么重点就要落到这个作用域了,我们要怎么改变函数中的this呢,那就是让它成为这个作用域(也就是传入的对象)的方法然后重新调用
比如传入的是对象我们作为context,在函数中this原本指向的是函数本身,
那我们为context增加一个和函数本身一样的方法再重新去调用,这时候函数里的this就会变成context了
同时要对参数进行处理
把 (obj, [argument1, argument2, ...])
也就是apply()
方法的arguments,去掉第一个参数obj形成新的数组,
然后把这个数组直接传入临时函数作为参数就好了
重点代码:context[fn] = this;
const args = [...arguments].slice(1)[0];
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)[0]; //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()
的作用更强大
- 它接受一个作用域,并且将函数绑定在这个作用域上,返回一个新的函数
- 接受多个参数
- 支持一次柯里化形式传参 如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()
方法 - 将第一个参数和第二个参数拼接在了一起
- 这里的柯里化形式传参只支持分两步传参