JavaScript中函数上下文this指向问题#面试常见读代码

this 总是指向执行时的当前对象。JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。也就是说 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

除了使用 with 和 eval 的情况,常用的可分为以下几种:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call 或 Function.prototype.apply调用

1、作为对象的方法调用

对象的方法被调用时,this 指向该对象。

var obj = {
  a: 1,
  getA() {
    alert(this === obj); // 输出:true 
    alert(this.a); // 输出: 1
  }
};
obj.getA();

2、作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。

这里注意对象的方法被单独拷贝出来后执行,那么原来的this会丢失,变成指向全局对象。

//在浏览器的JavaScript 里,这个全局对象是window 对象。
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

3、构造器调用

通常情况下,构造器里的 this 就指向返回的这个对象;

  • 如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,this指向返回的这个对象
  • 如果构造器显式地返回了一个对象,则实例化的结果会返回这个对象,而不是this;
//构造器里的this 就指向返回的这个对象,见如下代码:
var MyClass = function(){
   this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
var MyClass = function(){
   this.name = 'sven';
   return { // 显式地返回一个对象
       name: 'anne'
  }
};
var obj = new MyClass();
alert ( obj.name ); // 输出:anne
var MyClass = function(){
   this.name = 'sven'
   return 'anne'; // 返回string 类型
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven

 4、call 或 apply 调用

apply和call可以动态地改变传入函数的 this

var obj1 = {
   name: 'sven',
   getName: function(){
       return this.name;
  }
};
var obj2 = {
   name: 'anne'
};
console.log( obj1.getName() ); // 输出: sven
console.log( obj1.getName.call( obj2 ) ); // 输出:anne

除此之外,this在箭头函数中的指向与上述不同

箭头函数的this取值,规则非常简单,因为this在箭头函数中,可以看做一个普通变量。箭头函数没有自己的this值,箭头函数中所使用的this都是来自函数作用域链,它的取值遵循普通变量一样的规则,在函数作用域链中一层一层往上找。

有了箭头函数,我们只要遵守下面的规则,this的问题基本上就可以迎刃而解了

  • 对于需要使用object.method()方式调用的函数,使用普通函数定义,不需要使用箭头函数。对象方法中所使用的this值有确定的含义,指的就是object本身。
  • 其他情况下,全部使用箭头函数。

 那么,为什么要使用箭头函数呢?

当我们需要在对象方法中嵌套一个内层函数时,this就会给我们带来实际的困扰了‍比如:

// 使用临时变量self
var circle = {
    radius: 10,
    outerDiameter() {
        var self = this;
        var innerDiameter = function () {
            console.log(2 * self.radius);
        };
        innerDiameter();
    }
};
circle.outerDiameter(); // 打印20

outerDiameter函数是circle对象的方法,因此其this值就是circle对象。

如果能直接写this.radius多好啊,可惜不能这么写,因为内层函数innerDiameter并不会继承外层函数outerDiameter的this值。outerDiameter函数的this值就是circle对象,this.radius等于10。

解释:innerDiameter函数的this值不是circle对象,而是window,和变量不同,关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时this会指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。通常使用变量self来保存this。验证一下:

// innerDiameter函数中的this是window
var circle = {
    radius: 10,
    outerDiameter() {
        var innerDiameter = function () {
            console.log(this === window);
        };
        innerDiameter();
    }
};
circle.outerDiameter(); // 打印true

因此,如果直接在innerDiameter函数中使用this的话,并不能得到我们想要的结果:

// 使用普通函数
var circle = {
    radius: 10,
    outerDiameter() {
        var innerDiameter = function () {
            console.log(2 * this.radius);
        };
        innerDiameter();
    }
};
circle.outerDiameter(); // 打印NaN

于是,我们不得不使用一个临时变量self将外层函数outerDiameterthis值搬运到内层函数innerDiameter

.bind(this)

我们也可以使用.bind(this)来规避this变来变去的问题:

// 使用.bind(this)
var circle = {
    radius: 10,
    outerDiameter() {
        var innerDiameter = function () {
            console.log(2 * this.radius);
        };
        innerDiameter = innerDiameter.bind(this);
        innerDiameter();
    }
};
circle.outerDiameter(); // 打印20

但是,无论是使用临时变量self,还是使用.bind(this),都不是什么很简洁的方式。

总之,普通函数的this取值多少有点奇怪,尤其当我们采用面向对象的方式编程时,很多时候都需要用到this,大多数时候我们都不会去使用.bind(this),而是使用临时变量self或者that来搬运this的取值,一不小心就会蒙圈。

这时候箭头函数闪亮登场!!!✨

// 使用箭头函数
var circle = {
    radius: 10,
    outerDiameter() {
        var innerDiameter = () => {
            console.log(2 * this.radius);
        };
        innerDiameter();
    }
};
circle.outerDiameter(); // 打印20

对于内层函数innerDiameter,它本身并没有this值,其使用的this来自作用域链,来自更高层函数的作用域。innerDiameter的外层函数outerDiameter是普通函数,它是有this值的,它的this值就是circle对象。因此,innerDiameter函数中所使用的this来自outerDiameter函数,其值为circle对象。

理论小结,反复查看,后面读代码的时候方便回顾


欢迎喜欢前端的各位一起交流讨论❤️

你可能感兴趣的:(javascript,前端,面试)