JavaScript 系列之 this(一)

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战数组

每一个函数的 this 是在调用时被绑定的,彻底取决于函数的调用位置,调用位置就是函数在代码中被调用的位置(而不是声明的位置)。markdown

1、绑定规则

this 永远指向最后调用它的那个对象app

关于 this 的误解:this 指向函数自身、this 指向函数做用域。ide

  • this 是在运行时绑定的,彻底取决于函数的调用位置
  • 并非在编写时绑定的,它的上下文取决于函数调用时的各类条件。

⚠️:做用域链依据词法做用域,this 依据动态做用域。函数

1.1 默认绑定

当函数直接使用不带任何修饰的函数引用进行调用时,则绑定到全局对象或 undefined(严格模式时)上oop

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

var a = 2;
foo();//2
复制代码

1.2 隐式绑定

隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象post

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

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

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

obj1.obj2.foo();//42
复制代码

注意:对象属性引用链中只有上一层或者说最后一层在调用位置中起做用ui

1.2.1 隐式丢失

被隐式绑定的函数会丢失绑定对象,就是说会默认绑定,从而把 this 绑定到全局对象或 undefined 上this

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

var obj = {
  a: 42,
  foo: foo
};

var bar = obj.foo; // 函数别名
var a = "oops";

bar(); // "oops"
复制代码
// 传入回调函数时
function foo() {    
  console.log(this.a);
}

function doFoo(fn) {
  // fn 其实引用的是 foo
  fn(); // <-- 调用位置
}

var obj = {
  a:42,
  foo: foo
};

var a = "oops";
doFoo(obj.foo);// oops
复制代码
// setTimeout()也会丢失 this 绑定
function foo() {    
  console.log(this.a);
}

var obj = {
  a:42,
  foo: foo
};

var a = "oops";
setTimeout(obj.foo, 100);
复制代码

回调函数丢失 this 绑定很是常见编码

1.3 显式绑定

利用 call(),apply()

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

var obj = {
  a:2
};

foo.call(obj); //2
复制代码

区别在于第二个参数,call()是依次输入参数,而apply()是一个参数数组

1.3.1 硬绑定

首先建立函数 bar(),并在它的内部手动调用了 foo.call(obj),所以强制把 foo 的 this 绑定到了 obj,不管以后如何调用函数 bar,它总会手动在 obj 上调用 foo。这种绑定是一种显示绑定,称为硬绑定。

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

var obj = {
  a:2
};

var bar = function() {
  foo.call(obj);
}

bar(); // 2
setTimeout(bar, 100); // 2
bar.call(window); // 2
复制代码

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

function foo(something) {    
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2
}

var bar = function() {    
  return foo.apply(obj, arguments);
};

var b = bar(3); // 2 3
console.log(b);// 5
复制代码

进而咱们能够建立一个能够重复使用的函数:

function foo(something) {    
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2
}

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  }
}

var bar = bind(foo, obj);

var b = bar(3); // 2 3
console.log(b);// 5
复制代码

以上也是 ES5 内置 Function.prototype.bind 的使用方法,bind() 会返回一个硬编码的新函数,它会把你指定的参数设置为 this 的上下文并调用原始函数。

1.3.2 API 调用的上下文

第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一种可选参数,一般被称为“上下文”。

function foo(el){
  console.log(el, this.id);
}

var obj = {
  id: 'a'
}

// 调用 foo 时把 this 绑定到 obj
[1, 2, 3].forEach(foo, obj);
// 1 a 2 a 3 a
复制代码

1.4 new 绑定

实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

function foo(a) {    
  this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2
复制代码

使用 new 来调用 foo 时,咱们会构造一个新对象并把它绑定到 foo() 调用中的 this 上。

1.5 规则补充

1.5.1 优先级

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

1.5.2 绑定例外

如下应用的是默认绑定规则

call(null/undefined)
apply(null, [2,3])
bind(null2)
复制代码

1.5.3 this 词法

ES6 中的箭头函数不会使用四条绑定规则,而是根据当前的词法做用域来决定 this。

箭头函数继承外层函数调用的 this 绑定,这其实和 ES6 以前代码中的 self = this 机制同样。

function foo() {
  // 返回一个箭头函数
  return (a) => {
    // this 继承自 foo()
    console.log(this.a);
  };
}

var obj1 = {
  a: 2
};
var obj2 = {
  a: 3
};

var bar = foo.call(obj1);
bar.call(obj2); // 2
复制代码

foo() 内部建立的箭头函数会捕获调用时 foo() 的 this。因为 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定没法修改。

箭头函数实际上是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在下面这个例子中,由于调用 a 符合前面代码中的第一个状况,因此 this 是 window。而且 this 一旦绑定了上下文,就不会被任何代码改变。

function a() {
  return () => {
    return () => {
      console.log(this)
    }        
  }
}

console.log(a()()()) // window
复制代码
相关文章
相关标签/搜索