JavaScript核心技术开发解密

针对JavaScript中的核心技术,结合前沿开发实践,对JavaScript 的内存函数,执行上下文,闭包,面向对象,模块等重点知识,进行系统全面的讲解和分析。
展开查看详情

1.avaScri t 阳波编著 穹手工营也版社 - Publishing House of Electronics Indus位y 北京 •BEIJING

2. 内容简介 本书针对 JavaScr明中的核心技术,结合前沿开发实践,对 JavaScript 的内存 、 函数、执行上 下文 、闭包 、面 向对象 、模块等重点知识,进行系统全面的讲解与分析 。 每一个知识点都以实际 应用为依托,帮助读者更加直观地吸收知识点,为学习目前行业里的流行框架打下坚实基础 。 本 书适合 JavaScript 初学者,有一定开发经验但是对于 JavaScript 了解不够的读者,以及开发经验 丰富但没有形成自己知识体系的前端从业者 。 未经许可,不得以任何方式复制或抄袭本书之部分或全部内容 。 版权所有, 侵权必究 。 图书在版编目( CIP )数据 JavaScript 核心技术开发解密 / 阳波编著. - 北京 :电子工业出版社, 2018.3 ISBN 978-7-121-33696-6 I ①J···II. ① 阳… III . ①JAVA 语言 一程序设计 IV. ①TP312 中国版本图书馆 CIP 数据核字( 2018 )第 029430 号 策划编辑:安娜 责任编辑 :安娜 印 刷: 三河市华成印务有限公司 装 订: 三河市华成印务有限公司 出版发行:电子工业出版社 北京市海淀区万寿路 173 信箱 邮编: 100036 开 本 : 787 ×980 1/16 印张: 14 . 5 字数 : 276 千字 版 次: 2018 年 3 月第 1 版 印 次: 2018 年 3 月第 1 次印刷 定价: 69.00 元 凡所购买电子工业出版社图书有缺损问题,请向购买书店调换 。 若书店售缺,请与本社发行 部联系,联系及邮购电话:( 010 ) 88254888' 88258888 。 质量投诉请发邮件至 zlts@phei.com.cn ,盗版侵权举报请发邮件至 dbqq@phei .com.cn 。 本书咨询联系方式 : 010-51260888-819, faq@phei . com . cn 。

3.目。再 在阅读这本书之前,不知道大家有没有思考过一个问题: 前端学习到底有没有捷径? 在我看来,学习的捷径并不是指不用付出多少努力就能够获得成功,而是在我们付出努力之 后,能够感觉到自己的努力没有白费,能够学到更多的知识,能够真正做到一分耕耘,一分收获 。 所以学习有没有捷径?我的答案是 : 一定有 。 其实大多数人并不是不想付出努力,而是不知道如何去努力,不知道如何有效地努力 。 我们 想要学好一个知识,想要掌握一 门技术,但是往往不知从何下手 。 前端的学习也是如此 。 也许上手简单的 HTML/CSS 知识,会给刚开始学习的读者一个掌握 起来很容易的印象 。 但是整个前端知识体系繁杂而庞大,导致大多数人在掌握了 一些知识之后, 仍然觉得自己并没有真正入门,特别是近几年,前端行业的从业人员所要掌握的知识越来越庞 杂,入门的门槛也越来越高,甚至进阶道路也变成了一场马拉松 。 也许在几年以前,我们只需会用 j Query 就可以说自己是一名合格的前端开发者,但是现在 的 JavaScript 语言 已 经不再是几年前那样 ,只需处理一些简单的逻辑就足够了 。 随着前后端分离 的方式被越来越多的团队运用于实践,用户对 UI 的要求越来越高,对性能的要求也越来越高, JavaScript 承载了更多的任务 。 虽然前端行业大热,但是我们的学习压力也随之倍增 。 所以我一直在思考,在这样的大环境背景 下,对于新人朋友来说,什么样的学习方式能让我 们的学 习效率更高? 或者说, 一本什么样的前端书籍才算是好书? 是将所有的前端知识按部就班地罗列出来告诉大家吗?肯定不是 。 很多书籍,以及各类官方文档其实都在做这件事 。 但是对于多数读者来说,把所有知识罗列 出来摆在眼前,并不是一个能够掌握它们的有效方式 。 这种学习方法不仅效率低下,而且学完之 后,也并不知道在实践中到底如何使用它们,我们其实是迷茫的 。 所以,如果有一本书,它在努力地向读者传递一种行之有效的学习方法,那么对于适合这种 学习方法的读者来说,就一定是一本好书 。 这就是我们这本书努力的方向 。

4.iv I 前言 凭借多年的工作经验 ,在长期写博客并与读者互动的过程中,我总结出了 一套适合大多数人 的学习方式,那就是: 围绕核心,渐进增强。 本书将整个 JavaScript 相关的知识点简单粗暴地划分为核心知识与周边知识。 周边知识的特点就是相对独立,不用非得学会了其他的知识点之后才能掌握它,也不用掌握 了它之后才能学习其他的知识 。 例如 Ajax ,如果仅仅只是想要使用它,那么用别人封装好的方 法,通过官方文档或者搜索引擎,只需要两分钟你就知道怎么使用 。 周边知识不会成为我们学习 的瓶颈 。 而核心知识不一样,核心知识是整个前端知识体系的骨架所在 。 它们前后依赖,环环相扣 。 例如,在核心知识链中,如果你搞不清楚内存空间管理,你可能就不会真正明白闭包的原理,就 没办法完全理解原型链,这是一个知识的递进过程 。 我们在学习过程中遇到的瓶颈,往往都是由 于某一个环节的核心知识没有完整掌握造成的 。 而核心知识的另一个重要性就在于,它们能够帮 助我们更加轻松地掌握其余的周边知识 。 @\· @@ @@ 所以,如果读者知道这条核心知识链到底是什么,并且彻底地掌握它们,那么你就已经具备 了成为一名优秀前端程序员的能力 。 这样的能力能够让你在学习其他知识点的时候方向明确,并 且充满底气 。 所以这本书的主要目的就在于帮助读者拥有这样的进阶能力 。 基于这个思路,这本书的呈现方式必定与其他图书不同 。 本书不会按部就班地告诉你如何声

5. 前言 I v 明变量、如何声明函数,不会罗列出所有的基础知识,对于基础知识的传授,《 JavaScript 高级编 程》已经做得足够好,因此没有必要重复做同样的事情 。 我会一步一步与大家分享这条完整的核 心链 。 我的期望是,当大家学完这本书中的知识后,能够对前端开发的现状有 一个大致 的了解, 知道什么知识是最有用的,什么知识是工作中需要的,拥有进一步学习流行前端框架的能力,拥 有在前端方向自主学习 、 自主进步的知识基础与能力 。 最后希望在这本书的陪伴下,大家能有一个愉快的、充实的学习历程 。 读者服务 轻松注册成为博文视点杜区用户( www.broadview.com.cn ),扫码直达本书页面 。 @ 下载资源: 本书如提供示例代码及资源文件,均可在下载资源处下载 。 @ 提交勘误: 您对书中内容的修改意见可在提交勘误处提交,若被采纳,将获赠博文视点杜 区积分(在您购买电子书时,积分可用来抵扣相应金额)。 @ 交流互动: 在页面下方读者评论处留下您的疑问或观点,与我们和其他读者一同学习交流 。 页面人口 : http : //www.broadview. eom . cn/33696

6. 目录 前言 Ill 1 三种基础数据结构 1 川战 1.2 堆 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 队列.............. . .... . ............... ... . . . . 4 2 内存 空 间 5 2.1 基础数据类型与变量对象.............. . . . . . . . . . . . . . 5 2.2 引用数据类型与堆内存空 间 . . ...... ~ . . . . . . . . . . . . . . . . . . . . 7 2.3 内存空 间管理 9 3 执行上下文 11 3.1 实例 I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.2 实例 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.3 生命周期.......................................... 18 4 变量对象 20 4. 1 创建过程 ..... . ..... . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2 实例分析 .. .. . ... . ................. . ... ....... . . ... 23 4.3 全局上下文的变量对象..... . .. ..... . ..... .. .. .. . .. ... ... 26

7. 目录 I vii 5 作用域与作用域链 27 5.1 作用域.......................... .. ............ . .. 27 5.1.1 全局作用域 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5.1.2 函数作用域 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5.1.3 模拟块级作用域 ..... .... . . . . . . . . . . . . . . . . . . . . . . 29 5.2 作用域链.. ....... ......... . . . . . . . . . . . . . . . . . . 31 6 闭包 33 6.1 概念. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 6.2 闭包与垃圾回收机制 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6.3 闭包与作用域链. . . . . . . . . .. . . . . . . . . . . . . . . . . .· . . . . . 39 6.4 在 Chrome 开发者工具中观察函数调用枝、作用域链与闭包. ........ . ... 41 6.5 应用闭包. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.5.1 循环 、 setTimeout 与闭包..... . ....................... 49 6.5.2 单例模式与闭包. ..... .. ......... ............. . . . 50 6.5.3 模块化与闭包.... .... .......... . .............. . 53 7 this 59 8 函数与函数式编程 67 8.1 函数... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 8.2 函数式编程.... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 8.2.1 函数是一等公民....... . ................ . . . . . . . 77 8.2.2 纯函数....... . .................. . . . . . . . . 80 8.2.3 高阶函数..... .. ... . ..... . . . . . . . . . . . . . . . . . . 85 8.2.4 柯里化................. . . . . . . . . . . . . . . . . . . . . 91 8.2.5 代码组合........ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

8.viii I 日录 9 面向对象 106 9.1 基础概念 .... .. ................. .. . . . . . . . . .. .. ... . 106 9.1.1 对象的定义 . . . . . . . . . . . . . . . . . . . . . . . . ... ... ... . 106 9.1.2 创建对象. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 9.1.3 构造函数与原型.... . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 9.1.4 更简单的原型写法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 9.1.5 原型链 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 9.1.6 实例方法、原型方法、静态方法 . . . . . . . . . . . . . . . . . . . . . . 117 9.1.7 继承 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 9.1.8 属性类型...... .... ........... . …. . . . . . . . . . . . 122 9.1.9 读取属性 的特性值 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 9.2 jQuery 封装详解... .. ...... . . . . . . . . . . . . . . . . . . . . . . . . . 127 9.3 封装一个拖曳对象............ .. ........... . . . . . . . . . . . 134 9.4 封装一个选项卡. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 9.5 封装无缝滚动 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 10 ES6 与模块化 159 10.1 常用语法知识.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 10.2 模板字符串. ... ..... .... . . . . . . . . . . . . . . . . . . . . . . . . . . . I 们 10.3 解析结构..... .... . .. ................ . . . . . . . . . . . . . 168 10.4 展开运算符.. .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 10.5 Promise 详解........ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 10 . 5.1 异步与 同步 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 10.5.2 Promise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 175 10.5.3 as严1c/await . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 10 . 6 事件循环机制 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 10 .7 对象与 class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 10 .8 模块化.. . . ....... . . . . . . . . . . . . . . . . . .. . . .... . ... 202 10.8.1 基础语法..... . . . . . . . . . . . . . . . ... ... .. ... . .. 204 10.8 .2 实例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .. 209

9. 三种基础数据结构 在 JavaScript 中,有 三种常用的数据结构是我们必须了解的,它们分别是棋( stack )、堆 (heap )、队列( queue )。 它们是理解整个核心的基础,在 JavaScript 中分别有不同的应用场景,因 此先来介绍它们 。 1.1 枝 当我们在学习中遇到楼这个名词时,可能面临的是不同的含义 。 如果没有理清不同的应用场 景,就会给我们的理解带来困惑 。 场景 l :棋是一种数据结构,它表达的是数据的一种存取方式,这是一种理论基础 。 场景 2 :钱可用来规定代码的执行顺序,在 JavaScript 中叫作函数调用钱( call stack ),它是 根据钱数据’结构理论而实现的一种实践 。 理解函数调用梭的概念非常重要,我们会在后续的章节 里详细说明 。 场景 3 :战表达的是一种数据在内存中的存储区域,通常叫作枝区 。 但是 JavaScript 作为一 门高级程序语言,并没有同其他语言那样区分校区或堆区,因此这里不做扩展 。 我们可以简单粗 暴地认为在 JavaScript 中,所有的数据都是存放在堆内存空间中的 。 这里需要重点掌握找这种数据结构的原理与特点,学习它的最终目的是掌握函数调用梭的运 行方式 。 下面可以通过乒乓球盒子这个案例来简单理解校的存取方式,如图 1 - 1 所示 。

10.2 I JavaScript 核心技术开发解密 先选后出,后边先出(LI FO) + 峭| 惶惶 帽圃 中 的 ,阜乓球 触 童子 !rl脑 ·.。c' 货比 4 图 1-1 乒乓球盒子 往图 1-1 所示的乒乓球盒子中依次放入乒乓球,当想要取出来使用的时候,处于盒子顶层 的 乒乓球 5 ,它一定是最后被放进去并且最先被取出来的 。 而要想使用底层的乒乓球 l ,则必须先 将上面的所有乒乓球取出来之后才能取出 。 但乒乓球 l 是最先放入盒子的 。 这种乒乓球的存取方式与校数据结构如出 一辙 。 这种存取方式的特点可总结为先进后出,后 进先出 ( LIFO , Last In, First Out )。 如图 1-1 右侧所示,处于找顶的数据位ue ,最后进楼 ,最先出 钱 。 处于战底的数据 l ,最先进枝,最后出拢 。 在 JavaScript 中,数组( Array )提供了两个校方法来对应械的这种存取方式,它们在实 践中 十分常用 。 push :向数组末尾添加元素(进技方法)。 push 方法可以接收任意参数,并把它们逐个添加到数组末尾,并返回数组修改后的长 度 。 var a = []; a . push(!); II a : [1] a . push(2, 4, 6); II a :口, 2, 4, 6] var 1 = a.push(5) ; II a: [1, 2, 4, 6, 5] 1 : 5 pop :弹出数据最末尾的一个元素(出枝方法)。 pop 方法会删 除数组最末尾的一个元素,并返回 。

11. 1 三种基础数据结构 I 3 var a = [1, 2, 3) ; a . pop(); II a: [1, 2) 11 a.pop ()的返回 结采为 3 1.2 堆 堆数据结构通常是一种树状结构 。 它的存取方式与在书架中取书的方式非常相似 。 书虽然整齐地摆放在书架上,但是只要知道 书的名字,在书架中找到它之后就可以很方便地取出,我们甚至不用关心书的存放顺序,即不用 像从乒乓球盒子中取乒乓球那样,必须将一些乒乓球拿出来之后才能取到中间的某一个乒乓球 。 图 1-2是 testHeap 示意图 。 图 1-2 testHeap 示意图 该示意图可以用字面量对象的形式体现出来 。 var ta -- 口unu eanr 俨 1 S +hw4A 曰: , b: 20,

12.4 I JavaScri pt 核心技术开发解密 c: { m: 100 , n: 110 } } 当我们想要访问 a 时,只需通过 testHeap.a 来访问即可,而不用关心 a 、 b 、 c 的具体顺序 。 1.3 队列 在 JavaScript 中,理解队列数据结构的目的是为了搞清楚事件循环( Event Loop )机制到底 是怎么回事 。 在后续的章节中会详细分析事件循环机制 。 队列( queue )是一种先进先出( FIFO )的数据结构 。 正如排队过安检一样,排在队伍前面 的人一定是最先过安检的人 。 队列原理如图 1-3所示 。 队列:先滋先出(AFO) 一辆··-囚曰曰曰囚『”+ ‘kJIJll•lll响 图 1-3 队列原理

13. 内存空间 因为 JavaScript 有垃圾自动回收机制,所以对于前端开发人员来说,内存空间并不是一个经 常被提及的概念,很容易被大家忽视 。 特别是很多非计算机专业的读者在进入前端行业之后,通 常对内存空间的认知比较模糊,甚至一无所知 。 但是内存空间却是真正的基础,这是我们进一步 理解闭包等重要概念的理论基石,所以非常有必要花费一点时间去了解它 。 2.1 基础数据类型与变量对象 最新的 ECMAScript 标准号定义了 7 种数据类型,其中包括六种基础数据类型与一种引用数 据类型( 0均 ect )。 其中基础数据类型表如表 2- 1 所示 。 表 2-1 基础数据类型表 类型 值 Boolean 只有两个值 :位ue 与 false Null 只有一个值 : null Undefined 只有一个值: undefined Number 所有的数字 String 所有的字符串 Symbol 符号类型 var sym = Symbol(‘ testsymbol ’)

14.6 I JavaScript 核心技术开发解密 由于目前常用的浏览器版本还不支持 Symbol ,而且通过 babel 编译之后的代码盘过 大,因此在实践中建议暂时不要使用 Symbol 。 下面来探讨一个问题,有一个很简单的例子如下所示 。 function fn() { var a1 = 10; var a2 =’ hello ’; var a3 = null; } 现在需要思考的是,当运行函数 fn 时,它其中的变量 al 、 a2 、 a3 都保存在什么地方? 函数运行时,会创建一个执行环境,这个执行环境叫作执行上下文( Execution Context ,我 们会在后续的章节详细介绍它)。 在执行上下文中,会创建一个叫作变量对象( VO ,后续章节详 细学习)的特殊对象 。 基础数据类型往往都保存在变量对象中,如图 2-1 所示 。 变’董事’民(VO) 变矗毡 .体健 。1 10 a2 'hello' aJ null 图 2-1 变量对象 变量对象也存在于堆 内存中 ,但是由于变量对象有特殊职能,因 此在理解时, 建议仍然将其 与堆内存空间区分开来 。

15. 2 内存空间 I 7 2.2 引用数据类型与堆内存空间 引用数据类型( Object )的值是保存在堆内存空间中的对象 。 在 JavaScript 中,不允许直接 访问堆内存空 间中的数据,因此不能直接操作对象的堆内存空间 。 在操作对象时,实际上是在操 作对象的引用而不是实际的对象 。 因此,引用数据类型都是按引用访问的 。 这里的引用,可以理 解为保存在变量对象中的一个地址,该地址与堆内存中的对象相关联 。 为了更好地理解变量对象与堆内存,下面用一个例子与图解配合讲解 。 function foo () { var al = 10; var a2 =’ hello ’; var a3 = null; var b = { m: 20 }; var c = [1, 2, 3]; } 如图 2-2所示,当我们想要访问堆内存空间中的引用数据类型时,实际上是通过一个引用(地 址指针)来访问的 。 囊’对a 雄内得空i回 变矗在 具体锦 。1 。 a2 a3 ’ hello' null θ c OX0012厅7 一 b Ox0012何2 一 图 2-2 变量对象地址指针 在前端面试题中,我们常常会遇到这样一个类似的题目 。

16.8 I JavaScript 核心技术开发解密 II demo0 1. j s var a = 20 ; var b = a; b = 30; // 这时 a 的位是多少 也 / fJV · s·· m nu Ja n4 。= ar 44nu ·b q4nu m 俨 、 、 , LFJ 4 var n = m; n .a = 15 ; // 这 llt m.a 的位是多少 在 demoOl 中,基础数据类型发生了 一次复制行为 。 在 d巳mo02 中,引用数据类型发生了 一 次复制行为 。 当变量对象中的数据发生复制行为时,新的变量会被分配到一个新的值 。 在 demoO I 中,通 过 var b =a 发生复制之后,虽然 α 与 b 的值都等于 20 ,但它们其实已经是相互独立互不影 响的 值了 。 因 此 当我们修改了 b 的值以后, α 的值并不会发生变化 。 具体如图 2-3所示 。 复制制 蟹’因后 b值修改后 货量对’k 变量~· 变.”’ I· I 20 HtE HtE 图 2-3 变量对象复制 在 demo02 中,通过 var n = m 发生了一次复制行为 。 引用类型的复制同样会为新的变量自动 分配一个新 的值并保存在变量对象中 。 但不同的是,这个新的值,仅仅只是引用类型的一个地址 指针 。 当地址指针相同时,尽管它们相互独立,但是它们指向的具体对象实际上是同一个 。

17. 2 内存空间 I 9 因此, 当修改 n 时, m 也会发生变化,这就是引用类型的特性 。 具体如图 2-4所示 。 ..”’e 袍””’重附 ’E瓢’曾 费罐’咱. 难内部安蝇 θ 图 2-4 变量对象引用复制 2.3 内存空间管理 因为自动垃圾回收机制的存在,使得我们在开发时好像并不用那么关心内存的使用问题,内 存的分配与回收完全实现了自动管理 。 但是根据笔者的开发经验,了解内存机制有助于自己清晰 地认知到自己写的代码在执行过程中都发生了什么,从而写出性能更加优秀的代码 。 因此在成为 更好的前端开发者的道路上,关心内存空 间管理是一件非常重要的事情 。 下面通过一个非常简单的例子来了解内存空间的使用过程 。 var a = 20 ; alert(a + 100); a = null; 上面的 三条语句,分别对应如下三个过程 。 @ 分配内存;

18.10 I JavaScri pt 核心技术开发解密 @ 使用分配到的内存; @ 不需要时释放内存 。 分配内存与使用内存都比较好理解,我们需要重点理解的是第 三个过程 。 这里涉及 JavaScript 垃圾回收机制的实现原理 。 JavaScript 的垃圾回收实现主要依靠“引用”的概念 。 当一块内存空间中的数据能够被访问 时,垃圾回收器就认为“该数据能够被获得” 。 不能够被获得的数据,就会被打上标记,并回收 内存空间 。 这种方式叫作 标记一清除算法 。 这个算法会设置一个全局对象,并定期地从全局对象开始查找,垃圾回收器会找到所有可 以 获得与不能够被获得的数据 。 因此在上面这个例子中,当我们将 a 设置为 null 时,那么刚开始分配的 20 ,就无法被访问到 了,而是很快会被自动回收 。 注意 : 在局部作用域中,当函数执行完毕后,局部变量也就没有存在的必要了,因此垃圾收 集器很容易做出判断并回收 。 但是在全局中,变量什么时候需妥自动释放内存空间则很难判断, 因此我们在开发时,应尽量避免使用全局变量 。 如果使用了全局变量,则建议不再使用它时,通 过 a= null 这样的方式释放引用,以确保能够及时回收内存空间 。

19. 执行J:P 文 JavaScript 代码在执行时,会进入一个执行上下文中 。 执行上下文可以理解为当前代码的运 行环境 。 JavaScript 中的运行环境主要包括以下三种情况 。 @全局环境:代码运行起来后会首先进入全局环境 。 @ 函数环境 : 当函数被调用执行时,会进入当前函数中执行代码 。 @ eval 环境:不建议使用,这里不做介绍 。 因此可以预见的是,在一个 JavaScript 程序中,必定会出现多个执行上下文 。 JavaScript 引 擎会 以榜的方式来处理它们,这个钱,就是前面多次提到的函数调用钱 。 函数 调用校规定了 JavaScri pt 代码的执行顺序 。 战底永远都是全局上下文,枝顶则是当前正在执行的 上下文 。 当 代码在执行过程中遇到以上几种情况时,都会生成一个执行上下文并放入函数调用战中, 处于战顶的上下文执行完毕之后,会自动出拢 。 为了更加清晰地理解整个过程,我们可以通过几个实例来了解函数调用梭的执行规则 。 3.1 实例 1 I I demo01. j s var color z ’ blue ’;

20.12 I JavaScript 核心技术开发解密 function changeColor () { var anotherColor =’ red ’; function swapColors () { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors () ; } changeColor () ; 我们用 ECStack 来表示处理执行上下文的堆拢 。 第一步全局上下文人钱,并一直存于找底, 如图 3-1 所示。 金属上下文入钱 ECStack Global Context 图 3-1 全局上下文入枝 第二步,全局上下文入战之后,从可执行代码开始执行 , 直到遇到 changeColor(),这句代码 激活了函数 changeColor ,从而创建 changeColor 自己的执行上下文,因而此时是 changeColor EC 的上下文人梭,如图 3-2所示 。

21. 3 执行上下文 I 13 changeColor EC入钱 ECStack changeColor EC Global Context 图 3-2 changeColo「 EC 的上下文入枝 第三步, changeColor EC 的上下文入校之后,开始执行其中的可执行代码,并在遇到 swap­ Colors()这句代码之后又激活了 swapColors 的执行上下文 。 因此第三步就是 swapColors EC 的上 下文入钱,如图 3-3所示 。 swapColor百 EC入校 ECStack swap臼tors EC , changeColor EC Global Context 图 3-3 swapColors EC 的上下文入枝 第四步,在 swapColors 的可执行代码中,没有其他能生成执行上下文的情况,因此这段代码 顺利执行完毕, swapColors 的上下文从战中弹出,如图 3-4所示 。

22.14 I JavaScript 核心技术开发解密 swapColors EC出钱 ECStack changeCofor EC Global Context 图 3-4 swapColors EC 出枝 第五步, swapColors 的执行上下文弹出之后,继续执行 changeColor 的可执行代码,没有再 遇到其他执行上下文,顺利执行完毕后弹出 。 这样, EC Stack 中就只剩下 全局上下文了,如图 3-5所示 。 changeColor EC出钱 ECStack Global Context 图 3-5 changeColor EC 出枝 最后,全局上下文在浏览器窗口关闭后出拢 。 注意:函数执行过程中遇到 return 能直接终止可执行代码的执行,因此会直接将当前上下文 弹出裁 。 整个过程如图 3 -6所示 。

23. 3 执行上下文 I 15 主m上下直入槐 。回119.Color ECλ锦 swapColors EC入幡 swapC。lors EC剧精 d咱俩,.Color EC出强 εCSUI由 ECSUlclt εCSUldt ECSUI由 ecs回dt 由angeColor EC changeC剧。r EC chong 旺。lorEC Global Context Global Conte晴 Global Cont1四t G阳bol c。ntext Global Context 图 3-6 全局 上 下文出 入枝 3.2 实例 2 11 demo02. j s function f1(){ var n =999 ; function f2 () { alert(n); 于 return f2; } var resul t=f 1 () ; result() ; 11 999 这是一个简单的闭包例子,整个例子具有一定 的迷惑性 。 但是我们只需要根据“函数执行时才会创建执行上下文 ” 这一原则来理解 ,那么这段代码执 行时的函数调用枝顺序就会 比较清晰 了 。 第一步 , 仍然是全局上下文先入拢 ,如 图 3-7所示 。

24.16 I JavaScript 核心技术开发解密 金属上下文入钱 岳CS tack Global Context 图 3-7 全局上下文入枝 第二步,全局代码在执行过程中,遇到了 fl ()函数,执行 var r e sul t=f 1 () ;,因此 fl 会创 建对应的执行上下文并入枝,如图 3-8所示 。 fl EC入钱 ECStack fl EC Global Context 图 3-8 fl EC 入枝 第三步,在 n 的可执行代码中,虽然声明了 一个函数口,但是并没有执行任何函数,因此 也就不会产生别的上下文,代码执行结束后, n 自然会出拢,如图 3-9所示 。

25. 3 执行上下文 I 17 f1 EC出钱 。 ECStack :> Global Context 图 3-9 fl EC 出枝 第四步, fl 出楼之后,继续执行全局上下文的代码,这个时候遇到了 result() , result()会创建 一个新的上下文,因此这个时候 result 的上下文入拢,如图 3-10所示 。 result EC入钱 ECStack result EC Global Context 图 3-10 result k 入枝 第五步,这个 result()其实就是在 n 中声明的函数口,因此这个时候会执行口中的代码 。 由 于在口中没有产生新的上下文,因此执行完毕后直接出钱,如图 3-11 所示 。

26.18 I JavaScript 核心技术开发解密 result EC出钱 ECStac.k Global Context 图 3 - 11 「esult EC 出校 完整过程如图 3 - 12所示 。 主用上下立λ幡 fl EC入蜡 flEC出银 ,回ult ECλ揭 呻钊It EC出“ ECStack ECStack ECStack ECStack 民Stack fl EC 帽副ltEC GlobalC。ntext Global Context GI。bal Context GI曲创 Con幢目 Global C。ntext 图 3-1 2 全局上下文出 入枝 3.3 生命周期 我们知道, 当一个函数调用时, 一个新的执行上下文就会被创建 。 一个执行上下 文 的生命周 期 大致可以分为两个阶段 : 创建阶段和执行阶段 。 创建阶段 在这个阶段,执行上下文会分别创建变量对象,确认作用域链,以及确定 th is 的指向 。 执行阶段

27. 3 执行上下文 I 19 创建阶段之后,就开始执行代码,这个时候会完成变量赋值、函数引用,以及执行其他可执 行代码,如图 3- 13所示 。 r ~成资·对徽、 r 变’E赋值 、 刨撞 < 确认作用罐罐 〉--一+执行 < 酬引用 ←-一-执行醉后出铺.鞠翩收一-+ l 确定this搁向 J l 执行英他代码 J 执行上下文笠命刷刷 图 3-13 执行上下文生命周期 从执行上下文的生命周期可以看到它的重要性,其中涉及了变量对象、作用域链、 this 等许 多重要但并不那么容易搞清楚的概念,这些概念有助于我们真正理解 JavaScript 代码的运行机制 。

28. 变量对象 在第 2 章内存空间中曾提到过变量对象( Variable Ob归ct ),我们在 JavaScript 代码中声明的 所有变量都保存在变量对象中,除此之外,变量对象中还可能包含以下内容 。 @函数 的所有参数( Firefox 中为参数对象 arguments )。 @当前上下文中的所有函数声明(通过 function 声明的函数)。 @当前上下文中 的所有变量声明(通过 var 声明的变量)。 4.1 创建过程 变量对象的创建,依次经历了以下几个过程 。 1. 在 Chrome 浏览器中,变量对象会首先获得函数的参数变量及其值;在 Firefox 浏览器中,是 直接将参数对象 arguments 保存在变量对象中 。 2. 依次获取当前上下文中所有的函数声明,也就是使用也nction 关键宇声明的函数 。 在变量 对象中会以函数名建立一个属性,属性值为指向该函数所在的内存地址引用 。 如果函数名 的属性已经存在,那么该属性的值会被新的引用覆盖 。 3. 依次获取当前上下文中的变量声明,也就是使用 var 关键字声明的变量 。 每找到一个变量声 明,就在变量对象中就以变量名建立一个属性,属性值为 undefined 。 如果该变盐名的属性 已经存在,为了防止同名的函数被修改为 undefined ,则会直接跳过,原属性值不会被修改 。

29. 4 变量对象 I 21 ES6 支持新的变量声明方式 leνconst ,规则与 var 完全不同,它们是在上下文的执行 阶段开始执行的,避免了变量提升带来的一系列问题,因此这里暂时先不详细介绍, 在之后的 ES6 相关章节再详细解读 。 知道了上面的规则后,我们来思考一个问题,当我们执行以下代码时,具体的执行过程是怎 样的呢? var a = 30; 首先上下文的创建阶段会先确认变量对象,而变量对象的创建过程则是先获取变量名井赋值 为 undefined ,因此第一步是: var a = undefined; 上下文的创建阶段完毕后,开始进入执行阶段,在执行阶段需要完成变量赋值的工作,因此 第二步是: a = 30; 需要注意的是,这两步分别是在上下文的创建阶段与执行阶段完成的 。 因此 var a = undefined 这一步其实是提前到了比较早的地方去执行,下面通过一个简单的例子来证明 。 console. log (a) ; II a 这个时候输出结采为 undefined var a = 30; 结合之前的理解,这个例子的实际执行顺序为: //创建阶段 var a = undefined; //执行阶段 console.log(a); a = 30;