this指向
JS中的this指向总结
为什么会出现this
首先在学习this到底指向谁之前,先要知道为什么要有this?实际上从某些角度来说,即便没有this也无伤大雅,我们先看个例子。
1 | const obj = { |
在这个例子中,我们会发现,即便不用this,也不影响我们代码的正常执行,但是如果有一天obj这个对象的名字发生了变化,那么内部的所有obj都要换成对应的名字,否则就找不到这个对象了,这样就会影响我们代码的正常运行。所以我们就需要一个对象,能够指向他自身,那么这个时候this就理所当然的出现了。
1 | const obj = { |
我们会神奇的发现代码照常运行,并没有任何问题,更神奇的是这个this还是动态绑定的,这也是this那么晦涩难懂的原因,他在不同的情况下会有不同的绑定规则。
this的绑定规则
以下绑定规则都是在非严格模式下
如果没有指明在什么环境下,默认都是在浏览器环境下
全局作用域下的this到底指向谁?
首先我们需要知道,this的指向和所处的环境有关,在浏览器下面和在node环境下指向的是不同的东西,具体有什么区别在实际案例中会指出,首先我们先看下面例子:
1 | console.log(this); |
我们只是单纯的输出一下this,会发现在浏览器下面运行这段js脚本输出的是window,而在node环境下输出的是一个空对象({}),首先在浏览器下,这个this指向浏览器的window对象,这没什么好说的,在node环境下为什么会指向一个空对象?那是因为node是commonjs的最成功的实践者,他内部使用commonjs的模式开发,他将每个文件都分成一个模块,而这个this就是使用了call绑定到一个变量thisValue,看下面这行图片

那么问题来了,this.exports有是一个什么东西呢?继续看下面这一张图片

显而易见,这个东西就是一个空对象。我的理解是这个空对象实际上就是module.exports,如果我的理解有误,可以随时联系提醒我。
默认绑定
1 | //定义一个函数 |
我们会发现这里的this在浏览器环境下指向window,而在node环境下这里的this指向global全局对象。其实在判断函数的this指向的时候,我们第一点需要判断的是他是不是一个独立的函数调用,如果是,那么都是指向window的。
看这个例子:
1 | let obj = { |
一样的,不用去管bar对象是谁给的,怎么给的,就看他是怎么调用的,前面赋值在写的花里胡哨,只需要永远记得,this是在执行的时候动态绑定的,只要你的调用的是一个单独的函数调用,那么这个this就是指向window。
隐式绑定
隐式绑定在实际开发中也很常见,是通过某个对象调用的,这个时候会进行一个隐式绑定。
函数和方法
在js中,方法和函数有什么区别?我们主要看这个函数是不是独立的,如果他是独立的,那么我们将他称之为函数,如若不然,我们将他称之为方法。举个例子,在对象中的函数我们通常称之为方法,因为他始终和这个对象联系在一起,调用也是通过这个对象调用的
看下面例子:
1 | function foo() { |
在执行foo函数的时候,通过obj对象调用的,所以this会动态绑定到obj对象上,这就是一个典型的隐式绑定的例子。所以还是那句话,一定要关注函数调用的位置,他怎么被调用的,this指向的关键在这个位置。
显式绑定
首先我们先看看在js中函数有几种调用方式
1 | function foo() { |
我们会发现上面三种调用方式,都会执行函数,那么下面两种方式存在的意义是什么?js发展至今,只要某个语法存在,那么必定有他存在的意义,不然早就被抛弃的。区别在于this指向的问题,下面两种调用方式我们可以手动的绑定一个对象作为this的指向,还有一种bind方法,也是绑定this的,但是他并不会执行这个函数,后续我会单独出一篇文章探讨call、apply和bind方法并且使用js自己实现一下这三个方法。
1 | let obj = { |
我们会发现使用call或者是apply的方式调用,输出的结果和直接调用是不用的,他们的this指向的是call函数的第一个参数。当然会有第二个参数,第二个参数也是这两个方法不同的地方。这里我们就不做探讨,一起放到后面专门讲这几个方法的文章中
小提示
如果call、apply、bind绑定了一个null或者是undefined,那么默认会绑定到window,应该是实现这几个函数的时候内部做了边界判断
new绑定
new操作符涉及到面向对象的概念,也是js实现面向对象的基础。首先对于new操作符在内部做了什么,我们就直接上结论,我们主要关注使用new操作符之后,我的this指向谁了?
1 | /* |
这里我们需要注意一个细节,通常情况下构造函数我们会以大写字母开头,用于和普通的函数做一个区分,因为他们都是用function定义的。果然,不遵守这个规范也无可厚非,从代码运行的层面来说,他并不会影响代码的正常运行,主要是加以区分方便日后的开发者和自己进行维护
this指向谁了
- 在内存中创建一个对象p1
- 将People的prototype赋值给p1._proto_
- 将创建出来的对象p1和构造函数内部的this进行一个绑定
- 处理内部代码
- return 这个对象出去(函数内部没有返回其他对象
new操作符我们主要关注this绑定的问题,在内部会将p1对象的this绑定到函数调用的this上面。
构造函数的this
1 | function Person(name) { |
内置函数的this
forEach
1
2
3
4
5
6
7
8
9
10
11
12//1. 只传递一个参数
let items = [1, 2, 3, 4, 5];
items.forEach(function (item) {
console.log(item, this); // 我们会发现这里的this指向的是window
});
//2. 传递两个参数
let items = [1, 2, 3, 4, 5];
items.forEach(function (item) {
console.log(item, this); // 我们会发现这里的this指向的是coderwei
},'coderwei');当然类似的方法也有map、filter等等,需要注意的是这里不能写成箭头函数,因为箭头函数不绑定this的。
setTimeout
1
2
3setTimeout(function () {
console.log(this); // window
}, 200);这个位置的this指向的也是window,但是这里有个特殊的点,看下面代码
1
2
3
4
5;
function foo() {
console.log(this);
}
foo(); //undefined在严格模式下函数内部的this指向的是undefined。但是放到setTimeout上,事情就变得不一样了。
1
2
3
4;
setTimeout(function () {
console.log(this); // window
}, 200);诡异吧,这个this指向的还是window,这个点笔者没有办法证明是如何实现的,如果有其他朋友有思路的可以指出。我的猜测是内部处理setTimeout的时候,对这个函数并不是直接执行,而是执行了一个bind方法,这个在翻阅资料的时候看到过一篇文章,在V8的测试文件中关于setTimeout的片段的注释中提到了一嘴bind。
this绑定的优先级
显式绑定vs隐式绑定
首先隐式绑定没有什么好说的,隐式绑定讲道理嘛,他就应该是最低的,他是内部自动绑定的,所以说只要你想改,无论用什么方式,都应该成功的改变this的指向
1 | function foo() { |
隐式绑定VS new绑定
1 | let obj = { |
显式绑定vs new绑定
1 | function foo() { |
这里需要注意一点,call和apply不能被new实例化,所以他们之间无法比较优先级
总结
- new绑定优先级大于显式绑定
- 显式绑定优先级大于隐式绑定
- 隐式绑定优先级大于默认绑定
箭头函数
箭头函数是ES6新增的一种新的编写函数的方式,他会比以往编写函数的方式更加简单。
- 箭头函数是不会绑定this的,他也没有arguments(在函数内部能够拿到的实参列表)
- 箭头函数不能当成构造函数来使用,因为他没有自己的this
因为箭头函数没有this,所以它不适用上面的几钟规则,如果箭头函数内部使用到了this,他就需要去到外层作用域去找this
1 | let obj = { |
分析几道面试题
第一道
1 | var name = "window"; |
这道题总的来说没多大难度,主要关注(person.sayName)()本质上还是通过person调用的,所以他的this还是指向person,(b = person.sayName)()这里他是一个自执行函数,也就是前面讲的独立的函数调用,所以说这里指向了window
第二道
1 | var name = "window"; |
分析一下每一行的函数调用,
- 在第一次调用的时候,我们直接通过一个对象去调用一个函数,这里会有一个默认绑定,所以结果是coderwei
- 第二次函数调用通过call的方法来调用,所以他会覆盖隐式绑定,以显示绑定为主,所以结果是person2
- 第三次函数调用是调用了一个箭头函数,箭头函数不绑定this,所以他回去外层作用域去找,外层作用域是全局,注意:对象没有作用域,不要把对象那个花括号当成一个作用域。
- 第四次函数调用通过call的方式来调用,但是咱们的箭头函数不绑定this,你给我,我还不要呢,所以他的this依旧去上层作用域找,所以还是window
- 第五次函数调用是返回了一个新的函数,实际上是拿到新的函数直接做一个调用,所以他就是一个独立的函数调用,结果显而易见,依旧是window
- 第六次函数调用是给foo3用call绑定了一个this,然后在执行内部返回的函数。但是我们只是给外部的函数绑定了一个this,关内部函数什么事情,本质上还是一个独立的函数调用,所以返回的还是一个window
- 第七次函数调用是先执行foo3函数,拿到返回的函数之后给这个函数绑定了this,所以结果是person2
- 第八次函数调用式是先调用foo4函数,然后拿到返回的函数后继续调用。但是需要注意的是返回的函数是一个箭头函数,所以他没有自己的this,他要去外层作用域找,他是被一个函数包裹的,函数是有作用域的,也就是说他拿到的是外层的函数的this,也就是我们前面进行隐式绑定的person2
- 第九次函数调用跟第八次一样,都是要去外层寻找this,不过这一次我们给外层的函数指定了this,是person2,所以他拿到的就是person2
- 第十次函数调用是拿到返回的箭头函数之后给这个箭头函数绑定this,箭头函数没有自己的this,也不能绑定this,所以说他依旧是去外层找,外层函数的this隐式绑定的person,也就是coderwei
所以说箭头函数有点像一个二百五,我不管,我就是没有,你给我我也不要,我就要去上层作用域要,他是什么我就拿什么作为我的this。









