总结一句话:改变函数内部的 this 指向,并执行函数。
call、apply 是 Function 构造函数原型对象上的方法,所有的函数(包括call)都可以调用 call 和 apply。
call
先看一下原生call的效果(运行在浏览器环境)
let context = {
name: 'z'
}
// 全局环境, 用let变量不会挂在window上
var name = 'j'
function say(age,sex){
console.log(this.name+age+sex)
}
say.call(context,12,1) // z121
say.call(null,18,0) // j180
我们稍加分析,将成call做的事情拆解为三步:
- 先把方法say挂在call的第一个参数,context的属性下(context指上下文,this所指向的新执行环境)
- 执行 context.say(age, sex),并传入参数
- 删除 context.say,不能侵入性修改原数据
按照这三步我们复现一下 写一个myCall
Function.prototype.myCall = function(context) {
// 设置一个参数默认值,浏览器环境默认window,node环境是global
context = context ? context : window;
// 原函数调用call函数,所以this指向原函数本身,即 say
context.fn = this
// 获取除掉第一个context外的参数
let args = []
for(let i=1;i<arguments.length;i++){
args.push('arguments['+i+']')
}
// 调用函数
var result = eval('context.fn('+ args +')')
// 删除增加的属性
delete context.fn
// 返回执行结果
return result
}
执行一下
let context = {
name: 'z'
}
// 全局环境, 用let变量不会挂在window上
var name = 'j'
function say(age,sex){
console.log(this.name+age+sex)
}
say.myCall(context,12,1) // z121
say.myCall(null,18,0) // j180
完美复现,okkkk~~~
解释一下eval问题,之所以这样写,是因为之前还没有结构啊,Array.from类数组转数组语法什么的。eval遇到数组会调用 args.toString(),解析后是这样的
context.fn(arguments[1], arguments[2], ...)
apply
apply与call基本相同,只是额外参数是数组形式
Function.prototype.myApply = function (context,args){
context = context ? context : window
context.fn = this
// 判断是否传递额外参数,如果没有直接执行函数即可
if(!args) return context.fn()
var result = eval('context.fn('+ args +')')
delete context.fn
return result
}
bind
bind下篇文章讲,关注一下哈哈~~~