你的东西我也能用,-关键字this到底指向谁?

403 阅读3分钟

为什么要用this?

以一个优雅的方式隐式传递了一个对象的引用,提高了代码的复用性。

this绑定

  • 默认绑定: 函数在哪个词法环境中生效,函数的this就指向哪里(词法环境:函数声明在哪里,它的词法环境就是哪里)。

我们来看段代码:

function foo() {
    console.log(this.a);
}
function bar() {
    var a = 2
    foo()
}
function baz() {
    var a = 3
    bar()
}
var a = 1
baz()

在这段代码中,函数baz的执行带来的函数bar的调用,函数bar的执行带来了函数foo的调用,然后执行foo。也就是函数foo在bar中生效,bar又在函数baz中生效,而函数baz和函数bar的词法环境在全局,也就是函数的this最终会指向全局,执行结果为a=1

  • 隐式绑定:当函数被某个对象拥有时,触发隐式绑定规则。会把函数中的this绑定到对象当中。 直接来看代码:
    function foo() {
        console.log(this.a);
    }

    var obj = {
        a: 2,
        foo: foo
    }
    obj.foo()

上面代码就触发了this的隐式绑定;函数foo的引用给到了对象obj,当函数被obj调用的时候,此时this就绑- 多次引用函数,最终函数调用前没有上下文对象,就会隐式丢失,从而走默认绑定定到了对象obj中,打印出来的结果为 2

  • 隐式丢失:多次引用函数,最终函数调用前没有上下文对象,就会隐式丢失,从而走默认绑定

可能有些抽象,我们直接看代码:

    function foo() {
        console.log(this.a);
    }

    var obj = {
        a: 2,
        foo: foo
    }
    var bar = obj.foo
    bar()

这段代码多次引用了函数,最后函数bar相当于函数foo了,词法环境变为了全局,最终打印出来的结果为 undefined

  • 显示绑定: call 修改函数的this指向,接受零散的参数
function foo(x, y) {
    console.log(this.a, x + y);
}

var obj = {
    a: 2
}

foo.call(obj,1,2)

像这样,通过call可以显式地让this访问到对象obj里面的值,打印出来的值为 2 , 3;好用且简便。

  • apply 修改函数的this指向,参数以数组形式接受

apply的用法和call的用法相似,只不过参数是以数组的形式接受 foo.apply(obj,[1,2])

  • bind 修改函数的this指向,会返回一个新的函数,接受零散的参数
function foo(x, y) {
    console.log(this.a, x + y);
}

var obj = {
    a: 2
}

var bar = foo.bind(obj, 2, 3)
bar()

bind的效果和call,bind一样,都会修改函数的this指向,但是它会返回出一个新的函数,然后接受零散参数。上面代码参数也可以写在返回出的函数bar()中,即bar(2,3)

注意一下:如果使用这三种方法修改this指向,括号里没有指定。例:foo.call()或foo.call(null)函数中的this就指向window

箭头函数

在es6中新的一种函数定义方式,函数function foo(a, b) { return a + b } 用箭头函数可以定义为: var bar = (x, y) => { return x + y; }

在箭头函数中本身没有this这个概念,写在其内部的this,也是其外部的非箭头函数的this。

扩展(call的实现原理)

我们知道通过call能修改函数的this指向,那它到底是怎么实现的呢? 我们来看代码:

function foo(x, y) {
    console.log(this.a);
}

var obj = {
    a: 2
}
Function.prototype.myCall = function (context) {
    //拿到调用myCall的那个函数
    //把它引用到context中
    //再让context调用该函数

    let args = [...arguments].slice(1)//[4,5]

    context.fn = this
    let res = context.fn(...args)
    delete context.fn
    return res
}


foo.myCall(obj, 4, 5)

call的实现原理其实就是借助了this的隐式绑定规则;看上面代码,我们在函数原型上定义了一个myCall函数属性,这使得函数都能使用这个方法,然后在myCall中将传入进来的实参进行分解再传入函数foo中;函数foo引用到了对象context中,就触发了隐式绑定,再调用函数foo。这就是call方法修改函数this指向的原理。