读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)
一、 序言
最近我在读一本书:《你不知道的JavaScript》,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这本书,受益匪浅。
《你不知道的JavaScript上卷》
现在我读完这本书的一些心得与总结:
很多人在做项目时候,遇到bug是我们程序猿最令人头疼的一件事,不过,无论多大多小的bug,都会被我们debug,所以,一切的bug都有原因,只要慢慢静下心来细想想这段代码的流程结构是否正确,哪一步骤出了错误,bug就迎刃而解啦。
聊了这么多铺垫,其实我想说的就一句话:bug从不细心得来,debug是从细心解决。
这本书的第一部分是讲的是作用域与闭包,现在我谈谈作用域的理解,同时也聊聊理解JavaScript的作用域,是对分析JavaScript的代码流程有多么的重要。
二、JavaScript的作用域是什么,他是如何运行工作的?
好比:
1.这段代码会输出什么呢?
var num = 10;
console.log(num);
2.或许这段呢?
var num;
console.log(num);
num = 10;
我们都轻易知道上面的代码会分别输出:10,undefined;即使简单,相信大家脑子已经想了一次这段代码的执行流程;
不用着急,先理解一下作用域:
《你不知道的JavaScript》先开头就已经有定义好的约定,我们也来一下:
1.引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程。
2.编译器:引擎的好朋友之一,负责语法分析及代码生成等脏活累活
3.作用域:引擎的另一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查 询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
为了能够完全理解 JavaScript 的工作原理,你需要开始像引擎(和它的朋友们)一样思考, 从它们的角度提出问题,并从它们的角度回答这些问题
这段出处《你不知道的JavaScript》上卷第一部分
好,我们一起来分析一下上面的代码:
var num = 10;
我们第一眼看到这句代码,很可能认为这是一句声明,但js的引擎却认为这里应该有2个声明,第一个是由编译器
在编译时处理,另一个是由引擎在运行时处理
也就是说:
var num = 10; 分为:
声明:var num;
赋值:num = 10;
三、引用一下《你不知道的JavaScript》的引擎和作用域的对话:
LHS 和 RHS 的含义是“赋值操作的左侧或右侧”并不一定意味着就是“= 赋值操作符的左侧或右侧”。
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
让我们把上面这段代码的处理过程想象成一段对话,这段对话可能是下面这样的。
我的理解图顺序:
第一步:当开始执行js时候,js引擎用上到下开始扫描
=> 1.读到了一个foo的函数
foo(){
...
}
之后继续读下一步(没有查询到foo()调用是不会继续读函数下去的)
=> 2. 读到了foo();
这里就要开始调用foo函数,
所以引擎:我说作用域,我需要为 foo 进行 RHS 引用。你见过它吗?
作用域:别说,我还真见过,编译器那小子刚刚声明了它。它是一个函数,给你。
引擎:哥们太够意思了!好吧,我来执行一下 foo
=> 3. 当引擎执行foo函数时候发现有个a的参数,
然后引擎当然需要为a开始查询:
引擎:作用域,还有个事儿。我需要为 a 进行 LHS引用,这个你见过吗?
作用域:这个也见过,编译器最近把它声名为 foo 的一个形式参数了,拿去吧。
引擎:大恩不言谢,你总是这么棒。现在我要把 2 赋值给 a。
=> 4. js引擎继续往下面读:
发现一个console.log,所以
引擎:哥们,不好意思又来打扰你。我要为 console 进行 RHS 引用,你见过它吗?
作用域:咱俩谁跟谁啊,再说我就是干这个。这个我也有,console 是个内置对象。 给你。
引擎:么么哒。我得看看这里面是不是有 log(..)。太好了,找到了,是一个函数。
=> 5. 最好执行console.log()里面的a
引擎:哥们,能帮我再找一下对 a 的 RHS 引用吗?虽然我记得它,但想再确认一次。
作用域:放心吧,这个变量没有变动过,拿走,不谢。
引擎:真棒。我来把 a 的值,也就是 2,传递进 log(..)。
...
这段对话引用《你不知道的JavaScript》的引擎和作用域的对话
到了这里,我想常常我在开发中,当遇到bug时候,我都会整理思路,想想哪一步出错了,现在我才发现,理解js的作用域是多么重要,才知道哪一步出了问题。
补充
来,继续看考虑一下一下代码:
function foo(a){
console.log(a + b);
}
var b = 2;
foo(2); 会输出什么呢?他的执行流程是什么呢?
tips:引擎从当前的执行作用域开始查找变量,如果找不到就会向上一级继续查找。当抵达最外层的全局作用域还是没有找到,查找的过程都会停止。
四、函数作用域
好,聪明的我们继续来看一段代码:
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar( b * 3 );
}
foo( 2 ); // 分别输出多少?
聪明的我们肯定看了以上代码一下子能知道答案,我们再来看一段想想分别输出多少:
function foo(a) {
var b = 2;
function bar(c) {
console.log( a, b, c );
}
var c = 3;
}
bar(); // 输出多少呢?
console.log(a); // 输出多少呢?
console.log(b); // 输出多少呢?
console.log(c); // 输出多少呢?
好了,到了这里,我们都已经有了答案,很明显,
第一个代码域,都分别输出了2,4,12
..
第二个代码域,都报了ReferenceError错误
很明显,在外面想访问函数里面的值,是访问不到的,所以知道了函数拥有自己的作用域,外面是访问不到的。
这里可以引申一个技巧:
私有变量 与 共有变量
看一下代码如何优化:
function doSomething(a) {
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething( 2 ); // 15
在这个代码片段中,变量 b 和函数 doSomethingElse(..) 应该是 doSomething(..) 内部具体 实现的“私有”内容。给予外部作用域对 b 和 doSomethingElse(..) 的“访问权限”不仅 没有必要,而且可能是“危险”的,因为它们可能被有意或无意地以非预期的方式使用, 从而导致超出了 doSomething(..) 的适用条件。更“合理”的设计会将这些私有的具体内 容隐藏在 doSomething(..) 内部,例如:
function doSomething(a) {
function doSomethingElse(a) {
return a - 1;
}
var b;
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
doSomething( 2 ); // 15
现在,b 和 doSomethingElse(..) 都无法从外部被访问,而只能被 doSomething(..) 所控制。 功能性和最终效果都没有受影响,但是设计上将具体内容私有化了,设计良好的软件都会 依此进行实现
五、块级作用域
兄弟,以下这段代码我们肯定写过几万次了~
for (var i = 0; i< 10; i++) {
console.log(i);
}
这个就是最常见的块级作用域,
你可以试试在for{}外面执行一下console.log(i)试试 输出什么?
for (var i = 0; i< 5; i++) {
console.log(i); // 0,1,2,3,4
}
console.log(i); // 5
我们在 for 循环的头部直接定义了变量 i,通常是因为只想在 for 循环内部的上下文中使
用 i,而忽略了 i 会被绑定在外部作用域(函数或全局)中的事实。
for (let i = 0; i< 5; i++) {
console.log(i); // 0,1,2,3,4
}
console.log(i); // ReferenceError: i is not defined
当把var 改为了 let ,那么for循环里的i只能在{}这个作用域有效,外面就是访问不到了,所以报了ReferenceError
{
console.log( bar ); //报了ReferenceError
let bar = 10;
}
上面代码未声明的变量,不能使用,不存在变量的提升
5.2.垃圾回收
另一个块作用域非常有用的原因和闭包及回收内存垃圾的回收机制相关。这里简要说明一 下,而内部的实现原理
function process(data) {
console.log(data);
}
var someReallyBigData = {
'name': 'bobobo',
};
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt) {
console.log("button clicked");
}, false );
click 函数的点击回调并不需要 someReallyBigData 变量。理论上这意味着当 process(..) 执 行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。
但是,由于 click 函数形成 了一个覆盖整个作用域的闭包,JavaScript 引擎极有可能依然保存着这个结构(取决于具体 实现)。
块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:
function process(data) {
console.log(data);
}
// 在这个块中定义的内容可以销毁了!
{
var someReallyBigData = {
'name': 'bobobo',
};
}
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt) {
console.log("button clicked");
}, false );
六、先有鸡还是先有蛋?
a = 2;
var a;
console.log( a ); 输出什么?
当我看见这段代码时候,肯定想有没有坑啊?这不是等于2么?
《你不知道的JavaScript》书里解释到:很多开发者会认为是 undefined,因为 var a 声明在 a = 2 之后,他们自然而然地认为变量 被重新赋值了,因此会被赋予默认值 undefined。但是,真正的输出结果是 2。
果然是2!一起再考虑另外一段代码:
console.log( a ); 输出多少??
var a = 2;
是2,ReferenceError 还是 undefined呢?
以上代码结合一开始所说的:
js引擎去读时候,会读到了
var a;
consoel.log(a);
a = 2;
所以输出undefined: 先有蛋(声明)后有鸡(赋值)。
七、函数优先?
函数声明和变量声明都会被提升。
foo(); // 输出多少?
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
是函数优先还是变量优先呢?
答案是: 输出1,函数优先
一个普通块内部的函数声明通常会被提升到所在作用域的顶部,这个过程不会像下面的代 码暗示的那样可以被条件判断所控制:
foo(); // "b"
var a = true;
if (a) {
function foo() {
console.log("a"); }
}
else {
function foo() {
console.log("b");
}
}
但是需要注意这个行为并不可靠,在 JavaScript 未来的版本中有可能发生改变,因此应该 尽可能避免在块内部声明函数。
读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)的更多相关文章
- 浅谈javascript函数节流
浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈 JavaScript 编程语言的编码规范
对于熟悉 C/C++ 或 Java 语言的工程师来说,JavaScript 显得灵活,简单易懂,对代码的格式的要求也相对松散.很容易学习,并运用到自己的代码中.也正因为这样,JavaScript 的编 ...
- 浅谈JavaScript中的正则表达式(适用初学者观看)
浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...
- 浅谈JavaScript浮点数及其运算
原文:浅谈JavaScript浮点数及其运算 JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...
- 浅谈javascript的原型及原型链
浅谈javascript的原型及原型链 这里,我们列出原型的几个概念,如下: prototype属性 [[prototype]] __proto__ prototype属性 只要创建了一个函数,就会为 ...
- 浅谈JavaScript中的null和undefined
浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...
- [转载]浅谈JavaScript函数重载
原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...
- 浅谈JavaScript中的内存管理
一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...
随机推荐
- 在ssh框架中service,action,jsp,formbeam,dao的调用顺序
本文来自:http://blog.csdn.net/w_basketboy24/article/details/8642846 jsp发起请求. actionform封装请求参数. action接受请 ...
- canvas 简易的加载进度条
做一个web app,想在第一次或者更新的时候,有一个更新进度条,我个人比较喜欢圆的那种. canvas + svg高低配,应该还不错的.顺便一提,canvas用来压缩图片也是么么哒的. 先看下效果图 ...
- 第十章:Python の 网络编程基础(二)
本課主題 Python中的作用域补充 socketserver 源码 线程的介绍和操作实战 进程的介绍和操作实战 协程的介绍和操作实战 本周作业 Python中的作用域补充 Python世界里沒有块级 ...
- Maven代理设置
公司需要设置代理才能上网,而运行Maven时需要下载依赖的库. 怎么办呢? 原来Maven也像IE一样,可以设置HTTP代理的. 步骤如下: ·编辑 ~/.m2/setting.xml 文件.如果该目 ...
- Zabbix实战-简易教程(9)--模板
1.模板概念 场景:比如你老板给你一个任务:有100台机器需要监控他的OS性能(CPU/内存/磁盘IO/网络),都是同样的监控项200个,上午需要添加完成,并且检查监控项的信息是否准确.这时你会怎么操 ...
- .Net程序员学用Oracle系列:视图、函数、存储过程、包
1.视图 在实际操作过程中,本人发现 Oracle 视图定义有一个缺陷,就是不大方便注释,每次写好的注释执行之后再打开视图定义所有注释就全都没了.后来我发现把注释写到末尾就不会被清除,但这样总感觉乖乖 ...
- Java入门篇(二)——Java语言基础(下)
上篇说到Java中的变量与常量,接下来就是简单的计算了,首先需要了解一下Java中的运算符. 六.运算符 1. 赋值运算符 赋值运算符即"=",是一个二元运算符(即对两个操作数进行 ...
- Java中import及package的用法
有些人写了一阵子 Java,可是对於 Java 的 package 跟 import 还是不 太了解很多人以為原始码 .java 档案中的 import 会让编译器把所 import 的程式通通写到编 ...
- 51Nod 1182 完美字符串(字符串处理 贪心 Facebook Hacker Cup选拔)
1182 完美字符串 题目来源: Facebook Hacker Cup选拔 基准时间限制:1 秒 空间限制:1 ...
- 利用dfs解决规定路程问题
今天继续dfs的训练,遇到了一道神题,不停地TLE,我DD都快碎了.....好在经过本渣不懈努力,还是弄了出来,不容易啊,发上来纪念一下,顺便总结一下关于用dfs解决规定路程的问题. 先上题目: De ...