这篇文章上次修改于 204 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

今天看面经的时候才发现,自己认为已经了解的this指向问题,还有很多细节不清楚,所以再写一篇文章记录一下。

this的指向有四种,全局,对象,new和显式调用,我们通过习题的方式来发现一些this指向中容易被忽略的问题

let val = 1;
function foo() {
  console.log(val);
}
function bar() {
  let val = 2;
  foo();
}
bar();
// 1

这里输出为1的原因是,js的作用域链是静态的,当一个函数/对象访问的变量在其中找不到时,他会去词法上的父区域进行寻找,这就是js中的作用域链,而动态语言中,他会在调用的父区域进行寻找,所以这里输出的是1

window.name = 'ByteDance';
function A() {
  this.name = 123;
}
A.prototype.getA = function () {
  return this.name + 1;
};
let a = new A();
let funcA = a.getA;
console.log(funcA()); //bytedance1
// 解释完上面答下面这种情况
console.log(a.getA());//124

这里需要注意的是,当把a.getA赋值给funcA时,相当于把这个函数的引用赋值给了这个变量,所以当调用这个函数时,实际上是直接调用了这个函数,而不是通过对象调用了函数,所以this的指向是windows

const obj = {
  birth: 1990,
  getAge(year) {
    let fn = y => y - this.birth;
    return fn.call({ birth: 2000 }, year);
  },
};
console.log(obj.getAge(2020));
//30

这个是非常tricky的一道题,我看的时候懵了很久,这道题主要的考察点在于,箭头函数没有this,他的this是通过父作用域继承的,这个描述在当时看箭头函数的不同时是看到了的,但是并没有特别深刻的体会,这道题中,正常会认为,call函数会指定this,所以this.birth会是2000,但是值得注意的是,call函数对于箭头函数是无效的,所以箭头函数的this依然是obj,所以输出30

let a = 10
const b = 20

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

这里涉及了一个我一直没有注意到的知识点,那就是使用var在全局声明变量时(位置必须是全局而不能是函数内部),会自动绑定到window对象上,所以使用this.a是可以访问到的,但使用const和let的话,就不会将变量绑定到window对象上。

隐式丢失问题

隐式绑定是指this会指向最后一个调用这个函数的对象,但是隐式绑定会存在丢失的情况,有两种原因:

  • 使用另一个变量来给函数取别名
  • 将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定
function foo () {
  console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }

obj.foo();
foo2();
obj2.foo2();
//1
//2
//3

这道题第一个输出是隐式绑定,this是obj,所以输出为1,当将obj.foo赋值给foo2时,发送了隐式绑定丢失,所以this绑定对象丢失了,而foo2()本身的调用对象是window,所以this指向window,输出2,同理,将obj.foo赋值给obj2的foo2时,也发生了隐式绑定丢失,所以this指向obj2

第二种隐式绑定丢失的情况是函数作为参数

function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)
//{ a:3, doFoo: f }
//2

如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

显式绑定

关于显式调用,值得注意的是,如果call,bind,apply的第一个参数是空或者null、undefined的话,则会忽略这个参数。

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this)
      console.log(this.a)
    }, 0)
  }
}
var a = 3

obj2.foo1()
obj2.foo2()
//2
//3

第二个输出是3的原因是,setTimeout这个函数之中,函数作为参数被传入了setTimeout函数,发送了隐式绑定丢失,所以这里this指向了window。

箭头函数相关

箭头函数里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时。

js中只有两种作用域,全局作用域和局部作用域,而局部作用域只能通过函数来定义

var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var name = 'window'
obj.foo1()
obj.foo2()()
//window
//obj
//obj

值得注意的是最后一个obj,这是因为箭头函数没有自己的this,从父作用域继承,而父作用域为foo2(),this指向obj,所以输出obj

主要需要注意作用域的定义,并不是我认为的大括号之间就是作用域,而是只有函数能创造作用域