JavaScript 中的 this 指向

84 阅读3分钟

默认绑定

1. 全局环境下,this 指向 window

console.log(this);

2. 函数独立调用,this 也指向 window

function fn() {
  console.log(this);
}
fn(); // 相当于 window.fn()

3. 被嵌套的函数独立调用时,this 也指向 window

let obj = {
  a: 2,
  foo: function () {
    function test() {
      console.log(this);
    }
    test();
  },
};
obj.foo(); // foo() 中的 this 指向为 obj,但此处输出仍然为 window,因为 test() 相当于独立调用。

// 如何在 test 中拿到 obj 中的 a ?
let obj = {
  a: 2,
  foo: function () {
    let that = this;
    function test() {
      console.log(that.a);
    }
    test();
  },
};
obj.foo();

4. IIFE(自执行函数中内部的 this 也指向 window)

// 情况一
(function () {
  console.log(this);
})();
// 输出 window

// 情况二
function foo() {
  (function test() {
    console.log(this);
  })();
}

let obj = {
  a: 2,
  foo: foo,
};

obj.foo();
// 输出 window

// 如何在 test 中拿到 obj 中的 a ?
function foo() {
  (function test(that) {
    console.log(that);
  })(this);
}

let obj = {
  a: 2,
  foo: foo,
};

obj.foo();

5. 闭包中的 this 仍然指向 window

let obj = {
  a: 2,
  foo: function () {
    return function test() {
      console.log(this);
    };
  },
};

let result = obj.foo();
result();

// 输出 window

// 如何在 test 中拿到 obj 中的 a ?
let obj = {
  a: 2,
  foo: function () {
    let _this = this;
    return function test() {
      console.log(_this.a);
    };
  },
};

let result = obj.foo();
result();

隐式绑定

把对象当做方法来调用,this 指向调用者

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

let obj1 = {
  a: 1,
  foo: foo,
  obj2: {
    a: 2,
    foo: foo,
  },
};

obj1.foo(); // 输出 obj1
obj1.obj2.foo(); // 输出 obj2

隐式丢失

以下几种情况会造成隐式丢失

1. 函数别名

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

let obj = {
  a: 1,
  foo: foo,
};

let bar = obj.foo;
bar(); // 输出 window

2. 参数传递

var _this = "window";

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

function bar(fn) {
  fn();
}

let obj = {
  _this: "obj",
  foo: foo,
};

bar(obj.foo); // 输出 window

3. 内置函数

此类情况主要出现在 setTimeoutsetInterval 中,类似于 “参数传递” 的情况。

setTimeout(function () {
  console.log(this);
}, 1000);

4. 间接调用

  • 隐式绑定,函数当做对象中的方法来使用,内部的 this 指向了该对象
    obj.foo(); // 输出 3
    
  • 将 obj.foo 函数对象赋值给 p.foo 函数,然后立即执行。相当于仅仅是 foo() 函数的立即调用,内部的 this 默认指向了 window
    (p.foo = obj.foo)(); // 输出 2
    
  • 将 obj.foo 赋值给 p.foo 函数,之后 p.foo() 函数再执行,其实是属于 p 对象的方法的指向,this 指向了当前的 p 对象
    p.foo = obj.foo;
    p.foo(); // 输出 4
    

5. 其他情况(以下几种情况,this 都指向 window)

var a = 0;
var obj = {
  a: 1,
  foo: foo,
};
function foo() {
  console.log(this.a);
}
(obj.foo = obj.foo)(); // 输出 0
(false || obj.foo)(); // 输出 0
(1, obj.foo)(); // 输出 0

显式绑定

  • call(),apply(),bind() 把对象绑定到 this 上,叫做显式绑定

    var a = 0;
    function foo() {
      console.log(this.a);
    }
    var obj = {
      a: 2,
    };
    foo(); // 输出 0
    foo.call(obj); // 输出 2
    foo.apply(obj); // 输出 2
    foo.bind(obj)(); // 输出 2
    
  • 硬绑定,显式绑定的一个变种,使得 this 不能再被改变

    var a = 0;
    function foo() {
      console.log(this.a);
    }
    var obj = {
      a: 2,
    };
    var bar = function () {
      foo.call(obj);
    };
    bar(); // 输出 2
    setTimeout(bar, 2000); // 输出 2
    bar.call(window); // 输出 2
    
  • forEach()map()filter()some()every() 等一些内置函数中,在第二个形参的位置可以传入一个对象,改变回调函数中 this 的指向为该对象

    var _this = "window";
    var obj = {
      _this: "obj",
    };
    var arr = [1, 2, 3];
    arr.forEach(function (el, index) {
      console.log(el, index, this);
    }, obj);
    

new 绑定

如果是 new 关键字来真实性函数,相当于构造函数来实例化对象,那么内部的 this 指向了当前实例化的对象

function fn() {
  console.log(this);
  return;
}
var f1 = new fn(); // 输出 fn
function fn2() {
  console.log(this);
  return {
    name: "fn2",
  };
}
var f2 = new fn2(); // 输出 fn2

严格模式下 this 的指向

  1. 独立调用的函数内部的 this 指向 undefined

    function foo() {
      "use strict";
      console.log(this);
    }
    foo(); // 输出 undefined
    
  2. 函数 apply()call() 内部的 this 始终是它们的第一个参数

    var color = "red";
    function showColor() {
      "use strict";
      console.log(this);
      console.log(this.color);
    }
    showColor.call(undefined); // 输出 undefined