面试官:怎么理解Javascript中的this?

带着问题出发

  1. this的定义是什么?
  2. this在不同的情况下调用,指向分别如何?
  3. 普通函数和es6中箭头函数中的this有什么区别?

理解

this代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。

这里注意的是 运行时,这说明this关键词只与函数的执行环境有关。

this会在执行上下文中绑定一个对象,但是是根据什么条件绑定的呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。

this绑定规则

0. 全局作用域下-this默认指向window
在浏览器中测试可知,全局作用下this指向是指向window

console.log(this) // window

var name = "guava"

console.log(this.name,window.name) // "guava","guava"

开发中很少直接在全局作用域下使用this,通常都是在函数中使用

  1. 直接调用普通函数-this指向window

对于直接调用普通函数,this 的指向一定是 window

function foo(){
  console.log(this.a)
}
var a = 1;

//相等于window.foo()
foo()

所有函数被调用时,都会创建一个执行上下文

  • 这个上下文中记录着函数的调用栈、函数的调用方式、传入的参数信息等
  • this也是其中的一个属性

这里看一个例子:

function foo(func) {
  func()
}

var obj = {
  name: "guava",
  bar: function() {
    console.log(this); // window
  }
}

foo(obj.bar);

这里打印的结果时window,猜对了吗?真正调用函数的位置,是foo函数,foo函数没有进行任何的对象绑定,foo函数的this指向当然是window,传入的bar函数被当成了普通函数执行,所以也是指向window。

  1. 通过对象调用函数-谁调用函数,this指向谁

一般情况下,谁调用了函数,this就指向谁。

const obj = {
  a: 2,
  foo: function (){
    console.log(this.a)
  }
}

// obj调用了foo函数,所以foo里面的this指向的是obj对象
obj.foo() 
// 2

那么思考一下,下面的案例,this指向哪里?

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

var obj1 = {
  name:"obj1",
  foo:foo
}

var obj2 = {
  name:"obj2",
  obj1:obj1
}

obj2.obj1.foo()

很简单,最终foo函数还是通过obj调用的,所以还是指向了obj1

  1. 显式绑定-bind、call、apply

显式绑定后,this会明确的指向绑定对象

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

foo.call(window); // window
foo.call({name: "guava"}); // {name: "guava"}
foo.call(123); // Number {123}

foo.apply(window); // window
foo.apply({name: "guava"}); // {name: "guava"}
foo.apply(123); // Number {123}

var bar1 = foo.bind(window)
var bar2 = foo.bind({name: "guava"})
var bar3 = foo.bind(123)

bar1() // window
bar2() // {name: "guava"}
bar3() // Number {123}
  1. 调用new 关键词创建的对象实例的函数

构造函数或Class类,可以通过new 关键词创建的对象实例,这时 this 永远的指向了实例化的对象上面

function Foo() {
  this.a = 3
}
let f = new Foo()

console.log(f.a) // 3

当函数中有return关键词:如果函数中返回值是一个对象(null除外),则实例化对象时this指向就是这个对象,否则指向该函数的实例。

function Foo1(){
    this.name = "guava"
    return null 
    //return 1
    //return false
    //return undefined
    //return "guava"
}

let person1 = new Foo1()
console.log(person1.name) // "guava"


function Foo2(){
  this.name = "guava"
  return {name:"st"}
}

let person2 = new Foo2()
console.log(person2.name) // "st"

  1. 规则优先级

前四条规则中,多条规则存在时,谁的优先级更高呢?也就是多条规则存在应用那一条规则呢?
结论是:new关键词 > 显式绑定 > 对象调用 > 普通函数调用

ES6的箭头函数

ES6中箭头函数(()=>{})没有 this、arguments、super 等,不受上边的规则限制,这些只依赖包含箭头函数最接近的函数中的规则,也就是外层作用域来决定this。

var obj1 = {
  name:"guava",
  getName:function(){
    setTimeout(()=>{
      console.log(this.name)
    },1000)
  }
}

obj1.getName() // "guava"

var name = 'st'
var obj2 = {
  name:"guava",
  getName:()=>{
    setTimeout(()=>{
      console.log(this.name)
    },1000)
  }
}

obj2.getName() // "st"

代码中,obj1中的setTimeout函数使用了箭头函数,所以this获取的是getName函数的this,所以指向了调用函数的对象obj1;obj2中的setTimeout和getName函数都是箭头函数,所以继续向外找,找到了全局作用域window

this面试题

  1. 面试题1
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();

这题很简单,相信你已经知道答案了

function sayName() {
  var sss = person.sayName; 
  sss(); // window 

  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window  全局声名 变量 b ,然后调用
}
  1. 面试题2
var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()(); 
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

答案和解析如下:

person1.foo1(); // person1
person1.foo1.call(person2); // person2

person1.foo2();// window
person1.foo2.call(person2);// window

//相当于返回的函数再在window下调用
person1.foo3()();// window  
//同上
person1.foo3.call(person2)();// window 
//显式调用
person1.foo3().call(person2);// person2 

//箭头函数往上一层作用域为person1
person1.foo4()();// person1	
//foo4的作用域显式的绑定为person2,所以箭头函数往上一层作用域为person2
person1.foo4.call(person2)();// person2
//返回的是箭头函数,只看上一层作用域,所以还是person1
person1.foo4().call(person2);//person1 
  1. 面试3
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)() 
person1.foo3().call(person2) 

person1.foo4()() 
person1.foo4.call(person2)() 
person1.foo4().call(person2) 

答案和解析如下:

//对象调用
person1.foo1() // person1 
//显式调用
person1.foo1.call(person2) //person2 

//箭头函数,则往上一层作用域
person1.foo2() // person1
//同上
person1.foo2.call(person2) // person1

//相当于返回的函数再在window下调用
person1.foo3()() // window
//同上
person1.foo3.call(person2)() // window
//返回的函数 显式调用
person1.foo3().call(person2) // person2

//箭头函数,则往上一层作用域
person1.foo4()() // person1
//同上
person1.foo4.call(person2)() // person2
//箭头函数使用显式绑定无效,只会找上一层作用域
person1.foo4().call(person2) // person1
  1. 面试题4
var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

是不是已经驾轻就熟,一眼就看出来了

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

总结

通过上面的学习,当面试官问到我们可以从容的回答了:
当函数运行时,函数内部的执行上下文会创建一个this的对象属性,在不同的情况下this绑定的对象也有不同的指向,一般地遵循四条规则

  • 调用普通函数时,this默认指向到window
  • 对象调用函数时,this指向到调用的对象,也就是谁调用,this指向谁
  • 使用call、apply、bind函数可以显式的绑定this指向
  • new关键词创建的实例,this默认指向到实例化对象

多条规则同时存在的情况下,其优先级是new关键词>显式绑定>对象调用函数>默认绑定
ES6中箭头函数没有this对象属性,只会找上级作用域的this。

参考:

前端面试之彻底搞懂this指向
彻底理解js中this的指向,不必硬背。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值