绑定规则

this的绑定规则总结起来总共有四种:

  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定

默认绑定

这条规则是无法应用其它绑定时的默认规则,思考以下代码:

1
2
3
4
5
6
    function foo() {
        console.log(this.a)
    }

    var a = 2
    foo() // 2

foo()是直接使用不加任何修饰的函数引用进行调用的,因此只能应用默认绑定,在非严格模式下this默认绑定到了全局对象

严格模式下则被绑定到undefined

1
2
3
4
5
6
7
    function foo() {
        "use strict"
        console.log(this.a)
    }

    var a = 2
    foo()  // TypeError

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,

思考下面代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    function foo() {
        console.log(this.a)
    }

    var obj = {
        a: 2,
        foo: foo
    }

    obj.foo() // 2

调用位置obj.foo()会使用obj上下文来引用函数,因此可以说函数被调用时obj对象"拥有"或者"包含"它,当函数引用有 上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象。

对象引用链只有最后一层在调用位置起作用, 举例来说

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    function foo() {
        console.log(this.a)
    }

    var obj2 = {
        a: 42,
        foo: foo
    }

    var obj1 = {
        a: 2,
        obj2: obj2
    }

    obj1.obj2.foo()  // 42

隐式丢失问题

一个常见的问题就是被隐式绑定的函数会丢失绑定对象。

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    function foo() {
        console.log(this.a)
    }

    var obj = {
        a: 2,
        foo: foo
    }

    var bar = obj.foo
    var a = "global"

    bar() // global

虽然bar引用的是obj.foo,但是bar实际上引用的是foo函数本身,因此,此时的bar()是一个不带 任何修饰的函数调用,所以foo应用了默认绑定。

显式绑定

JavaScript中所有的函数都具有的方法callapply

通过bar.call(obj)可以强制将foothis绑定到obj

如果callapply被传入了一个原始值(字符串类型,布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转换成 对象的形式(也就是 new String(…)…),这通常称作"装箱"

硬绑定

硬绑定的典型应用场景就是创建一个包裹函数,负责接收函数参数并返回值

1
2
3
4
5
6
    ...
    var bar  = function() {
        return foo.apply(obj, arguments)
    }

    bar()  // 2

或者构建一个简单的绑定辅助函数

1
2
3
4
5
    function bind(fn, obj) {
        return function() {
            fn.apply(obj, arguments)
        }
    }
ES5中内置的bind方法

bind()会返回一个硬绑定的函数,无论在哪里调用(除了使用new),这个this以及传入的arguments都会生效

API调用上下文

许多函数提供了绑定this的参数,比如forEach(callback, thisArg)

1
    [1,2,3].forEach(foo, obj)

new绑定

思考下面代码:

1
2
3
4
5
6
    function foo(a) {
        this.a = a
    }

    var bar = new foo(2)
    console.log(bar.a) // 2

使用new来调用foo时,构造的新对象会被绑定到foo(...)调用中的this

优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

绑定例外

被忽略的this

如果把null, undefined,作为this的绑定对象传入call``apply或者bind,这些值在调用时会被 忽略,实际应用的是默认绑定规则。

间接引用

经典的例子

1
2
3
4
5
6
    var a = 2
    var o = { a: 3, foo: foo }
    var p = { a: 4 }

    o.foo()  // 3
    (p.foo = o.foo)()  //2

赋值表达式p.foo = o.foo返回的是目标函数的引用,因此调用位置是foo(),这里会应用默认绑定。

箭头函数

箭头函数不使用this的四种标准规则,而是根据外层的作用域来决定this,箭头函数的绑定无法修改,new 也不行。