原文

一、 序言

最近我在读一本书:《你不知道的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作用域(一)的更多相关文章

  1. 浅谈javascript函数节流

    浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...

  2. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  3. 浅谈 JavaScript 编程语言的编码规范

    对于熟悉 C/C++ 或 Java 语言的工程师来说,JavaScript 显得灵活,简单易懂,对代码的格式的要求也相对松散.很容易学习,并运用到自己的代码中.也正因为这样,JavaScript 的编 ...

  4. 浅谈JavaScript中的正则表达式(适用初学者观看)

    浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...

  5. 浅谈JavaScript浮点数及其运算

    原文:浅谈JavaScript浮点数及其运算     JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...

  6. 浅谈javascript的原型及原型链

    浅谈javascript的原型及原型链 这里,我们列出原型的几个概念,如下: prototype属性 [[prototype]] __proto__ prototype属性 只要创建了一个函数,就会为 ...

  7. 浅谈JavaScript中的null和undefined

    浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...

  8. [转载]浅谈JavaScript函数重载

     原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...

  9. 浅谈JavaScript中的内存管理

    一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...

随机推荐

  1. OpenCascade Ruled Surface

    OpenCascade Ruled Surface eryar@163.com Abstract. A ruled surface is formed by moving a line connect ...

  2. Ubuntu 搭建简单的git server

    Git 可以使用四种主要的协议来传输资料:本地协议(Local),HTTP 协议,SSH(Secure Shell)协议及 Git 协议. 在此,我们将会讨论那些协议及哪些情形应该使用(或避免使用)他 ...

  3. npm ERR! code EINTEGRITY npm! ERR! shal-

    npm ERR! code EINTEGRITY npm ERR! sha1-nbqdpC/e8IOA7poHctXL5+bVXsE= integrity checksum failed when u ...

  4. Oracle loop循环无法插入数据

    以下的测试基于scott用户下的emp表 首先用while循环进行测试,向emp表插入999条数据 declare i emp.empno; begin loop insert into emp(em ...

  5. 编码与模式------《Designing Data-Intensive Applications》读书笔记5

    进入到第四章了,本篇主要聊的点是编码(也就是序列化)与代码升级的一些场景,来梳理存储之中涉及到的编解码的流程.目前主流的编解码便是来自Apache的Avro,来自Facebook的Thrift与Goo ...

  6. AdaBoost入门

    写一点自己理解的AdaBoost,然后再贴上面试过程中被问到的相关问题.按照以下目录展开. 当然,也可以去我的博客上看 Boosting提升算法 AdaBoost 原理理解 实例 算法流程 公式推导 ...

  7. es6的常用语法

    最常用的ES6特性 let, const, class, extends, super, arrow functions, template string, destructuring, defaul ...

  8. 不常见的for循环命名以及with(document)

    for循环想必大家是很常见的,但是for循环的命名可能很多人听了是一头雾水. 说起for循环的命名呢,主要用途是与for循环的终止break有关! 提到break,大家肯定都了解的.终止整个循环嘛! ...

  9. bootstrap-table操作之“删除”

    最近在做一个新的后台管理系统,在对数据进行操作时需要写一个"删除"功能,如图所示: 下面我来描述一下实现过程中出现的bug以及解决方法: 1.href值为空(href=" ...

  10. bzoj:1659: [Usaco2006 Mar]Lights Out 关灯

    Description 奶牛们喜欢在黑暗中睡觉.每天晚上,他们的牲口棚有L(3<=L<=50)盏灯,他们想让亮着的灯尽可能的少.他们知道按钮开关的位置,但喜闻乐见的是他们并没有手指.你得到 ...