前端面试之必会--搞清楚js中this(使用技巧)

136 阅读5分钟

this的运用首先会涉及到执行上下文知识

  • 全局执行上下文:执行window
  • 函数执行上下文:涉及到执行上下文栈(函数执行为这个函数创建函数,执行完毕之后,移除执行上下文栈)
  • eval函数执行上下为

image.png

image.png

执行上下文栈-动画解释

在大多数情况下,函数的调用方式决定了this的值,运行时绑定; 在非严格模式下,总是指向一个对象,在严格模式下可以为任意值

es5中可以通过bind方法来设置函数的this的值,es6引入箭头函数,箭头函数不提供自身的this绑定,this的值将保持为闭合词法上下文的值

this的几种使用场景

  1. 全局上下文
  2. 函数上下文
  3. 类上下文
  4. 派生类
  5. bind,apply,call改变this

派生类的构造函数没有初始的this绑定,所以在构造函数中调用super()会生成一个this绑定,like this: this = new Base(), Base为基类 所以我们在class中constructor里面需要调用super的用意便是如此,在调用super()之前引用this会抛出错误

this 函数调用

在函数内部,this的取值取决于函数被调用的方式 ES5中里面有3种形式的函数调用

1. func(arg1, arg2) // 直接函数调用
2. obj.childProp.method(arg1,arg2) // 对象方式调用
3. func.call(context, arg1,arg2)

可以说第1,2种方式是第三种方式的语法糖, 都可以等价的转换为call的方式

func(arg1, arg2) => func.call(undefined, arg1,arg2) obj.childProp.method.call(obj.childProp,arg1,arg2)

即记住func.call(context, arg1,arg2)形式,理解this指向就容易多了

举例子解释:

在非严格模式下,使用call, apply,如果用做this的值不是对象,就会被尝试转换为对象; null,undefined被转换为全局对象

  function fun1(){
   console.log(this)
  }
  // ======== 简写为 =====:
  fun1.call(undefined) // 浏览器的规则,如果你传的context是undefined或者null,那么window就是默认的context, 严格模式下context是undefine

ECMAScript262规范规定:如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值

作为对象的方法

在函数作为对象的方法调用时,this被设置为调用函数的对象,this的绑定只受最接近的成员引用的影响 接下来看我们的上面所说的第二种当时obj.childProp.method(arg1,arg2)

let obj = {
    name: function(){
     console.log(this)
    }
}
// 对象调用方法
obj.name()

// 可以套用我们的框架 obj.name.call(obj),所以此时的this就是obj

// =========== 延伸1: []语法的this指向:===================

function func(){
  console.log(this)
}
function  fn(){console.log(11)}
let array = [func, fn]
 array[0]() // 即array.0() 转换为我们的框架形式: =>  array.0.call(array),最接近的成员引用array
  1. 或许我们经常犯错误的点在这样一个场景:将一个方法从对象中拿出来,然后再调用,期望方法中的this是原来的对象,比如回调中传入这个方法,立即执行函数,但是实际上,如果我们不做特殊处理,一般会丢失原来的对象

2.匿名函数的this是指向全局对象的

3.this绑定的优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。 new 绑定是比隐式绑定优先级高

//========== 延伸1.: 只是引用对象的方法,但是没有调用它的情况:================
this.age = 18;
let obj = {
    age:12, 
    name: function(){
     console.log(this.age)
    }
}
let obj2 = obj.name
obj2() // 18,   // 转换为我们的框架形式:obj.call(), 没有表明context, 默认undefined, 即指向window对象
延伸-箭头函数this问题

箭头函数没有this, 如果你在箭头函数里面看到了this,直接把它看成是箭头函数外面的this就行,外面的this是啥,箭头函数中的this就是啥,箭头函数内外部的this是同一个东西。

this判断流程图

image.png

this题 小练习1
function foo() {
  console.log( this.a );
}

function doFoo() {
  foo(); // 可以等价于window.foo()
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo() // 2
// 题目解析
 执行foo的时候,执行环境是doFoo函数,执行环境为全局,即window.foo(), this指向window
this题 小练习2
var a = 10
var obj = {
  a: 20,
  say: () => { // 箭头函数,这里的this就是函数外部父级所处的上下文的this => window, 如果这里改成普通函数,this指向的就是obj
    console.log(this.a) 
  },
  say1: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
}


obj.say() 
var o = obj.say1;
o() // 1111, window => f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window
obj.say1(); // 1111 obj对象 => 谁调用say,say 的this就指向谁
var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) // 箭头函数使用apply改变this无效

// 输出结果:10  10
this题 小练习3
var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
   // console.log(this.a) // 2 
    printA();
  },
  getA: function(){
    var a = 5;
    return function(){
      return this.a;
    }()
  }
}
console.log(obj.getA())  // 1, 匿名函数的this是指向全局对象的,所以this指向window
obj.foo(); // 2 => foo的this指向obj
obj.bar(); // 1 => printA在bar方法中执行, printA的this指向的是window
var foo = obj.foo;
foo(); // 1 =>  将一个方法从对象中拿出来,然后再调用,this改变了,指向window
小练习4

this绑定的优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

function foo(something){
    this.a = something
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3 => 考察this绑定的优先级了,new 绑定是比隐式绑定优先级高

监听事件中的this指向

MDN:通常来说 this 的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意; 当使用 addEventListener() 为一个元素注册事件的时候,事件处理器里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样

my_element.addEventListener('click', function (e) {
  console.log(this.className)           // 输出 my_element 的 className
  console.log(e.currentTarget === this) // 输出 `true`
})