this的指向问题是我们工作中经常用到,面试中也会频繁问到的一个基础必考题。
这篇文章来对this的指向做一个全面的总结。若有不对的地方,欢迎指正。
一、全局执行
1. 非严格模式
浏览器下
this指向window
console.log(this); // window
2. 严格模式
严格模式下
this指向undefined
"use strict";
console.log(this); // undefined
当然,面试官可能会这样迷惑一下你:
var a = 1
function fn() {
var a = 2
console.log(this.a) // console what ?
}
fn()
// -------------- 变 -----------------
// 1. 把最外层 var a = 1 -> let a = 1,输出结果是?
// -------------- 变 -----------------
var b = 1
function outer () {
var b = 2
function inner () {
console.log(this.b) // console what ?
}
inner()
}
outer()
// --------------- 变 -----------------
const obj = {
a: 1,
fn: function() {
console.log(this.a)
}
}
obj.fn() // console what ?
const f = obj.fn
f() // console what ?
二、作为对象方法执行
- 调用方式为 对象.方法() 时,该方法中的
this指向这个对象。即谁调用指向谁。
var obj = {
name:'aaa',
hello3(){
console.log('我是hello3 this:',this); // obj
}
}
obj.hello3(); // obj
- 若将对象的方法赋值给一个变量,然后使用变量调用该方法时,
this指向window。
var obj = {
name:'aaa',
hello3(){
console.log('我是hello3 this:',this); // obj
}
}
var func = obj.hello3;
func(); // window
练习题
function fn () {
console.log(this.a)
}
const obj = {
a: 1
}
obj.fn = fn
obj.fn() // console what ?
function fn () {
console.log(this.a)
}
const obj1 = {
a: 1,
fn
}
const obj2 = {
a: 2,
obj1
}
obj2.obj1.fn() // console what ?
有时面试官一般问的是一些边界 case,比如隐式绑定失效(列举部分):
// 第一种 是前面提过的情况
const obj1 = {
a: 1,
fn: function() {
console.log(this.a)
}
}
const fn1 = obj1.fn // 将引用给了 fn1,等同于写了 function fn1() { console.log(this.a) }
fn1() // 所以这里其实已经变成了默认绑定规则了,该函数 `fn1` 执行的环境就是全局环境
// 第二种 setTimeout
setTimeout(obj1.fn, 1000) // 这里执行的环境同样是全局
// 第三种 函数作为参数传递
function run(fn) {
fn()
}
run(obj1.fn) // 这里传进去的是一个引用
// 第四种 一般匿名函数也是会指向全局的
var name = 'The Window';
var obj = {
name: 'My obj',
getName: function() {
return function() { // 这是一个匿名函数
console.log(this.name)
};
}
}
obj.getName()()
// 第五种 函数赋值也会改变 this 指向,下边练习题会有 case,react 中事件处理函数为啥要 bind 一下的原因
// 第六种 IIFE
三、定时器内部执行
定时器
setTimeOut和setInterVal内部的this都指向window
setTimeout(()=>{
console.log(this); // window
},2000)
四、构造函数内部执行
构造函数的调用要使用
new,所以,构造函数中的this,指向该构造函数实例出的对象。
function Person(name){
this.name = name
console.log(this)
}
var p1 = new Person('yunxi'); // Person
若构造函数return了一个对象,则this指向这个对象。
function foo(a) {
this.a = a
}
const f = new foo(2)
f.a // console what?
// ------------------------- 变 ---------------------------
function bar(a) {
this.a = a
return {
a: 100
}
}
const b = new bar(3)
b.a // console what ?
五、箭头函数
箭头函数的
this是在定义时根据上下文函数决定的。
1.当箭头函数的上下文没有函数环境时,即直接调用时,this指向window/undefined
var obj = {
a: 100,
hello5:()=>{
console.log(this.a)
}
}
obj.hello5(); // undefined
2.当箭头函数有上下文函数环境时,this指向上下文函数的this。(因为是obj2调用的hello6,所以箭头函数的this,也就是hello6的this)。
var obj2 = {
a: 100,
hello6:function(){
var a = 200;
setTimeOut(()=>{
console.log(this.a)
},2000)
}
}
obj2.hello6(); // 100
箭头函数本身是没有 this 的,继承的是外层的
function fn() {
return {
b: () => {
console.log(this)
}
}
}
fn().b() // console what?
fn().b.bind(1)() // console what?
fn.bind(2)().b.bind(3)() // console what?
六、call、apply、bind
使用
call、apply、bind可以修改this的指向。(箭头函数的this不会被修改)
1. call 的用法
(1)call的第一个参数就是this,若第一个参数传的是null,则不会改变this指向。
(2)call 可以传多个参数
function fn(){
console.log(this)
}
var obj = {
a: 100
}
fn.call(obj); // {a:100}
fn.call(obj,[1,2],true,{},false)
2. apply 的用法
apply只接受2个参数:
(1)第一个参数就是this,若第一个参数传的是null,则不会改变this指向。
(2)第二个参数必须是数组。
function fn(){
console.log(this)
}
var obj = {
a: 100
}
fn.apply(obj); // {a:100}
fn.apply(obj,[1,2,4])
3.bind 的用法
(1)bind不会立即执行。
(2)bind的第一个参数就是this,若第一个参数传的是null,则不会改变this指向。
(3)多bind时,只和第一次bind的值有关。
(4)bind 可以传多个参数。
function fn(){
console.log(this)
}
var obj = {
a: 100
}
fn.bind(obj)(); // {a:100}
fn.bind(1).bind(2)(); // 1
总结:
相同点:
bind、call、apply都可以改变this的指向,其中接受的第一个参数就是this。若第一个参数传的null,都不会改变this的指向。
不同点:
1.bind不会立即执行,而apply和call会立即执行。
2.apply和call的区别是:apply只能接受两个参数并且第二个参数必须是数组,而call可以接受很多参数并且不限制类型。
七、优先级
这上面的各种方式一定是有先后顺序的,同时作用于一个函数的时候,以哪一个为准呢?这取决于优先级
// 隐式 vs 默认 -> 结论:隐式 > 默认
function fn() {
console.log(this)
}
const obj = {
fn
}
obj.fn() // what ?
// 显式 vs 隐式 -> 结论:显式 > 隐式
obj.fn.bind(5)() // what ?
// new vs 显式 -> 结论:new > 显式
function foo (a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // what ?
// new
var baz = new bar(3)
console.log( obj1.a ) // what ?
console.log( baz.a ) // what ?
// 箭头函数没有 this,比较没有意义
优先级「new 绑」 > 「显绑」 > 「隐绑」 > 「默认绑定」
八、练习题
// 1.
function foo() {
console.log( this.a ) // console what
}
var a = 2;
(function(){
"use strict" // 迷惑大家的
foo();
})();
// 2.
var name="the window"
var object={
name:"My Object",
getName: function(){
return this.name
}
}
object.getName() // console what ?
(object.getName)() // console what ?
(object.getName = object.getName)() // console what ?
(object.getName, object.getName)() // console what ?
// 3.
var x = 3
var obj3 = {
x: 1,
getX: function() {
var x = 5
return function() {
return this.x
}(); // ⚠️
}
}
console.log(obj3.getX()) // console what?
// 4.
function a(x){
this.x = x
return this
}
var x = a(5) // 替换为 let 再试试
var y = a(6) // 替换为 let 再试试 // 再换回 var,但是去掉 y 的情况,再试试
console.log(x.x) // console what ?
console.log(y.x) // console what ?
// 等价于
window.x = 5;
window.x = window;
window.x = 6;
window.y = window;
console.log(x.x) // void 0 其实执行的是 Number(6).x
console.log(y.x) // 6