说一下this,实现apply、call


highlight: a11y-dark

理解this

在ES5中,this的指向始终坚持一个原理:“this永远指向最后调用它的那个对象”,切记这句话。下面看几个例子。

例一

var obj = {
    name: 'zhangsan',
    say: function() {
        console.log(this.name);
    }
}

obj.say() // zhangsan

最基本的使用,函数say是obj调用的,函数中的this指向obj,所以打印的是zhangsan

例二

var name = 'lisi'
var obj = {
    name: 'zhangsan',
    say: function() {
        console.log(this.name);
    }
}

var func = obj.say
func() // lisi

把say赋给变量func,然后调用func,前面没有调用对象,那么此时的调用对象就是全局对象window,因此this指向window,所以打印lisi

例三

var name = 'lisi'
var obj = {
    name: 'zhangsan',
    say: function() {
        console.log(this.name);
    }
}

window.obj.say() // zhangsan

this永远指向最后调用它的那个对象,这里虽然前面加了个window,但是最后调用者还是obj,因此this指向obj。

可以看出:this的指向并不是在创建的时候就可以确定的。在ES5中,this永远指向最后调用它的那个对象

例四

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function() {
        console.log(this.name); // zhangsan
        func()
        function func() {
            console.log(this.name); // lisi
        }
    },

}

obj.say()

say的调用者是obj,say函数内部的this就指向obj,因此第一次打印zhangsan。再调用func,此时func的调用者是window,func函数内部的this就指向window,因此第二次打印lisi

如何改变this指向

改变this指向有以下几种方法:

  • 使用ES6的箭头函数
  • 把this存一下然后调用:_this=this
  • 使用apply、call、bind改变this指向

箭头函数

ES6中的箭头函数可以避免ES5中很多this的坑。箭头函数中的this始终指向函数定义时的this,而非执行时。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一次非箭头函数的this

例五

var name = 'lisi'

var obj = {
    name: 'zhangsan',

    func1: function() {
        console.log(this.name);
    },

    func2: function() {
        setTimeout(function() {
            this.func1()
        }, 1000)
    }
}

obj.func2() // this.func1 is not a function

如上代码,调用func2报错,因为最终调用setTimeout的对象是window,函数里的this指向window,而window中并没有func1,所以报错。

这里补充个知识点:匿名函数的this永远指向window。this永远指向最后调用它的那个对象。我们来找找最后调用匿名函数的对象是哪个,很尴尬,匿名函数没有名字,所以是没有办法被其他对象调用的,所以只能在window下执行。因此匿名函数的this始终指向window。

var name = 'lisi'

var obj = {
    name: 'zhangsan',

    func1: function() {
        console.log(this.name);
    },

    func2: function() {
        // 使用箭头函数
        setTimeout(() => {
            this.func1()
        }, 1000)
    }
}

obj.func2() // zhangsan

使用箭头函数即可解决问题,此时this就是func2函数中的this,而func2是obj调用的,因此this实际指向obj。

_this=this

简单把this存一下即可

例六

var name = 'lisi'

var obj = {
    name: 'zhangsan',

    func1: function() {
        console.log(this.name);
    },

    func2: function() {
        // this存一下在匿名函数里使用
        var _this = this
        setTimeout(function() {
            _this.func1()
        }, 1000)
    }
}

obj.func2() // zhangsan

apply、call

apply和call都是用来改变this指向的,作用相同,用法稍微有点不同。

MDN中apply定义如下:

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

用法:

func.apply(thisArg, ArrayArg)

call定义如下:

call()  方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

用法:

func.call(thisArg, arg1, arg2, ...)

详细用法见MDN

例七

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function(arg) {
        console.log(this.name, arg);
    }
}

var func1 = obj.say // lisi 1
func1(1)

此时this指向window,打印lisi 1。如果我们现在仍想要this指向obj,那么就可通过apply改变this:

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function(arg) {
        console.log(this.name, arg);
    }
}

var func1 = obj.say // zhangsan 2
func1.apply(obj, [2])

通过apply改变this指向,指向obj。然后通过一个数组传入调用时的参数。这就是apply用法的简单实例。call用法与其类似,只不过不是通过数组传参,而是需要显式传入多个参数。

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function(arg) {
        console.log(this.name, arg);
    }
}

var func1 = obj.say // zhangsan 3
func1.call(obj, 3)

手动实现apply、call

理解了this、this指向、apply、call后我们可以思考一下如何手动实现apply、call。

手动实现apply

Function.prototype._apply = function(context) {
    // context是要绑定的this
    // this是调用的函数即func1

    if (typeof this !== "function") {
        throw new Error('只能在函数上调用')
        return
    }
    
    context = context || window

    let result = null
    // 把需要执行的函数赋给context,由context调用即可
    context.fn = this

    if (arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }

    delete context.fn
    return result
}

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function(arg) {
        console.log(this.name, arg);
    }
}

var func1 = obj.say // zhangsan 1
func1._apply(obj, [1])

总结步骤如下:

  • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  • 将函数作为上下文对象的一个属性。

  • 判断参数值是否传入

  • 使用上下文对象来调用这个方法,并保存返回结果。

  • 删除刚才新增的属性

  • 返回结果

手动实现call

Function.prototype._call = function(context) {
    // context是要绑定的this
    // this是调用的函数即func1

    if (typeof this !== "function") {
        throw new Error('只能在函数上调用')
        return
    }
    
    context = context || window

    const arg = Array.from(arguments).splice(1)

    let result = null
    // 把需要执行的函数赋给context,由context调用即可
    context.fn = this

    result = context.fn(...arg)

    delete context.fn
    return result
}

var name = 'lisi'

var obj = {
    name: 'zhangsan',
    say: function(arg) {
        console.log(this.name, arg);
    }
}

var func1 = obj.say // zhangsan 2
func1._call(obj, 2)

总结如下:

  • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  • 处理传入的参数,截取第一个参数后的所有参数。

  • 将函数作为上下文对象的一个属性。

  • 使用上下文对象来调用这个方法,并保存返回结果。

  • 删除刚才新增的属性。

  • 返回结果

参考链接:

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容