01. 一网打尽this,对上下文说Yes

279 阅读2分钟

为什么要掌握this?

  • 一直是面试的热点
  • 基础,Javascript进阶的重要一环
  • 指向多变,很多隐藏bug的源由
  • 熟练驾驭,写出更简洁,优雅的代码

this到底指向谁

有一种广为流传的说法是”谁调用它,this就指向谁“,但是这样说法并不全面,也不规范。

这里可以先”死记硬背“一下几条规律:

  1. 在函数体中,非显式或隐式地简单调用函数时,严格模式下,函数内this绑定到undefined上,非严格模式绑定到全局对象window/global上
  2. new 方法调用构造函数时,构造函数内部的this会被绑定到新创建的对象上
  3. call/apply/bind方法显式调用函数,函数内this会被绑定到指定参数对象上
  4. 通过上下文对象调用函数时,函数体内的this会被绑定到该对象上
  5. 箭头函数中,this指向由外层(函数或全局)作用域来决定

1. 全局环境中的this

最基础的例子:

function f1(){
	console.log(this)
}
function f2(){
	'use strict'
	console.log(this)
}
f1() //window
f2() //undifined

变种:

const foo1 = {
  bar: 10,
  fn: function () {
    console.log(this)
  },
}
let fn1 = foo1.fn
fn1() //window
foo1.fn() //foo1对象

在函数执行时不考虑显式绑定,this如果被上一级对象调用,那么指向上一级对象,否则指向全局环境

2. 上下文中调用this

const o1 = {
  text: 'o1',
  fn: function() {
    return this.text
  }
}
const o2 = {
  text: 'o2',
  fn: function() {
    return o1.fn()
  }
}
const o3 = {
  text: 'o3',
  fn: function() {
    var fn = o1.fn
    return fn()
  }
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefinde 

3. 通过bind,call,apply改变this指向

一段代码说明下面是等价的

const target = {}
fn.call(terget, 'arg1', 'arg1')
fn.applay(terget, ['arg1', 'arg1'])
fn.bind(terget, 'arg1', 'arg1')()

4. 构造函数和this

function Foo(){
  this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar);// Lucas

new 操作符做了什么

简略的答案:

  1. 创建了一个新对象
  2. 将构造函数的this指向这个新对象
  3. 为这个对象添加属性,方法等
  4. 最终返回这个对象 代码表述为
var newObj = {}
newObj.__proto__ = Foo.prototype
Foo.call(this)

如果构造函数中出现return,需要注意:

返回简单数据类型,this指向实例

返回复杂类型,this指向这个对象

5. 箭头函数的this

在《你不知道的JavaScript》书中指出:

箭头函数中的this指向是由其所属函数或者全局作用域决定的

const foo1 = {
  fn:function(){
    setTimeout(function(){
      console.log(this);
    })
  }
}
const foo2 = {
  fn:function(){
    setTimeout(()=>{
      console.log(this);
    })
  }
}
foo1.fn();// 出现在匿名函数 指向 window
foo2.fn();// foo对象

箭头函数的this无法更改

var a = 123
function foo(){
  return a =>{
    console.log(this.a)
  }
}
const obj1 = { a:1 }
const obj2 = { a:2 }
var bar = foo.call(obj1) // 2
console.log(bar.call(obj2)) // undefined

两层箭头函数的情况

var a = 123
const foo = () => (a) => {
  console.log(this.a)
}
const obj1 = { a:1 }
const obj2 = { a:2 }
var bar = foo.call(obj1) // 123
console.log(bar.call(obj2)) // undefined

如果把全局 var a 改为 let a 或者const a 则为undefined

let 和 const 声明变量不会挂载到全局对象上

本文出自:《前端开发核心知识进阶:从夯实基础到突破瓶颈》