前言

要正确理解this,首先得理解执行上下文,这里推荐汤姆大叔的执行上下文,因为this是在运行代码时确认具体指向谁,箭头函数除外。

全局作用域中的this

node: 每个javaScript文件都是一个模块,this指向空对象(module.exports

this.a = 1;
console.log(this, module.exports);
// { a: 1 } { a: 1 }

当然也有些意外,比如下面这种情况:

this.a = 1;
module.exports = {}
console.log(this, module.exports);
// { a: 1 } {}

浏览器端: this指向window

函数作用域中的this

这里分为两种,一种是全局作用域下直接执行函数,另外一种是被当作某个对象的属性的时候执行。eval的情况这里不作讨论。

全局环境下执行
function foo() {
console.log(this); // 此时的执行上下文为全局对象
}
foo();
// node global, 浏览器 window

当然严格模式下有不同,具体区别如下:

严格模式

this指向undefined(node and 浏览器端)

非严格模式

浏览器端: this指向全局变量window

node: this指向global

被当作属性调用

当函数作为一个对象的属性时,node和浏览器端一致,指向调用该属性的对象

var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
} obj.foo();
// { name: 'foo', foo: [Function: foo] }

接下来,做一些升级。

var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
} var objA = obj.foo; objA();
// node环境指向global,浏览器端指向window,严格模式下均指向undefined
--------------------------------------------------------------
var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
} var objA = {
name: 'objA',
foo: obj.foo
}; objA.foo();
// { name: 'objA', foo: [Function: foo] }
call、apply、bind

如果想手动更改函数里的this指向,可通过上述3个方法。callapply会立即执行,bind则返回一个绑定好this指向的函数。

var obj = {
name: 'foo',
foo: function foo() {
console.log(this);
}
} var objA = {
name: 'objA',
foo: obj.foo
}; obj.foo.call(objA); // 将this指向objA
obj.foo.apply(objA);
obj.foo.bind(objA)(); // bind函数会返回一个绑定好this的函数,可供以后调用
/**
{ name: 'objA', foo: [Function: foo] }
{ name: 'objA', foo: [Function: foo] }
{ name: 'objA', foo: [Function: foo] }
*/

这里对上述3个方法进行更细的说明,方便更好的理解之间的差异。

var obj = {
name: 'foo',
foo: function foo() {
console.log(this, arguments); // 通过arguments对象访问函数传入的参数列表,类似数组但不是数组,可通过arguments[0]访问到传入的Tom
}
} var objA = {
name: 'objA',
foo: obj.foo
}; obj.foo.call(objA, 'Tom', 'Jerry');
obj.foo.apply(objA, ['Tom', 'Jerry']);
obj.foo.bind(objA, 'Tom', 'Jerry')(1);
/**
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry' }
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry' }
{ name: 'objA', foo: [Function: foo] } [Arguments] { '0': 'Tom', '1': 'Jerry', '2': 1 } 可以看到call和bind是按序列传参,而apply是按数组传参,bind不会更改传参的顺序
*/
new构造

当函数被当作构造函数调用时,this指向构造的那个对象。

注:new调用中的this不会被callapplybind改变。

接下来,简单验证一下,由于callapply会立即执行,无法被当作构造函数,只能选择bind

function Foo() {
console.log(this);
}
var foo = Foo.bind({ name: 'Tom' });
foo();
// { name: 'Tom' }
new foo();
// Foo {}

箭头函数中的this

this在定义时,就已经知道其具体指向,因为在运行到声明的箭头函数时,会将this进行强绑定到外部作用域中的this,且无法更改。可以理解为继承了外部作用域中的this。由于箭头函数的this是确定的,无法更改,因此也无法被当作构造函数调用。

外部作用域为全局作用域:

var foo = () => {
console.log(this);
}
this.a = 1;
foo();
// 或者下面代码
var obj = {
name: 'obj',
foo: () => {
console.log(this);
}
}
var foo = obj.foo;
obj.foo();
foo();
foo.call({ name: 'Tom' });
/**
因为obj是在全局作用域下被定义,所以外部作用域为全局对象
node: 指向module.exports
浏览器:指向window
*/

外部作用域为函数作用域:

function foo() {
var a = () => {
console.log(this); // 继承外部作用域foo函数的this
};
a();
}
foo();
foo.call({ name: 'foo' });
new foo();
/**
这里foo函数中的this并不确定,由于调用方式不同,其this指向也不同
*/

相信写ES6类的情况很多,本人经常写React类组件,刚开始初学者会好奇为什么在类组件里写方法时要用bind或者箭头函数来强绑定this。因为一般类组件里的方法,都会设计到this的处理。比如事件处理函数,当触发相应事件时,调用事件对应的处理函数,此时访问到的thisundefined(ES6默认类与模块内就是严格模式),这就导致不能正确处理该组件的状态,甚至出错(处理函数内可能调用this.setState方法)。所以在类组件内部声明方法时会需要我们进行强绑定。

接下来我们看看React组件渲染流程:new构造一个组件实例instance,然后调用其render方法进行渲染和事件绑定。new构造的过程,this已经确定指向构造的组件实例,所以你可以在constructor进行bind或直接使用箭头函数,这样函数内部this就绑定到了instancerender函数里之所以能正常访问this,是因为以instance.render()进行渲染。

当然这里不特指React类组件,只要是ES6类,只能用new构造调用,否则会报错,所以ES6类里this指向是确定的,可以放心使用箭头函数。

迷惑的代码

还有一个比较迷惑的地方,遇到的机会很少,代码如下:

(function(){
console.log(this); // 运行结果和全局作用域下执行结果一致
})();
// 由于没有以对象属性的方式调用,则被认为是全局环境下调用
--------------------------------------------------------
(function(){
console.log(this);
}).call({ name: 'Hello World' });
// { name: 'Hello World' },this指向可以被改变
--------------------------------------------------------
new (function(name){
this.name = name;
console.log(this);
})('Tom');
// { name: 'Tom' },this指向新创建的对象

更好的阅读体验在我的github,欢迎

this解惑的更多相关文章

  1. [C#解惑] #2 对象的初始化顺序

    谜题 在上一篇C#解惑中,我们提到了对象的初始化顺序.当我们创建一个子类的实例时,总是会先执行基类的构造函数,然后再执行子类的构造函数.那么实例字段是什么时候初始化的呢?静态构造函数和静态字段呢?今天 ...

  2. [C#解惑] #1 在构造函数内调用虚方法

    谜题 在C#中,用virtual关键字修饰的方法(属性.事件)称为虚方法(属性.事件),表示该方法可以由派生类重写(override).虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态 ...

  3. Python 包管理工具解惑

    Python 包管理工具解惑 本文链接:http://zengrong.net/post/2169.htm python packaging 一.困惑 作为一个 Python 初学者,我在包管理上感到 ...

  4. 《浅谈磁盘控制器驱动》,磁盘控制器驱动答疑解惑![2012.1.29完结]by skyfree

    <浅谈磁盘控制器驱动>,磁盘控制器驱动答疑解惑![2012.1.29完结]  https://www.itiankong.net/thread-178655-1-1.html Skyfre ...

  5. SAE上传web应用(包括使用数据库)教程详解及问题解惑

    转自:http://blog.csdn.net/baiyuliang2013/article/details/24725995 SAE上传web应用(包括使用数据库)教程详解及问题解惑: 最近由于工作 ...

  6. 【解惑】Java动态绑定机制的内幕

    在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码? 这里面到底有什么内幕呢? 这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto ...

  7. word wrap 解惑

    源起 我们经常需要“修复”一个老生常谈的“bug”,那就是文本的自动换行问题.在专业术语上,这种期望得到的渲染现象被称作“word wrap”,即文本处理器有能力把超出页边的整个词自动传到下一行. 在 ...

  8. (译)iOS Code Signing: 解惑

    子龙山人 Learning,Sharing,Improving! (译)iOS Code Signing: 解惑 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切 ...

  9. C#中的 ref 传进出的到底是什么 解惑篇

    今天在浏览博文时,看到这篇文章:C#中的ref 传进出的到底是什么 ? 在传对象时使用ref的疑问 另附言: 本文写于早上,就在想发布的那瞬间,靠,公司断网了,原来修改的部分丢失了. 网一断就是一天了 ...

  10. 【解惑】深入jar包:从jar包中读取资源文件

    [解惑]深入jar包:从jar包中读取资源文件 http://hxraid.iteye.com/blog/483115 TransferData组件的spring配置文件路径:/D:/develop/ ...

随机推荐

  1. 【题解】Counting D-sets(容斥+欧拉定理)

    [题解]Counting D-sets(容斥+欧拉定理) 没时间写先咕咕咕. vjCodeChef - CNTDSETS 就是容斥,只是难了一二三四五\(\dots \inf\)点 题目大意: 给定你 ...

  2. 基础学习笔记之opencv(6):实现将图片生成视频

    基础学习笔记之opencv(6):实现将图片生成视频 在做实验的过程中.难免会读视频中的图片用来处理,相反将处理好的图片又整理输出为一个视频文件也是非经常常使用的. 以下就来讲讲基于opencv的C+ ...

  3. 流畅python学习笔记:第二十章:属性描述符:

    在前面一章中介绍了@property的用法,但是存在一个问题,如果我有多个属性想转变成property特性,那不是针对每个都需要实现一个 @propery.setter 和 @property.get ...

  4. (Android)react-native-splash-screen实践-解决react-native打包好后启动白屏的问题

    1.安装 npm i react-native-splash-screen --save or yarn add react-native-splash-screen --save 2.自动配置 re ...

  5. 微信小程序配置详解

    在之前已经通过微信公众平台的官方网站https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/devtools.html,注册好小程序并且登录成功后(这里主 ...

  6. Map总结--HashMap/HashTable/TreeMap/WeakHashMap使用场景分析(转)

    首先看下Map的框架图 1.Map概述 1.Map是键值对映射的抽象接口 2.AbstractMap实现了Map中绝大部分的函数接口,它减少了“Map实现类”的重复编码 3.SortedMap有序的“ ...

  7. Java for LeetCode 132 Palindrome Partitioning II

    Given a string s, partition s such that every substring of the partition is a palindrome. Return the ...

  8. SNMP服务安装 Centos6.8环境网络安装

    概念: snmp是英文“Simple Network Management Protocol”的缩写,意为:简单网络管理协议. snmp是目前最常用的环境管理协议. snmp被设计成与协议无关的. s ...

  9. 从mediaserver入手快速理解binder机制(最简单理解binder)【转】

    本文转载自;https://blog.csdn.net/u010164190/article/details/53015194 Android的binder机制提供一种进程间通信的方法,使一个进程可以 ...

  10. ping: sendto: Network is unreachable【转】

    本文转载自:http://blog.sina.com.cn/s/blog_640531380102wmzb.html 在我的板子上ping路由上的IP的时候可以ping通,但是ping外网的IP的时候 ...