阅读 2447

答应我,搞懂call、apply好嘛

总结一句话:改变函数内部的 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做的事情拆解为三步:

  1. 先把方法say挂在call的第一个参数,context的属性下(context指上下文,this所指向的新执行环境)
  2. 执行 context.say(age, sex),并传入参数
  3. 删除 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下篇文章讲,关注一下哈哈~~~

文章分类
前端
文章标签