JavaScript中的this指向全解析

面试常见问题,特地记录一下。

代码已经关联到github: 链接地址 觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:

this

在 JavaScript 中,this 是指当前函数中正在执行的上下文环境,注意这里的上下文跟执行上下文不是一个概念,需注意区分。
下面从定义的方式和调用的方式上简单聊下其中的this指向问题。

不同定义的形式

function关键字

通过 function 构造的函数,非显式或者隐式简单调用函数时,也就是正常通过fn.()去执行时,非严格模式会被绑定到全局对象window上 ,严格模式下会被绑定到undefined上面,其他不同调用方式下一节会细讲。

var obj = {
  foo: function () {
    //定义时位于全局,之后 obj 的 foo 指向了这个函数
  	console.log(this===window)
  }
};

// 写法一 这种是方法调用
obj.foo() //false 通过obj 找到foo ,运行再obj内部 this==obj

// 写法二 
var foo = obj.foo;
foo() // true foo指向函数本身 ,运行在全局, this==window 严格模式下为undefiend

箭头函数

箭头函数 是没有this的,所以通过 箭头函数 构造的函数:

  • 会始终跟随他外层函数或者全局的this,如果第一个嵌套的外层不存在 this,会再往上直至找到this,最终直到全局。
  • 使用 callapply 会忽略掉 this 参数,就算是下文的其他调用方式箭头函数也还是会依赖上一条的方式去查找this,想改变箭头函数this只能通过改变其父级的this实现。

this is the enclosing context where the arrow function is defined

var obj = {
  foo: ()=> {
    console.log(this === window)
  }
};

// 写法一
//函数运行时,找到其父的执行上下文,所以这个this===window
obj.foo() // true 

// 写法二
//函数运行时,找到其父的执行上下文,所以这个this===window
var foo = obj.foo;
foo() // true


//-----分割线
var obj2 = {
  obj:function(){
    return {
      foo: ()=> {
        console.log(this)//定义是外部的this,所以为obj2
      }
    };
  }
}

var foo = obj2.obj().foo;

// 写法一
//函数运行时,找到其父的执行上下文,所以这个this===window
obj2.obj().foo() // this==window

// 写法二
//函数运行时,找到其父的执行上下文,所以这个this===window
foo() //this==window

不同的调用方式

一般函数的调用方式分四种:

  • 作为函数直接被调用 parseInt('11')
  • 作为对象的方法调用 console.log('Hello World!')
  • 作为构造函数的实例调用 new RegExp('\\d')
  • 间接调用 alert.call(undefined, 'Hello World')

函数直接调用

this is the global object in a function invocation

全局对象取决于当前执行环境,在浏览器中,全局对象即 window (严格模式下指向 undefiend ) 。

this.a = 1
this.b = 2

function sum(){
  	console.log(this === window)
    console.log(this.a+this.b)
 }

sum() //true 3 ,严格模式下: false 报错

对象方法调用

this is the object that owns the method in a method invocation

当在一个对象里调用方法时,this 代表的是对象它自身。

const  cat = {  
  type:'Cat',
  legs:4,
  logInfo : function() {
    console.log(this)
    console.log(this === cat); // => 1.true 2.false
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  }
} 
//1. The Cat has 4 legs
cat.logInfo()

//2. 打印出 "The undefined has undefined legs"
// 或者在严格模式下抛出错误 TypeError
//这里将方法函数传入延迟执行,延迟执行将其作为 函数调用
setTimeout(cat.logInfo, 1000); 

class调用的也是他自身

class Planet {  
  constructor(name) {
    this.name = name;    
  }
  getName() {
    console.log(this === earth); // => true
    console.log(this.name); // => 'Earth'  
    return this.name;
  }
}
var earth = new Planet('Earth');  
//1. 作为方法调用,上下文为 earth
earth.getName(); // => true 'Earth'  

//2.作为函数调用
var getName = earth.getName
getName(); // => false undefiend或者报错

构造函数调用

this is the newly created object in a constructor invocation

构造器调用的环境是新创建的对象。通过传递构造函数参数来初始化新建的对象,添加属性初始化值以及事件处理器。

function Foo () {  
    console.log(this === Foo); // => 1.false 2.false
    console.log(this instanceof Foo); // => 1.true 2.false 是window
    this.property = 'Default Value';
  //return this 2. 需要return this 否则为 undefined
}
//1. 构造函数调用
var fooInstance = new Foo();  
console.log(fooInstance.property); // => 'Default Value'  

//2. 忘记 new 关键字构造函数调用
var fooIns2 = Foo()
console.log(fooIns2.property); // =>  Cannot read property 'property' of undefined

指定this调用

间接调用表现为当一个函数使用了 .call() 或者 .apply() 方法。

this is the first argument of .call() or .apply() in an indirect invocation

在间接调用中,this 指向的是 .call().apply()传递的第一个参数。

const cat = {  
  type:'Cat',
  legs:4,
} 
function say(str){
	console.log(str + ' say: ' + this.type + ' has ' + this.legs + ' legs')
}

// 间接调用
say.call(cat, 'Hello ');  // => 'Hello  say: Cat has 4 legs'  
say.apply(cat, ['Bye ']); // => 'Bye  say: Cat has 4 legs' 

绑定this调用

绑定函数调用是将函数绑定一个对象,它是一个原始函数使用了 .bind() 方法。

this is the first argument of .bind() when invoking a bound function

绑定函数的 this 永远指向绑定时的上下文,间接调用也不能更改

const cat = {  
  type:'Cat',
  legs:4,
} 
const fish = {
	type:'Fish',
  legs:0,
}
function say(str){
	console.log(str + ' say: ' + this.type + ' has ' + this.legs + ' legs')
}
const _say = say.bind(fish)
_say('Hello') //Hello say: Fish has 0 legs
// 间接调用
_say.call(cat, 'Bye ');  // => Bye  say: Fish has 0 legs
 

setTimeout调用

在《JavaScript高级程序设计》中有这样的描述:

setTimeout调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined。

实时真的如此吗?我们看个例子:

//注意var 会绑定到window.a 
var a  = 'global'

function fn1(){
  setTimeout(function(){
      console.log('fn1', this.a)
  })
}

function fn2(){
    setTimeout(()=>{
      console.log('fn2', this.a)
    })
}


fn1()//global 
fn2()//global 

fn1.call({a:'obj'}) //global
fn2.call({a:'obj'}) // 'obj'

从上面的例子我们可以看到,对于function定义的函数,确实跟红宝书介绍的一致,但是我们的箭头函数比较特立独行,它依然会孜孜不倦的查找父级的this

总结

  1. 显式简单调用函数时,
    • 严格模式下会被绑定到undefined上面
    • 非严格模式会被绑定到全局对象window
  2. 使用new方法调用构造函数,内部的this会被绑定到新创建的对象
  3. 使用call/apply/bind方法调用函数,会指向传进去的参数对象
  4. 一般使用具体对象去调用函数,this会绑定到该对象
  5. 箭头函数中,this指向是由外层(函数或者全局作用域)决定的
  6. setTimeout调用函数都是在全局作用域中执行的,this的绑定与调用与第1点一致,但是碰到箭头函数时例外,以箭头函数的指向为准。

实战1:

    var numbers = {  
      numberA: 5,
      numberB: 10,
      sum: function() {
        //函数关键字定义的函数,这里通过number调用,所以此处this为number
        console.log(this === numbers); // => true
        function calculate() {
          // 注意这里的执行上下文环境是number.sum方法执行的上下文,也就是window 
          //  this 是 window or undefined(严格模式下)
          console.log(this === numbers); // => false
          //window不存在这两个参数 返回NaN
          return this.numberA + this.numberB;
        }
        return calculate();
      },
      sum2: ()=>{
        //箭头函数只看执行时父级的上下文, numbers.sum2的执行上下文为window, 所以此处为window
        console.log(this === numbers); // => false
    		function Calculate() {
          //  new 的情况下 this 是自身实例
          console.log(this === numbers); // => false
          return {
            //自身实例不存在这两个参数 返回NaN
          	count: this.numberA + this.numberB 
          }
        }
        //new 构造函数调用
        const c = new Calculate()
        return c.count;
      }
    };
    
    numbers.sum(); // true  false  => NaN  , 严格模式下 throws TypeError  
    numbers.sum2() // false false  =>  NaN , 严格模式下 throws TypeError  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值