1

TL;DR: this 指向调用该方法的对象,只有函数执行时,this 才有定义。

关于 JavaScript 中 this 的坑大家都踩过。像本文开头的这句话,道理你都懂,但是……所以这里就总结了几个 this 最常用的使用场景。

全局环境

在浏览器的全局环境中,this === window 。
但是,当使用了 'use strict'; 进入严格模式时,this === undefined 。

如果是在 nodejs 环境中,全局对象会更复杂一些,因为它有两种执行方式。一个是命令行方式,即输入

$ node

进入类似于浏览器的控制台一样的界面,可以逐行执行代码。那这里的 this 和 global 是严格相等的。但如果是

$ node program.js

这样执行一个文件的话,nodejs 会为每个文件创建一个自执行匿名函数的块,这里面的 this 并是全局对象 global 。但是,如果声明变量时没有加 var 的话,这些变量还是会加到 global 上去。

函数调用

函数中的 this 可能更常见一点吧。

function foo() {
  console.log(this.name);
}

如果直接判断这里的 this 是全局对象的话,就太冲动了(还记得最开始这句话吗?只有当函数调用时才能判断 this 真正引用的对象是什么)。

如果它作为一个全局函数【foo()】,或者闭包【return foo;】,又或者是回调函数【other(foo)】的话,那么它在执行时就是全局对象了。

还有三个我们经常遇到的方法可以改变 this 的引用,就是 call、apply 和 bind 。

foo.call(thisArg);

那么这里的 this 就指向了 thisArg,但当它为 null 或者 undefined,this 会指向全局对象。

还有一种函数调用是使用 new 关键字。

new foo();

这里的 this 指向的是构造出来的新对象。

那么其他情况就必然是 x.foo() 类似的调用方式了,看到这条语句的时候再去看 foo 的定义,它里面 this 引用的就是对象 x 。
数学学得好的话,可以反应过来 x 可以表示任何对象,比如 a,a.b,或者其他更复杂的表达式。

好,来测试一下,有如下代码

var o = {
  foo: function() { return this; }
};

function bar() {
  return o.foo;
}

console.log(bar()());

结果是什么?反正就两个选择,一是 this 指向全局对象,二是指向 o 。

原型

在上一篇文章 《继承的实现方式及原型概述》 中其实有提到过一部分原型方面的知识,那这里就稍微深入一点。

这里介绍的是通过 new 创建对象时的 this 。每个函数对象(用 function 关键字修饰的变量)自带 prototype 属性,在 prototype 上定义的属性都会继承给 this 。这些属性被每个实例共享,实例中会创建所有 prototype 上的属性,值为这些属性的引用。

文字一多心里难免烦烦的……

图片描述

首先,这里通过 new 创建了两个实例,它们有相同的 prototype 。原型中的数据是创建在堆上的,所以继承下来的属性和方法都会指向同一个引用(左边的箭头)。

然后,在 this 上定义的属性和方法是创建在栈上的,所以这些属性和方法会有各自独立的内存区域(右边的箭头)。

假如在 this 上定义的变量与 prototype 上冲突了,那么 prototype 中的那个变量会“隐藏”起来。

如果有 child.foo = 'tom'; 那么思考一下怎样才能让 child.foo 重新获得 prototype 中的 foo 值?

一种是可以直接 Parent.prototype.foo 就完事了。另外一种可以通过

delete child.foo; 
console.log(child.foo); // 重新获得 prototype 中的 foo 值

前者的缺点在于,假设 foo 是个函数的话,那么 Parent.prototype.foo() 时,其中的 this 指向的是 Parent.prototype,而不是 child 实例。

很多道理都很简单,那么再来考一下……

var slice = ___?

slice({'0': 'a', '1': 'b', 'length': 2}) => ['a', 'b']

简单来说就是用一个表达式定义一个变量(函数),它可以将类数组对象(比如说 arguments)转化成数组。提示一下,数组对象的 slice 函数本身就是有这个功能的,也就是说 Array.prototype.slice.call(arguments) 可以达到要求。

DOM 事件

听说长篇大论不会受欢迎,但是我仔细想想还是得把这部分写下来。

事件有两种记法,一个是

el.addEventListener('event', handler);

attachEvent 也是类似,那么在 handler 中出现的 this 表示触发该事件的元素,也就是 el 。

另一种是

el.onevent = handler;

同样,handler 中的 this 还是 el 。

最后一个题……

var o = {
  foo: function() { return this; }
}

document.onload = o.foo;

请问,当 load 事件触发时,这里的 this 是什么?三个选择:o, window, document.

小结

从这里开始是“广告”时间了……

其实 this 的讨论可以展开很多,可能上面记的内容中有很多欠缺的地方,这个希望大家可以指正。

那我们的知识库总是会随着学习和努力慢慢扩大的,个人能力是一方面,花的精力是另一方面。只要肯静下心去琢磨,很多“网上各种人说这个难那个难”的知识(比如说闭包、原型,或者和知识面广度有关的,比如说数组中 slice 的高级用法等),花点时间总会搞懂的。

所以学习没有捷径,也没有培训班,有心就可以了。


名一
2.9k 声望523 粉丝