调用位置:函数在代码中被调用的 位置(而不是声明的位置)。
调用栈:到达当前执行位置所调用的所有函数。

默认绑定

独立的函数调用

  1. function foo() {
  2. console.log( this.a )
  3. }
  4. var a = 2;
  5. foo() // 2

strict mode 模式下 this 绑定到 undefiend,在 non-strict mode 下绑定到 全局对象。

隐式绑定

当 foo() 被调用时,它的落脚点指向 obj 对象。当函数有引用上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a: 2,
  6. foo: foo
  7. }
  8. obj.foo() // 2

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj2 = {
  5. a: 42,
  6. foo: foo
  7. }
  8. var obj1 = {
  9. a: 2,
  10. obj2: obj2
  11. }
  12. obj1.obj2.foo() // 42

显式绑定

使用 call、bind、apply 来绑定 this 称为显式绑定。

apply 和 call 的区别只是传入的参数不同。

  1. function foo() {
  2. console.log(this.a, this.b)
  3. }
  4. let obj = {
  5. a: 2,
  6. b: 3
  7. }
  8. foo.call(obj) // 2, 3
  9. foo.apply(obj) // 2, 3

bind 返回一个绑定了指定 this 的函数(硬绑定)

  1. var obj = {
  2. a: 2
  3. }
  4. var bar = foo.bind(obj)
  5. var b = bar(3) // 2 3
  6. console.log(b) // 5

new 绑定

new 自动执行以下操作:

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
  1. function foo(a) {
  2. this.a = a
  3. }
  4. var bar = new foo(2)
  5. console.log(bar.a) // 2

优先级

显式绑定高于隐式绑定

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj1 = {
  5. a: 2,
  6. foo: foo
  7. }
  8. var obj2 = {
  9. a: 3,
  10. foo: foo
  11. }
  12. obj1.foo() // 2
  13. obj2.foo() // 3
  14. obj1.foo.call(obj2) // 3
  15. obj2.foo.call(obj1) // 2

new 绑定高于隐式绑定

  1. function foo(something) {
  2. this.a = something
  3. }
  4. var obj1 = {
  5. foo: foo
  6. }
  7. var obj2 = {}
  8. obj1.foo(2)
  9. console.log(obj1.a) // 2
  10. obj1.foo.call(obj2, 3)
  11. console.log(obj2.a) // 3
  12. var bar = new obj1.foo(4)
  13. console.log(obj1.a) // 2
  14. console.log(bar.a) // 4

new 绑定高于显示绑定

  1. function foo(something) {
  2. this.a = something
  3. }
  4. var obj1 = {}
  5. var bar = foo.bind(obj1)
  6. bar(2)
  7. console.log(obj1.a) // 2
  8. var baz = new bar(3)
  9. console.log(obj1.a) // 2
  10. console.log(baz.a) // 3

判断规则

  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
  2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。

题外

硬绑定这种方式可以把 this 强制绑定到指定的对象(除了使用 new 时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。

如果可以给默认绑定指定一个全局对象和 undefined 以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。

  1. Function.prototype.softBind = function(obj) {
  2. var fn = this
  3. var curried = [].slice.call(arguments, 1)
  4. var bound = function bound() {
  5. // !this 考虑在 strict-mode 下运行
  6. return fn.apply(
  7. !this ||
  8. (typeof window !== 'undefined' && this === window) ||
  9. (typeof global !== 'undefined' && this === global)
  10. ? obj
  11. : this,
  12. curried.concat.apply(curried, arguments)
  13. )
  14. }
  15. bound.prototype = Object.create(fn.prototype)
  16. return bound
  17. }
  18. function foo() {
  19. console.log('name: ' + this.name)
  20. }
  21. var obj = { name: 'obj' }
  22. var fooOBJ = foo.softBind(obj)
  23. fooOBJ() //name:obj

参考

[1] 你不知道的js(上)第二部分第二章