理解 JavaScript 中的 this
前言
理解this
是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this
,你才能更加清晰地写出与自己预期一致的 JavaScript 代码。
本文是这系列的第三篇,往期文章:
什么是 this
消除误解
在解释什么是this
之前,需要先纠正大部分人对this
的误解,常见的误解有:
- 指向函数自身。
- 指向它所在的作用域。
关于为何会误解的原因这里不多讲,这里只给出结论,有兴趣可以自行查询资料。
this
在任何情况下都不指向函数的词法作用域。你不能使用 this
来引用一个词法作用域内部的东西。
this 到底是什么
排除了一些错误理解之后,我们来看看 this
到底是一种什么样的机制。
this
是在运行时(runtime
)进行绑定的,而不是在编写时绑定的,它的上下文(对象)取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录的其中一个属性,会在函数执行的过程中用到。( PS:所以this
并不等价于执行上下文)
this 全面解析
前面 我们排除了一些对于 this
的错误理解并且明白了每个函数的this
是在调用时被绑定的,完全取决于函数的调用位置。
调用位置
通常来说,寻找调用位置就是寻找“函数被调用的位置“,其中最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
下面我们来看看到底什么是调用栈和调用位置:
function foo(){
// 当前调用栈是:foo
// 因此,当前调用位置是全局作用域
console.log("foo");
bar(); // <-- bar 的调用位置
}
function bar(){
// 当前调用栈是 foo -> bar
console.log("bar");
}
foo(); // <-- foo 的调用位置
你可以把调用栈想象成一个函数调用链, 就像我们在前面代码段的注释中所写的一样。但是这种方法非常麻烦并且容易出错。 另一个查看调用栈的方法是使用浏览器的调试工具。 绝大多数现代桌面浏览器都内置了开发者工具,其中包含 JavaScript 调试器。
绑定规则
在找到调用位置后,则需要判定代码属于下面四种绑定规则中的哪一种,然后才能对this
进行绑定。 注意: this
绑定的是上下文对象,并不是函数自身也不是函数的词法作用域
默认绑定
这是最常见的函数调用类型:独立函数调用:
对函数直接使用而不带任何修饰的函数引用进行调用,简单点一个函数直接是func()
这样调用,不同于通过对象属性调用例如obj.func()
,也没有通过 new 关键字new Function()
,也没有通过apply
、call
、bind
强制改变this
指向。
当被用作独立函数调用时(不论这个函数在哪被调用,不管全局还是其他函数内),this
默认指向到Window
。(注意:在严格模式下this
不再默认指向全局,而是undefined
)。
示例代码:
function foo(){
console.log(this.name);
}
var name = "window";
foo(); // window
隐式绑定
函数被某个对象拥有或者包含,也就是函数被作为对象的属性所引用,例如obj.func()
,此时this
会绑定到该对象上,这就是隐式绑定。
示例代码:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
obj.foo(); // obj
隐式丢失:
大部分的this
绑定问题就是被“隐式绑定”的函数会丢失绑定对象,也就是说它会应用“默认绑定”,从而把this
绑定到Window
或undefined
上,这取决于是否是严格模式。
最常见的情况就是把对象方法作为回调函数进行传递时:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
var name = "window";
setTimeout(obj.foo,1000); // 一秒后输出 window
显式绑定
我们可以通过apply
、call
、bind
方法来显示地修改this
的指向。
关于这三个方法的定义(它们第一个参数都是接受this
的绑定对象):
apply
:调用函数,第二个参数传入一个参数数组。call
:调用函数,其余参数正常传递。bind
:返回一个已经绑定this
的函数,其余参数正常传递。
比如我们可以使用bind
方法解决上一节“隐式丢失”中的例子:
var obj = {
name : "obj",
foo : function(){
console.log(this.name);
}
}
var name = "window";
setTimeout(obj.foo.bind(obj),1000); // 一秒后输出 obj
new 绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的
this
。 - 如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象。
示例代码:
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
优先级
直接上结论:
new 绑定=显示绑定>隐式绑定>默认绑定
判断 this: 现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:
使用 new 绑定,
this
绑定的是新创建的对象。var bar = new foo();
通过
call
之类的显式绑定,this
绑定的是指定的对象。var bar = foo.call(obj2);
在某个上下文对象中调用(隐式绑定),this 绑定的是那个上下文对象。
var bar = obj1.foo();
如果都不是的话,使用默认绑定。
this
绑定到Window
或undefined
上,这取决于是否是严格模式。var bar = foo();
对于正常的函数调用来说,理解了这些知识你就可以明白 this 的绑定原理了。
this 词法
ES6 中介绍了一种无法使用上面四条规则的特殊函数类型:箭头函数。
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。(而传统的 this 与函数作用域没有任何关系,它只与调用位置的上下文对象有关)。
重要:
- 箭头函数最常用于回调函数中,例如事件处理器或者定时器.
- 箭头函数可以像
bind
一样确保函数的this
被绑定到指定对象 - 箭头函数用更常见的词法作用域取代了传统的
this
机制。
示例代码:
var obj = {
name : "obj",
foo : function(){
setTimeout(()=>{
console.log(console.log(this.name)); // obj
},1000);
}
}
obj.foo();
这在 ES6 之前是这样解决的:
var obj = {
name : "obj",
foo : function(){
var self = this;
setTimeout(function(){
console.log(console.log(self.name)); // obj
},1000);
}
}
obj.foo();
总结
总之如果要判断一个运行中函数的this
绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this
的绑定对象。
- 由 new 调用?绑定到新创建的对象。
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到
undefined
,否则绑定到全局对象。
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this
,具体来说,箭头函数会继承外层函数调用的 this
绑定(无论 this
绑定到什么)。这其实和 ES6 之前代码中的 self = this
机制一样。
(转载于:https://www.v2ex.com/t/529532#reply1)
理解 JavaScript 中的 this的更多相关文章
- 理解JavaScript中的原型继承(2)
两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...
- 深入理解JavaScript中创建对象模式的演变(原型)
深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...
- 深入理解JavaScript中的属性和特性
深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...
- 深入理解javascript中执行环境(作用域)与作用域链
深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...
- 【干货理解】理解javascript中实现MVC的原理
理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...
- 理解javascript中的策略模式
理解javascript中的策略模式 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效 ...
- 深入理解javascript中的立即执行函数(function(){…})()
投稿:junjie 字体:[增加 减小] 类型:转载 时间:2014-06-12 我要评论 这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是 ...
- 转载 深入理解JavaScript中的this关键字
转载原地址: http://www.cnblogs.com/rainman/archive/2009/05/03/1448392.html 深入理解JavaScript中的this关键字 1. 一 ...
- js架构设计模式——理解javascript中的MVVM开发模式
理解javascript中的MVVM开发模式 http://blog.csdn.net/slalx/article/details/7856769 MVVM的全称是Model View ViewMod ...
- 全面理解Javascript中Promise
全面理解Javascript中Promise 最近在学习Promise的时候,在网上收集了一些资料,发现很多的知识点不够系统,所以小编特意为大家整理了一些自认为 比较好的文章,供大家更好地学习js中非 ...
随机推荐
- 盒模型、position、float详解css重点汇总
元素类型 HTML 的元素可以分为两种: 块级元素(block level element) 内联元素(inline element 有的人也叫它行内元素) 两者的区别在于以下三点: 块级元素会独占一 ...
- 用什么方法给PDF添加页眉页脚
我们所看到的书本中都会设置好有页眉页脚,那么电子书想要添加页眉页脚要怎么操作呢,用什么方法可以在PDF中添加页眉页脚呢,今天就为大家分享一下,如何在电子文件中添加页眉页脚,想知道的小伙伴们就一起来看看 ...
- Windows Internals 笔记——作业
1.Windows提供了一个作业内核对象,它允许我们将进程组合在一起并创建一个“沙箱”来限制进程能够做什么.创建只包含一个进程的作业同样非常有用,因为这样可以对进程施加平时不能施加的限制. 2.如果进 ...
- C#软件监控外部程序运行状态
需要外挂一个程序,用于监控另一个程序运行状态,一旦检测到另一程序关闭,就触发一个事件做其他处理. 引用的类 1 using System.Diagnostics;//引入Process 类 声明 1 ...
- 一起学爬虫——使用Beautiful Soup爬取网页
要想学好爬虫,必须把基础打扎实,之前发布了两篇文章,分别是使用XPATH和requests爬取网页,今天的文章是学习Beautiful Soup并通过一个例子来实现如何使用Beautiful Soup ...
- Redis数据结构之quicklist
本文及后续文章,Redis版本均是v3.2.8 我们在使用Redis对外暴露的list数据结构时,给我们带来极大的便利性.其底层实现所依赖的内部数据结构就是quicklist. 我们先来回忆下list ...
- eclipse安装Spring的具体步骤
1.下载spring 官网下载需要jar包放在lib中 本人分享百度云jar 链接:https://pan.baidu.com/s/1iEMwBbTTCxuCNOEdprlGhg 提取码:e7tg 2 ...
- python-nmap的函数学习
简介 python-nmap是一个使用nmap进行端口扫描的python库,它可以很轻易的生成nmap扫描报告,并且可以帮助系统管理员进行自动化扫描任务和生成报告.同时,它也支持nmap脚本输出. 可 ...
- 下面为初学者分享一下SQL 数据库学习资料
一.基础 1.说明:创建数据库CREATE DATABASE database-name2.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备份 ...
- css样式的六种选择器
css常用的选择器有: 1.标签选择器: 标签选择器,这种选择器影响范围大,建议尽量应用在层级选择器中. 如: *{margin:0;padding:0} /* 影响所有的标签 */ div{co ...