谈谈函数内的this

本菜鸟在面试的过程中,曾经被问到一些有关this的问题,在面试官的反问下,我开始质疑自己对this到底了解多少?
(本文内容如有任何不正确,欢迎交流)

1.this是什么

根据MDN的说法:this为当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

注:本文所有代码都是在浏览器环境下运行(非严格模式下)

2.函数内的this指向谁?

2.1.普通函数内的this指向取决于谁最后调用了它

根据《javascript高级程序设计(第四版)》:在标准函数中, this 引用的是把函数当成方法调用的上下文对象(在网页的全局上下文中调用函数时, this 指向 windows)。

// from MDN
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"


/*
作为全局函数时的表现
*/
var name = "bd";
function T() {
   var name = "BD";
   console.log(this.name);          // bd
   console.log("T:" + this);    // T: Window
}
T();
console.log("t:" + this)         // t: Window
复制代码

T函数内部的this指向取决于谁最后调用了它,此时在全局对象 Window 上调用T函数,故 this->window ,所以 this.name==window.name ,输出bd

/*
作为对象方法时的表现
*/
var name = "BD";
var a = {
    name: "bd",
    fn : function () {
         console.log(this.name);      // "bd"
        },
    fn2 : function() {
         console.log(this)
        }
    }
a.fn();
a.fn2();
复制代码

结果如下:

image.png

将fn2改成以下后:

var name = "BD";
var a = {
    name: "bd",
    fn : function () {
         console.log(this.name);      // "bd"
        },
    fn2 : function() {
         return function(){
             console.log(this)       // "window......"
           }
        }
    }
a.fn();
a.fn2()();
复制代码

结果如下:

image.png

对象 a 调用了 fn2( )( ),此时为独立函数调用,此时 this 是指向 window 对象,如果想在该匿名函数内打印出 this ,可以使用闭包

fn2:function() {
   var _this=this
   return function(){
      console.log(_this)
   }
}
复制代码

结果如下:

image.png

再看看下面的代码:

var o = {
    prop: 'bd'
};
var prop = 'BD';
function fn() {
  return this.prop;
}
o.f = fn;
console.log(o.f()); // 'bd'
复制代码

在这里函数 fn( ) 虽然定义在全局作用域,可是 fn( ) 是作为对象 o 的 f 属性调用的,函数内的 this 指向取决于谁最后调用了它,故输出 “bd” 。

接着看:

o.b = {
    g: fn, 
    prop: 'bbdd'
};
console.log(o.b.g()); // 'bbdd'
复制代码

这里更加充分说明了普通函数内的 this 指向取决于谁最后调用了它,跟是不是谁的成员没有关系,最近的引用才是最重要的!

2.2.箭头函数内的this指向取决于定义箭头函数的上下文

根据《javascript高级程序设计(第四版)》:在箭头函数中, this 引用的是定义箭头函数的上下文

window.color = 'red';
let o = {
  color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
复制代码

很明显,定义 sayColor( ) 时,是在 window 上下文中定义的,故输出 red。

再看看阮一峰老师《ECMAScript 6 入门》中的例子:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
复制代码

匿名函数受 window 调用,并没有指向 Timer( )函数 ,而箭头函数中 this 指向定义时所在的作用域(Timer函数),故s1等于3

同样还有个来自《ECMAScript 6 入门》中的例子:

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}
复制代码

调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。–《ECMAScript 6 入门》

所以对象的属性建议是使用传统的写法来定义。

2.4.构造函数中的this

先来看看代码:

function Person (name) {
    this.name = name;
    this.printThis = function () {
        return this
    };
    this.sayName = function () {
        return this.name;
    };
}
var name='BD'
var person = new Person("bd");
person.sayName()
person.printThis()
复制代码

结果如下:

image.png

此时我们发现,构造函数 Person 内的 this 是指向刚刚创建的对象 person 的,其实这个跟 new 关键字的操作原理是息息相关的,我们来看看 new 操作符的工作过程

  1. 首先创建一个新对象 / var person = {}
  2. 接着将新对象的 proto 属性指向构造函数的原型对象 prototype
  3. 然后将构造函数内的 this 属性绑定到新对象中 / this -> person
  4. 然后执行构造函数内的代码,相当于给新对象绑定新属性
  5. 最后如果构造函数内有返回其他对象,该新对象就等于该对象,否则新对象就创建好了

根据以上的工作过程再结合上述的关于普通函数和箭头函数 this 指向的问题,我们理解构造函数内的 this 指向问题就轻而易举了。

2.3.apply、call和bind中的this

本文先不探讨,实质上其实是改变了被调用函数的this指向。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享