优先级:箭头函数 > new > 显式绑定 call / apply / bind > 隐式绑定 > 默认绑定。
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 的内存机制以及函数本身是引用类型,所以obj和obj.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定义的全局变量不属于全局对象。