前端入门17-JavaScript进阶之作用域
声明
本系列文章内容全部梳理自以下几个来源:
- 《JavaScript权威指南》
- MDN web docs
- Github:smyhvae/web
- Github:goddyZhao/Translation/JavaScript
作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。
PS:梳理的内容以《JavaScript权威指南》这本书中的内容为主,因此接下去跟 JavaScript 语法相关的系列文章基本只介绍 ES5 标准规范的内容、ES6 等这系列梳理完再单独来讲讲。
正文-作用域
在 ES5 中,变量的作用域只有两类:
全局作用域
函数作用域
只要不是在函数内部定义的变量,作用域都是全局的,全局的变量在哪里都可以被访问到,即使跨 js 文件。
函数作用域是指在函数体定义的变量,不管有没有在函数体的开头定义,在函数体的任何地方都可以被使用,因为 JavaScript 中的变量有声明提前的行为。
函数作用域需要区别于 Java 语言中的块级作用域:
var i = 0;
function A() {
console.log(i); //输出undefined
for (var i = 0; i < 1; i++) {}
console.log(i); //输出1
}
在 Java 中,类似的代码,在 for 循环前后输出的 i 都会是 0,因为都会使用成员变量 i,for循环内定义的 i 由于块级作用域限制,只在for 循环的 {} 大括号中的代码有效。
但在 JavaScript 中,变量作用域只分函数作用域,而且变量有声明提前的特性,所以在函数体内部第一次输出 i 时,此时变量已经提前声明,但还没初始化,所以会是 undefined。而函数内定义的变量的作用域或者说生命周期是整个函数内,所以即使 for 循环体语句结束,仍旧可以访问到 i 变量。
由于允许变量的重复定义,所以全局变量很容易起冲突,因为无法确保多份 js 文件中是否已经在全局中定义了该变量,一旦起冲突,浏览器行为仅仅是将后定义的覆盖掉前定义的而已,这对于浏览器角度没什么大问题,但对于程序而已,很容易出现不可控的问题。而且,极难排查。
所以,实际编程中,建议不要过多的使用全局变量,有多种方法可以避免:
- 使用一个全局对象来作为命名空间,将其余不在函数体内部定义的变量,作为该全局对象的属性来定义使用。
- 使用一个立即执行的函数来作为临时命名空间,函数执行结束释放临时命名空间。
- 如果临时命名空间内的部分变量需要供外部使用,一可以将这部分变量添加到作为命名空间的全局对象上的属性,二可以利用闭包的特性,返回一个新建的对象,为该对象添加一些接口可访问这部分变量。
全局对象作为命名空间
var DASU = {};
DASU.num = 1;
function a() {
console.log(DASU.num);
}
这里的全局对象意思是说,数据类型为对象的全局变量,简称全局对象,与前端里说的全局对象window是两个不同概念,区分一下。
其实也就是一种思想,将所有函数外需要定义的变量,都替换成对指定对象的属性来操作。
立即执行的函数作为临时命名空间
(function () {
var num = 1;
function a() {
console.log(num);
}
a();
}())
当引入 js 文件到 HTML 时,js 文件中的代码就会被执行,或者声明了 <script> 标签后,在标签内的代码也会立马被执行。但函数只有被调用的时候才会执行,所以,如果我们使用一个立即执行的函数,那这个函数体内部的代码行为就跟正常的 js 文件代码被执行的行为一致了。
而且,还可以利用函数内作用域这一特点,来保证,在这个立即执行的函数内部定义的变量不会影响到全局变量。
缺点就是函数内部代码执行结束后,这些在函数内定义的变量就被回收了。所以,如果有些信息需要跨 js 文件通信,此时要么通过全局对象方式,要么通过闭包特性来辅助实现。
临时命名空间内的变量共享方式
全局变量可以在任何地方被访问,所以可以将那些需要共享给外部使用的临时命名空间内的变量赋值给全局对象的属性,即结合第一种:全局对象做命名空间方式。
或者,通过闭包的特性,作为临时命名空间的立即执行的函数需要有一个返回值,当外部持有这个返回值时,这个函数内的变量就不会被回收。
然后,返回值可以是一个对象,公开一些接口来获取这些需要共享的变量,如:
var model = (function () {
var num = 1;
function a() {
console.log(num);
}
return {
getNum: function () {
return num;
}
}
}());
model.getNum();
//或者:
var model = (function () {
var num = 1;
function a() {
console.log(num);
}
return {
num:num
}
}());
model.num;
变量的声明提前原理
看个例子:
var i = 0;
function A() {
console.log(i); //输出undefined
for (var i = 0; i < 1; i++) {}
console.log(i); //输出1
}
A();
函数内第一个输出 undefined 是因为变量的声明提前,第二个输出 1 是因为变量作用域为函数作用域,而不是块级作用域。
那么,有想过,这些似乎理所当然的基础常识原理是什么吗?
我们先来看些理论,再结合理论返回来分析这个例子,但只分析变量的声明提前原理,至于作用域的原理留着作用域链一节分析。
理论
我们之前有介绍过执行上下文 EC,和变量对象 VO,执行上下文分全局执行上下文和函数执行上下文。在全局执行上下文中,VO 的具体表现是全局对象;在函数执行上下文中,VO 的具体表现是 AO,AO 存储着函数内的变量:形参、局部变量、函数自身引用、this、arguments。
不管是执行函数代码还是全局代码,js 解释器会分两个过程,有的文章翻译成:进入执行上下文阶段、执行代码阶段(我不怎么喜欢这个翻译)。
进入执行上下文阶段:其实本质上就是创建一个执行上下文,这个阶段会解析当前上下文内的代码,将声明的变量都保存到 VO 对象上。
执行代码阶段:就是代码实际运行期,当运行到相对应的变量的赋值语句时,就会将具体的属性值写入 VO 对象上保存的对应变量。
也就是说,在执行代码阶段,代码实际运行时,js 解释器已经解析了一遍上下文内的代码,并创建了执行上下文,且为其添加了一个 VO 属性,在 VO 对象上添加了上下文内声明的所有变量,这就是变量的声明提前行为。而之后函数体内对各变量的操作,其实是对 VO 上保存的变量进行操作了。
我看过一篇文章对这两个过程的翻译是:解析阶段、执行阶段。
我比较喜欢这种翻译,解析阶段主要的工作就是解析上下文内的代码,创建执行上下文,创建变量对象 VO 等,为执行阶段做准备;而执行阶段就是代码实际运行过程。
分析
var i = 0;
function A() {
console.log(i); //输出undefined
for (var i = 0; i < 1; i++) {}
console.log(i); //输出1
}
A();
再回过头来看这个简单的例子,假设这段代码放在一份单独的 js 文件中,解释器第一次执行这份代码,那么当执行全局代码时,首先进入全局执行上下文的解析阶段:
- 解析代码创建全局执行上下文
- 创建VO,并为其添加属性 i、A
- 省略该过程其他工作
- 将创建的全局EC放入ECS栈内
当实际开始执行第一行全局代码时,js解释器经过了解析阶段已经做了如上的工作,得到了一些基本的信息。之后便是执行全局代码,如果执行的代码是访问全局变量,那么直接读取全局 EC 中 VO 里的对应变量;如果是对全局变量赋值操作,那么写入全局 EC 中的 VO 里对应变量的属性值。
如果执行的代码是调用某个函数,此时就会为这个函数的执行创建一个函数执行上下文,那么这个过程同样需要两个阶段:解析阶段和执行阶段。
所以当代码执行到最后一行 A()
时,此时新的函数执行上下文的解析阶段做的工作:
- 解析 A() 函数内代码,并创建函数执行上下文 A函数EC
- 创建 AO,并为其添加属性
- 省略其他工作介绍
- 将创建的A函数EC放入ECS栈内
所以当执行函数 A 内的代码时,第一行输出才会输出 undefined,因为变量的声明提前特性在调用函数时创建函数执行上下文的过程中,已经解析了函数内的声明语句,并将这些变量添加到函数上下文 EC 的 AO 中了。
AO 就是变量对象 VO 在函数执行上下文中的具体表现。
而当执行完 for 循环语句,A 函数 EC 中的 AO 里的i属性已经被赋值为 1 了,而 A 函数 EC 是直到函数执行结束才销毁,所以即使在 for 语句内定义的 i 变量也可以在后面继续使用。
以上,就是变量声明提前的原理,当然,创建执行上下文的过程中,还涉及到其他很多工作,用来实现例如作用域链等机制,留待后续来说。
大家好,我是 dasu,欢迎关注我的公众号(dasuAndroidTv),公众号中有我的联系方式,欢迎有事没事来唠嗑一下,如果你觉得本篇内容有帮助到你,可以转载但记得要关注,要标明原文哦,谢谢支持~
前端入门17-JavaScript进阶之作用域的更多相关文章
- 前端基础之JavaScript进阶
一.流程控制 if - else var a = 10; if (a >5){ console.log("yes"); }else { console.log("n ...
- 结合个人经历总结的前端入门方法 (转自https://github.com/qiu-deqing/FE-learning)
结合个人经历总结的前端入门方法 (https://github.com/qiu-deqing/FE-learning),里面有很详细的介绍. 之前一直想学习前端的,都不知道怎么下手都一年了啥也没学到, ...
- 2019年Web前端入门的自学路线
本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文.本文内容不定期更新. 我前几天写过一篇文章:<裸辞两个月,海投一个月 ...
- 4、JavaScript进阶篇①——基础语法
一.认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面 ...
- JavaScript进阶(一)
OK接下来,我们再次梳理一遍js并且提高一个等级. 众所周知,web前端开发者需要了解html和css,会只用html和css创建一个漂亮的页 面,但是这肯定是不够的,因为它只是一个静态的页面,我们 ...
- JavaScript进阶 - 第1章 系好安全带,准备启航
第1章 系好安全带,准备启航 1-1让你认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂 ...
- JavaScript进阶之高阶函数篇
JavaScript进阶之高阶函数篇 简介:欢迎大家来到woo爷说前端:今天给你们带来的是JavaScript进阶的知识,接下来的系列都是围绕着JavaScript进阶进行阐述:首先我们第一篇讲的是高 ...
- 前端面试之JavaScript中数组的方法!【残缺版!!】
前端面试之JavaScript中数组常用的方法 7 join Array.join()方法将数组中所有元素都转化为字符串并连接在-起,返回最后生成的字 符串.可以指定一个可选的字符串在生成的字符串中来 ...
- 前端html、Javascript、CSS技术小结
简单地总结了一下前端用过的html.javascript.css技术,算是清点一下,做个大略的小结,为进一步的学习给个纲领. 一.HTML 由于HTML5的兴起,简单地判断一个网页是否是html5网页 ...
- javascript笔记:javascript的关键所在---作用域链
javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...
随机推荐
- js中的单例模式
1.场景:当我们需要多人合作完成一个项目,但是有一些操作是同样的操作时(例如:点击按钮显示加载的遮罩层:例如:提交表单时的验证都是一样的),这个时候我们就需要单例模式: 2.什么是单例模式:是一种常见 ...
- IPython绘图和可视化---matplotlib
1. 启动 IPython 2. >> fig = plt.figure() >> ax1 = fig.add_subplot(346) # 将画布分割成3行 ...
- High Availability手册(2): 架构
最底层是通信层corosync/openais 负责cluster中node之间的通信 上一层是Resource Allocation Layer,包含下面的组件: CRM Cluster Resou ...
- c++ 之bind使用
网络编程中, 经常要使用到回调函数. 当底层的网络框架有数据过来时,往往通过回调函数来通知业务层. 这样可以使网络层只专注于 数据的收发, 而不必关心业务 在c语言中, 回调函数的实现往往通过函数指针 ...
- 【安富莱专题教程第4期】SEGGER的J-Scope波形上位机软件,HSS模式简单易用,无需额外资源,也不需要写目标板代码
说明:1.在实际项目中,很多时候,我们需要将传感器或者ADC的数值以波形的形式显示.通常的解决办法是用串口上位机,USB接口上位机或者MDK的逻辑分析仪功能,使用这三种方式都比较繁琐.本期专题为大家讲 ...
- 关于H5页面的测试总结与分析
一.时下最流行的H5到底是什么 ?有什么优势和劣势? (1)H5 即HTML5,其实就是:移动端Web页面. (2)优势: H5可以跨平台使用,开发成本相对较低 H5可随时上线就更新版本,适合快速迭代 ...
- Android 音视频开发(三):使用 AudioTrack 播放PCM音频
一.AudioTrack 基本使用 AudioTrack 类可以完成Android平台上音频数据的输出任务.AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对 ...
- 流媒体协议(一):HLS 协议
一.HLS 概述 HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议,用于实时音视频流的传输.目前HLS协议被广泛的应用于视频点播 ...
- [Swift]LeetCode77. 组合 | Combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. Example: I ...
- [Swift]LeetCode111. 二叉树的最小深度 | Minimum Depth of Binary Tree
Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...