JS 里 “this” 的值详解

本文针对浏览器端的讲解

代码都是在 strict mode 下运行的,除非特别说明。关于英文单词的学习和记忆,大家可以访问这个网站。

里的 Value 部分可以得出,this 的值是 ‘A property of an execution context (global, function or eval)’,即是 execution context 的一个属性,在 strict mode 下总是一个 object,在 strict mode 下可以是任何类型。

也就是说,有 this 属性的地方就这三个地方。eval 用的比较少,在此忽略。global execution context 可理解为 global scope,function execution context 可理解为 function scope。

global execution context 的 this 属性在浏览器端就是指 window 了。

对于没有 this 属性的地方出现了 this

this 从外部有 this 属性的地方取,这就像是寻找一个变量的值一样 看这。比如数组里,下面的例子 thiswindow(取自 global execution context 的 this 属性)

let arr = [1, this];
console.log(arr[1]); // window

再比如对象里(下面代码片段包含左右两个例子)

let obj1 = {                     let obj2 = {
  name: this                       [this]: 'derek'
}                                }
// window                        // 'derek'                  
console.log(obj1['name']);       console.log(obj2[this]);

需要注意的是 this 是从创建的地方的外部有 this 属性的地方取的,而不是引用的地方的外部,比如下面例子

let arr = [1, this];
function func() {
  console.log(this); // undefined
  console.log(arr[1]); // window
}

func();

数组 arr 里的 this 值在创建 arr 的时候就已经确定了,不受引用 arr 的地方的限制。

 

接下来就剩函数里的 this 了。函数里 this 的值的影响因素比较多,主要包括以下几个方面

  1. 是普通函数还是箭头函数,
  2. 是否在 strict mode 下运行,
  3. 调用该函数时前面有没有加 . 或者 [](member access operators),可参考 The value of “this”
  4. 是否在 setTimoutsetInterval 的回调函数里,
  5. 有没有使用函数的 callapplybind 方法来绑定 this 的值。

下面分为普通函数和箭头函数两大类来展开。

普通函数里的 this

因为普通函数有 this 属性,所以普通函数里的 this 值不会从外部获取。

是否为 strict mode

下面的例子,如果是 strict mode 那么 func 函数里的 this 就为 undefined,如果不是 strict mode 就是全局对象 window

function func() {
  console.log(this); // undefined if strict mode, window otherwise
}

func();
调用该函数时前面有没有加 member access operator

当使用了 member access operator 调用该函数时,遵从 object before dot 规则,即该函数里的 this 值为调用该函数的对象,this 值是动态的,参考。不受是否为 strict mode 的影响

let obj = {
  name: "josh",
  func: function () {
    console.log(this === obj); // true
  }
};

obj.func()

再比如下面的例子里,数组 arr 里的 this 取自函数 func,而函数 func 的 this 属性值就是调用该函数的对象 obj

let obj = {
  name: "josh",
  func() {
    let arr = [1, this];
    console.log(arr[1] === obj); // true
  }
};

obj.func();

当与浏览器端的监听事件结合时的判断也一样

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>csdn blog</title>
  </head>
  <body>
    <div id="elem">click me</div>
    <script>
      let elem = document.getElementById("elem");
      elem.onclick = function () {
        console.log(this); // div#elem,遵从 object before dot 规则
      };
    </script>
  </body>
</html>

或者监听事件直接内嵌于 html 元素里时,this 就指那个元素

<div id="elem" onclick="console.log(this); // div#elem">click me</div>

当浏览器处理这个元素时,会生成 elem.onclick = function() { console.log(this); },参考,变成了上一个例子的写法。

另外,就是监听事件用 addEventListener 方法写的时候,addEventListener 回调函数里 this 的值指调用 addEventListener 的对象,结果最终也是一样

elem.addEventListener("click", function() {
  console.log(this); // div#elem
});

所以这三种事件监听的写法对应函数里 this 的值是一样的,参考

是否在 setTimout()setInterval() 的回调函数里

当是在 setTimoutsetInterval 的回调函数里时,this 指全局对象 window,不受是否为 strict mode 的影响

function func() {
  console.log(this); // window
}

setTimeout(func, 0);

因为这两个方法都属于全局对象 window 里的,也常写成

function func() {
  console.log(this); // window
}

window.setTimeout(func, 0);

注意当是在 js 内置对象(build-in objects)的回调函数里时,比如 array.forEach() 里,不会影响 this 值的判断。下面例子第一个 this 的值跟前面的 ‘是否为 strict mode’ 判断一样

function func() {
  console.log(this); // undefined
  setTimeout(function() {
    console.log(this); // window
  },0);
}

[1].forEach(func);

当 func 函数是在一个 object 里时,比如下面的情况

let obj = {
  name: "josh",
  func: function () {
    console.log(this); // undefined
    setTimeout(function() {
      console.log(this); // window
    },0);
  }
};

[1].forEach(obj.func);

结果是一样的。注意这里只是引用了函数 obj.func,执行 obj.func 的是 [1].forEach 函数。可以参考

有没有使用函数的 call、apply、bind 方法

如果是使用了函数的 callapplybind 方法,那么这个普通函数里 this 的值就是绑定的 thisArg(在 strict mode 下为 thisArg 的对象形式)

function func() {
  console.log(this); // "a string"
};

func.call("a string");

下面的例子即使该函数前面使用了 member access operator、即使在 setTimout() 的回调函数里,结果都是绑定的 thisArg

function func() {
  console.log(this); // 123
};

let obj = {
  name: 'josh',
  func
}

setTimeout(obj.func.bind(123), 0);

箭头函数里的 this

箭头函数比较特殊,它是没有 this 属性的,this 的值始终为创建这个箭头函数的时候从外部有 this 属性的地方获取的,不受其它因素的影响,跟前面讲的 对于没有 this 属性的地方出现了 this 的判断一样。

下面例子箭头函数 func 的 this 始终为 window,即使进行了 func.call('a string'); 想绑定 this 的值

let func = () => {
  console.log(this); // window
};

func.call('a string');

即使使用了 . 或者 [](member access operator)

let obj = {
  name: "josh",
  arrFunc: () => {
    console.log(this); // window
  }
};

obj.arrFunc();

有的同学可能会问箭头函数 arrFunc 是在 obj 里创建的,this 从外部获取为啥不是 obj?原因很简单,因为 obj 不是一个 execution context 并没有 this 属性。

箭头函数除了没有 this 属性外,它还没有特殊变量 arguments 和类继承里的 super.

补充

定义在全局作用域的函数自动成为全局对象 window 的属性,参考

所以下面例子当调用 func 前面加了 window 的时候,func 里 this 的值就为 window

function func() {
  console.log(this); // window
}

window.func();

this 在构造函数和 class 里的值

构造函数里的 this

构造函数严格来说也是普通函数,不过有两处不同 (看)

  1. 命名的时候首字母通常大写,
  2. 仅能使用 new 操作符来调用。

当一个构造函数被 new 后,会在该构造函数里创建一个空对象并赋给 this,该构造函数里的代码主要用来修改 this 比如添加新的属性,最后返回 this

function Town(population) {
  this.population = population;

  this.addOnePerson = function () {
    this.population++;
  }
}

let myTown = new Town(100000);

console.log(myTown.population); // 100000
myTown.addOnePerson();
console.log(myTown.population); // 100001

函数 Town 等同于

function Town(population) {
  // this = {};  (隐式声明)

  // 向 this 添加新的属性
  this.population = population;

  this.addOnePerson = function () {
    this.population++;
  }

  // return this;  (隐式声明)
}

因此构造函数里的 this 属性就是构造函数被 new 后生成的实例对象。

class 里的 this

class 也是一类函数

class Town {
  constructor(population) {
    this.population = population;
  }
}

console.log(typeof Town); // function

关于 class 的语法可参考: class is a function, or, more precisely the constructor method.

简单说, class Town 里的 constructor 类似于上面讲的构造函数,constructor 里的 this 属性是 classnew 后生成的实例对象,class 里的方法 (method) 储存于 Town.prototypeclass 里的字段 (field) 储存在 Town 被 new 后生成的实例对象里即 this 里。实例也可以使用 Town.prototype 对象里的 method 和 constructor 属性,可参考 F.prototype

class Home {
  obj = { // class field or property
    a: "obj",
    bar: () => {
      console.log("bar's this: ", this);
    }
  };
}

let home = new Home();

home.obj.bar();

因为 class 里的字段直接储存于被 new 后生成的实例里,所以 bar’s this 就是 home 实例。创建 obj 是 classnew 生成实例的时候。

另外,注意 class 里的 method with computed name 情况,computed name 里的 thisclass 外部取,如果是在全局作用域下就是 window 了,比如下面的例子

// initialization order: class fields then constructor
class User {
  that = console.log(this) || 'derek'; // 打印的是实例 user, 一个 empty object
  // now, this = { that: 'derek' };
  name = console.log(this) || this.that; // 打印 { that: 'derek' }. name is 'derek'

  name = console.log(this) || this.getName(); // 打印 { that: 'derek', name: 'derek' }.
  // After assignment, name is 'new derek'

  constructor(name) {
    console.log(this.name); // 'new derek'
    name ? this.name = name : ''; // update name field based on parameter name
    console.log(this.name); // josh
    console.log("constructor this", this); // 实例 with 最新的 class fields
  }

  getName() {
    return 'new derek';
  }
  
  [this]() { // this is the global object window
    console.log("inside method 'this'", this); // 实例 user (the instance user)
  }
}

let user = new User('josh');

user[this](); // this is the global object window

我们知道 console.log() 的参数里不管写什么,console.log() 都返回 undefined,所以第三行 that = 'derek'.

class static methods 里的 this

可参考

class User {
  static staticMethod() {
    console.log(this === User);
  }
}

User.staticMethod(); // true

遵从 object before dot 规则。

前面提到了 “class is a function, or, more precisely the constructor method”,所以在静态方法里可以进行下面的操作

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    return new this("Today's digest", new Date());
  }
}

let today = Article.createTodays();
console.log(today.title); // "Today's digest"

本篇文章至此就结束了,喜欢的给个赞吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值