重点2:this 指向

182 阅读3分钟

优先级:箭头函数 > new > 显式绑定 call / apply / bind > 隐式绑定 > 默认绑定。

this 指向在函数定义的时候是确定不了的,只有函数被调用时才能确定!

this指向

  • 先看 this 是在箭头函数还是普通函数中,如果是箭头函数,那么 this 指向就等于定义时的上层作用域中的 this 指向

  • 如果在普通函数中:

    • 用 new 调用函数生成实例时,函数中的 this 指向的是新生成的实例对象。
    • 显式绑定,this 指向参数列表里给出的对象。
    • 隐式绑定,当调用函数时左侧有对象,那么 this 就指向该对象
    • 默认绑定,直接调用函数时,this指向 window。严格模式下,全局环境 window,函数环境 undefined。

一、默认绑定

当函数是单纯作为独立的函数直接调用时,this 执行全局对象 window。注意:严格模式下,全局环境下的 this 指向 window,函数环境下的 this 指向 undefined

浏览器环境中全局对象是 window,Node.js 环境下是 global 对象。 ES5 之前,顶层对象的属性和全局变量是完全等价的,但 ES6 规定:(1)var 命令和 function 命令声明的全局变量依旧是顶层对象的属性;(2)let、const、class 命令声明的全局变量,不属于顶层对象的属性。

function outerFunc() {
  console.log(this);  // { x: 1 }
  function func() {
    console.log(this);  // Window
  }
  func();  // 直接调用!
}
outerFunc.bind({ x: 1 })();

二、隐式绑定

当函数被调用时,如果函数左侧有明确的调用对象,那么 this 指向该对象。

示例1

function func() {
  console.log(this.x)
}
let obj = { 
  x: 1 
};
obj.fn = func;  // 牢记该示例!
obj.fn(); // 1

示例2:如果函数调用前有多个对象,那么 this 指向离函数最近的对象

function func() {
  console.log(this.x)
}
let obj = { 
  x: 1, 
  y: {
    x: 2,
    fn: func
  }
};
obj.y.fn();  // 2

示例3:当 obj.fn作为 参数传递 或者 赋值给变量时,会丢失绑定!

由于 js 的内存机制以及函数本身是引用类型,所以objobj.fn储存在两个内存地址,假设地址1和地址2。obj.fn()直接调用时,是从地址1调用地址2,因此地址2的运行环境是地址1,this 指向 obj,但是,obj.fn这样表示时,是直接取出地址2再进行调用,要具体看地址2的运行环境!

参数传递:
bar(obj.fn);

赋值给变量:
let demo = obj.fn;
demo();

三、显式绑定:call / apply / bind

显式绑定时,this 指向的是参数列表里的第一个参数对象。当第一个参数为空、undefined 时,默认传入全局对象。

四、new 绑定

new 调用构造函数创建实例时,函数中的 this 指向的是新生成的实例对象

如果构造函数中返回的是一个对象(对象、数组、函数,不包括Null),那么 this 指向的就是这个返回对象,如果返回的不是对象,那么 this 还是指向实例对象!

function Person() {
  this.age = age;  // this 指向 p1
}
let p1 = new Person();

注意:因为箭头函数不可以当作构造函数、不能使用 new 命令,所以不存在 箭头函数绑定和new绑定同时存在的情况。

五、箭头函数

箭头函数没有自己的 this 对象,内部的 this 就等于 定义时上层作用域中的 this」。(如果箭头函数被普通函数包含,则 this 绑定的是最近一层普通函数的 this,否则)

因为 JS 采用的是 静态作用域,箭头函数能够访问到的上层作用域在定义时就已经确定了,不会随着执行环境变化而改变。所以箭头函数中的 this 指向是固定不变的,想改变就只能通过改变外层作用域的this指向。

六、一些特例

1. setTimeout、setInterval

如果回调函数是对象的方法,那么 setTimeout 使得方法内部的 this 指向「全局环境」。

var x = 1; 
let obj = { 
  x: 2,
  fn: function() {
    console.log('123 ' + this.x);
  } 
};
setTimeout(obj.fn, 1000);  // 1,如果var换成let,输出undefiend,因为let定义的全局变量不属于全局对象。