Javascript中this的指向

  在箭头函数出现之前,每个新定义的函数都有它自己的 this值(在构造函数的情况下是一个新对象,在严格模式的函数调用中为 undefined,如果该函数被称为“对象方法”则为基础对象等)。 ES6 引入了支持this词法解析的箭头函数(它在闭合的执行上下文内设置this的值)。

在具体说this的4种应用场景前,先从函数调用开始说起,以方便我们理解之后的代码。当我们执行一个函数,以下几种调用方式都是等价的 :

"use strict"
function fn(a,b){
    console.log(this)
}
fn(1, 2)

//等价于
fn.call(undefined, 1, 2)
fn.apply(undefined, [1, 2])
  • 在严格模式下, fn 里的 this 就是 call 的第一个参数,也就是 undefined。
  • 在非严格模式下(不加”use strict”), call 传递的第一个参数如果是 undefined 或者 null, 那 this 会自动替换为 Window 对象。
var obj = {
    fn: function(a, b){
        console.log(this)
    },
    child: {
        fn2: function(){
            console.log(this)
        }
    }
}
obj.fn(1, 2)
//等价于
obj.fn.call(obj, 1, 2)         // 所以 this 是 obj
obj.fn.apply(obj, [1, 2])

obj.child.fn2()
//等价于
obj.child.fn2.call(obj.chid)    // 所以 this 是 obj.child

除去不常用的with和eval等情况,具体到实际应用中,this指向大致可以分为以下4种。
1. 作为对象的方法调用
2. 作为普通函数调用
3. 构造函数调用
4. 箭头函数

作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象。

var obj = {
              a:1,
              getA: function(){
                  console.log(this === obj);  //true
                  console.log(this.a);  //1
              }
          }

obj.getA();

作为普通函数调用

当函数不能作为对象的属性被调用时,也就是我们常说的普通函数调用方式,此时的this总是指向全局对象(window对象), Es5的严格模式下会指向undefined。

window.name = 'globalName';
var getName = function(){
    return this.name;
}
console.log(getName())  //globalName

//或者

window.name = 'globalName';
var myObject = {
    name: 'sven',
    getName: function(){
        return this.name;
    }
}
var getName = myObject.getName;
console.log(getName());  //globalName

构造函数调用

当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。代码如下:

var MyClass = function(){
    this.name = 'sven';
}

var obj = new MyClass();
console.log(obj.name);   //sven
//或者
console.log(new MyClass().name)   //sven

如果构造函数不显示的返回任何数据,或者是返回一个非对象类型的数据,此时this指向没有问题。代码如下:

var MyClass = function(){
  this.name = 'sven';
  return 'anne';  //返回string类型
}

var obj = new MyClass();
console.log(obj.name); //输出sven

需要注意的是,用new调用构造函数时,如果构造函数显示地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是之前期待的this。

var MyClass = function(){
  this.name = 'sven';
  return {  //显示地返回一个对象
      name: 'anne'
  }
}

var obj = new MyClass();
console.log(obj.name); //输出anne

为了更好的理解new的过程,可以参考

箭头函数

箭头功能不会创建自己的this;它使用封闭执行上下文的this值。

在全局代码中,它将被设置为全局对象。

var window = this;
var foo = (() => this);
console.log(foo() === window);  //true

当在其他函数中创建的箭头函数:这些箭头函数的this被设置为外层执行上下文。

let app = {
    fn1: function(a){
        console.log(this)  //app
    },
    fn2(a) {
         console.log(this)  //app
    },
    fn3: (a)=>{
        console.log(this)  //window
    }
}
app.fn1();
app.fn2();
app.fn3();

粗略一看,fn1、fn2、fn3 貌似都一样,实际上 fn1和 fn2完全等价,但 fn3是有区别的
app.fn2相当于app.fn2.call(app)
app.fn3相当于app.fn3.call( 它的上一级的 this)。
因为箭头函数不会绑定this,所以会找它的上一级,找到window。

我们可能会写出如下代码:

function Person(){
     this.age = 0;
     setInterval(function growUp(){
         this.age++;
         console.log(p.age);  // 每隔一秒打印age
     },1000)
 }
 var p = new Person();

上面代码相当于

function Person(){
     this.age = 0;
     function growUp(){
         this.age++;
         console.log(p.age);  // 每隔一秒打印age
     }
     // 过1秒后执行
     growUp();
 }
 var p = new Person();

普通函数执行,this为window,而不是Person;在ECMAScript 3/5中,通过将this值分配给封闭的变量,可以解决this问题。代码如下:

function Person(){
     this.age = 0;
     var that = this;
     setInterval(function growUp(){
         that.age++;
         console.log(p.age);  //每隔一秒打印age
     },1000)
}
var p = new Person();

有了箭头函数之后,我们可以写出下面的代码:

function Person(){
     this.age = 0;
     setInterval(() => {
         this.age++;
         console.log(p.age);  //每隔一秒打印age
     },1000)
}
var p = new Person();

因为箭头函数不会绑定this,所以会找它的上一级,找到Person对象。

再来个稍微复杂点的例子:

var app = {
          init() {
              var menu = {
                  init: ()=>{
                      console.log(this)
                  },
                  bind() {
                      console.log(this)   
                  }
              }
              menu.init()   
              /*相当于  menu.init.call(menu 所在的环境下的 this)  , 所以 init 里面的 this 也就是 app。
              (假设 app.init 也是箭头函数,想想 menu.init 里面的 this 是什么?)          
              */
              menu.bind()   
              /*相当于 menu.bind.call(menu),也就是 menu,所以 bind 里面的 this 就是 menu
              */
          }
      }

app.init()

最后一个例子:

var app = {
    fn1() {
        setTimeout(function(){
            console.log(this)
        }, 10)
    },
    fn2() {
        setTimeout(()=>{
            console.log(this)
        },20)
    },
    fn3() {
        setTimeout((function(){
            console.log(this)
        }).bind(this), 30)        
    },
    fn4: ()=> {
        setTimeout(()=>{
            console.log(this)
        },40)        
    }
}
app.fn1()  //window
app.fn2()  //app
app.fn3()  //app
app.fn4()   //window

上面的代码可以转化为:

var app = {
    fn1() {
        function fn(){
            console.log(this)
        }
        //过10ms 后执行
        //fn.call(undefined) ,所以输出 Window
    },
    fn2() {
        //过20ms 执行箭头函数
        //箭头函数里面没资格有 自己的 this,借用 setTimeout 外面的 this,也就是 app
    },
    fn3() {
        // 可以转化为
         var fn = function(){
             console.log(this);
         }
         var fn2 = fn.bind(this);
         setTimeout(fn2,30);

        // 创建了一个新函数,这个新函数里面绑定了 外面的this,也就是 app
        // 20 ms 后执行新函数,输出 this,也就是刚刚绑定的 app    
    },
    fn4: ()=> {
        //过40ms 执行箭头函数
        //箭头函数里面没资格有 this,用 setTimeout 外面的 this
        //setTimeout 所在的 fn4也是箭头函数,没资格拥有自己的 this,借用外面的 this ,也就是 Window     
    }
}

注: 因为箭头函数并不绑定this,因此使用箭头函数后的对象尝试使用call,apply,bind设定this是无效的。

参考阅读:
1. JavaScript设计模式与开发实践–曾探著
2. this
3. this-course
4. this 的值到底是什么?一次说清楚
5. What’s this?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值