Javascript中this的指向

来源:互联网 发布:云计算的部署方式包括 编辑:程序博客网 时间:2024/06/05 03:57

  在箭头函数出现之前,每个新定义的函数都有它自己的 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 是 objobj.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);//或者console.log(new MyClass().name)

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

var MyClass = function(){  this.name = 'sven';  return 'anne';  //返回string类型}var obj = new MyClass();console.log(obj.name); //输出anne

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

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

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

  • new操作符里到底发生了什么
  • 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()  //windowapp.fn2()  //appapp.fn3()  //appapp.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?