【call、apply】
call、apply、bind作用是【改变函数执行时的上下文】,简而言之就是改变函数运行时的this指向。
- call: 接受多个参数,第一个参数是函数的上下文 this,后面的参数是函数本身需要的参数。形式:
fn.call(obj, param1, param2...) - apply: 接受两个参数,第一个参数是函数的上下文 this,后面的参数是一个数组形式传入的。形式:
fn.apply(obj, [params, param2...])
共同点:只是临时改变this指向一次,改变this指向后原函数会【立即执行】。当obj为空时,this为window或者undefined。
示例
var name="lucy";
const obj={
name:"martin",
say: function () {
console.log(this.name, arguments);
}
};
obj.say(); //立即输出:martin,this指向obj对象
setTimeout(obj.say, 5000); //5秒后输出:martin,this指向obj对象
setTimeout(obj.say.call(obj, 'call之后'), 5000); //立即输出:martin,this指向window对象
实现
call、apply
- 判断调用对象是否为函数
- 判断传入的上下文是否存在,不存在则设置为window
- 处理传入的参数,获取除第一个参数之外的其余参数
- 将函数作为上下文对象的一个属性
- 使用上下文来调用函数,并保存返回结果
- 从上下文中删除新增的属性
- 返回结果
【call实现】
Function.prototype.myCall = function(context){ // 只有第一个参数,值对应第一个this
if(typeof this !== 'function') return // this是mycall的调用者
context = context || window // 如果设置的this是null或者undefined,则设置this为Window
let result = null
context.fn = this // 给上下文添加这个属性, 如果context中已经有了fn这个属性,可以随机生成一个属性名
// call 方式
let params = [...arguments].slice(1) // 取出参数
result = context.fn(...params) // 执行函数
delete context.fn // 从上下文中删除增加的属性
return result
}
obj.say.myCall(null, 'call之后'); //call lucy,this指向window对象
【apply实现】
Function.prototype.myApply = function(context){ // 只有第一个参数,值对应第一个this
if(typeof this !== 'function') return // this是mycall的调用者
context = context || window // 如果设置的this是null或者undefined,则设置this为Window
let result = null
context.fn = this // 给上下文添加这个属性, 如果context中已经有了fn这个属性,可以随机生成一个属性名
context.fn(...params) // 执行函数
if(arguments[1]){
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
delete context.fn // 从上下文中删除增加的属性
return result
}
obj.say.mycall(obj, ['call之后']); //Martin,this指向obj对象
obj.say.mycall(null, ['call之后']); //lucy,this指向window对象
【bind】
- 第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
- 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
- 支持柯里化形式传参
- 返回一个绑定this的函数
- 【实现步骤】
- 保存调用函数的引用,获取剩下传入的参数值
- 创建一个函数返回
- 函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的this给apply调用,其余情况都传入指定的上下文对象
实现
定义一个对象和一个函数
var name = 'windowName'
function bindFn(name, second, third){
console.log('bindFn', this.name + ' '+ second + ' ' + third)
}
let bindObj = {
'name': 'bind'
}
// 原生方法
let bindFn2 = bindFn.bind(bindObj)
bindFn2(1,2,3) // bindObj对象,输出:bindFn bind 2 3
bindFn(1,2) // bindFn本身,输出:bindFn windowName 2 undefined
【bind实现】
Function.prototype.myBind = function(context){
if(typeof this !== 'function') return // 处理不是function调用的情况
context =context || window // 处理this是null的情况
let params = [...arguments].slice(1) // 获取参数,第一次绑定bind的时候可能会传递参数
let fn = this // 保存闭包要使用的this, 保存当前函数的引用
function fBound(){
// 因为支持柯里化形式传参,所以需要再次存储变量
let newParams = [...arguments] // 在调用的时候新传入的参数
// 保证后面执行的时候还是fn进行调用的,且区分是否用了new
return fn.apply(this instanceof fBound ? this: context, params.concat(newParams))
}
return fBound
}
let bindFn3 = bindFn.myBind(bindObj, 'before bind', 'second')//返回的是一个函数,可以先绑定一些参数
bindFn3(777) // 执行函数 bindFn bind second 777
let instance = new bindFn() // bindFn undefined undefined undefined