JavaScript笔记 | 作用域和闭包 |《你不知道的JavaScript(上卷)》第一部分
JavaScript | 作用域和闭包 | 读书笔记
读书笔记(自用)
来自《你不知道的JavaScript(上卷)》第一部分 作用域和闭包
1 作用域是什么
1.1编译的3个步骤
(1)分词/词法分析(Tokenizing/Lexing)
–将字符组成的字符串分解为对编程语言来说有意义的代码块(词法单元(token))
(2)解析/语法分析(Parsing)
–将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树(抽象语法树(Abstract Syntax Tree,AST))
(3)代码生成
–将AST转换为可执行代码(机器指令)的过程
1.2理解作用域
(1)LHS
当变量出现在赋值操作的左侧时进行LHS查询
LHS查询试图找到变量容器本身
如果查找的目的是对变量进行赋值,就会使用LHS
(2)RHS
当变量出现在赋值操作的右侧时进行RHS查询
RHS查询就是简单的查找某个变量的值
如果目的是获取变量,就会使用RHS
参考链接:
更详细的知识点内容如下:笔记1link ,笔记2 link,笔记3 link
1.3作用域嵌套
1.4异常
举例
function foo(a){console.log(a+b);b=a;
}
foo(2)
–ReferenceError异常–
指作用域判别失败
(1)RHS查询
由于b是一个未声明的变量,第一次对b进行的RHS查询在所有嵌套的作用域中都遍寻不到所需的变量,所以引擎会抛出ReferenceError异常。
(2)非严格模式下的LHS查询
当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎。
(3)严格模式下的LHS查询
严格模式禁止自动或隐式地创建全局变量,因此在严格模式中LHS查询失败时,并不会创建并返回一个全局变量,此时,引擎会抛出同RHS查询失败时类似的ReferenceError异常。
–TypeError异常–
指在作用域判别成功的情况下,对结果的操作是非法或不合理的。
如果RHS查询到了一个变量,但你尝试对这个变量的值进行不合理的操作,例如试图对一个非函数类型的值进行函数调用,或引用null或undefined类型的值中的属性,那么引擎会抛出TypeError异常。
2.词法作用域
2.1词法阶段
举例:找出3个逐级嵌套的作用域
2.2欺骗词法
如果词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修改”(欺骗)词法作用域呢?
(1)eval
–JS中的eval()函数可以接受一个字符串作为参数,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
–在执行eva()之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的,引擎指挥如往常地进行词法作用域查找。
举例:
function foo(str,a){eval(str);//欺骗console.log(a,b);
}
var b=2;
foo("var b=3;",1);//1,3
–注意:在严格模式下,eval()中的声明无法修改所在的作用域。
举例:
function foo(str){"use strict";eval(str);console.log(a);//ReferenceError:a is not defined
}
foo("var a=2");
(2)with
–通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
![图片来自《你不知道的JavaScript(上卷)》](https://img-blog.csdnimg.cn/299cf90768b848f192ee1fd3e9525fea.jpeg#pic_center
(3)性能
eval()和with会在运行时修改或创建新的作用域,一次欺骗其他在书写时定义的词法作用域。
大量使用eval()或with会导致运行起来会很慢。
3.函数作用域和块作用域
3.1函数中的作用域
3.2隐藏内部实现
好处:
- 最小暴露/授权原则,如果把所有变量和函数都放在全局作用域中,可能会暴露或泄露过多的本该是私有的变量或函数。
- 规避冲突,避免同名标识符之间的冲突,而冲突会导致变量的值被意外覆盖。
–>全局命名空间
–>模块管理
3.3函数作用域
函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。(另外,函数表达式可以是匿名的,但在JS中函数声明不可以省略函数名)
3.3.1匿名和具名
匿名函数表达式:
function()…没有名称标识符。
setTimeout( function() {console.log("I waited 1 second!");
}, 1000 )
(1)优点:
- 写起来简单快捷,很多库和工具也倾向于鼓励这种代码风格。
(2)缺点:
- 调试困难,因为匿名函数在栈追踪中不会显示有意义的函数名。
- 如果没有函数名,当函数需要引用自身时只能使用已经过期的darguments.callee 引用,比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。
- 不利于代码的可读性。
(3)解决方案:
- 给函数表达式指定一个函数名可以有效解决以上问题
3.3.2立即执行函数表达式
** IIFE(Immediately Invoked Function Expression)**
var a = 2;
(function foo() {var a = 3;console.log( a ); // 3
})();
console.log( a ); // 2
由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个( ) 可以立即执行这个函数,比如 (function foo(){ … })()。第一个 ( ) 将函数变成表达式,第二个 ( ) 执行了这个函数。
3.4块作用域
3.4.1with
3.4.2try/catch
JavaScript 的 ES3 规范中规定 try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。
try {undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {console.log( err ); // 能够正常执行!
}
console.log( err ); // ReferenceError: err not found;
3.4.3let
ES6引入let,let 关键字可以将变量绑定到所在的任意作用域中(通常是 { … } 内部)。换句话说,let为其声明的变量隐式地了所在的块作用域。
(1)垃圾收集
举例
function process(data) {// 在这里做点有趣的事情
}
var someReallyBigData = { .. };
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt) {console.log("button clicked");
}, /*capturingPhase=*/false );
click 函数的点击回调并不需要someReallyBigData 变量。理论上这意味着当 process(…) 执行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于 click 函数形成了一个覆盖整个作用域的闭包,JavaScript 引擎极有可能依然保存着这个结构(取决于具体实现)。
块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:
function process(data) {// 在这里做点有趣的事情
}
// 在这个块中定义的内容可以销毁了!
{let someReallyBigData = { .. };process( someReallyBigData );
}
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt){console.log("button clicked");
}, /*capturingPhase=*/false );
3.4.4const
var、let、const知识点详细版 后续补充
4.提升
4.2编译器
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {// ...
};
这个代码片段经过提升后,实际上会被理解为以下形式
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {var bar = ...self...
// ...
}
4.3函数优先
这一部分后续补充
5.作用域闭包
5.3循环和闭包
for (var i=1; i<=5; i++) {setTimeout( function timer() {console.log( i );
}, i*1000 );
}
正常情况下,我们对这段代码行为的预期是分别输出数字 1~5,每秒一次,每次一个。
但实际上,这段代码在运行时会以每秒一次的频率输出五次 6。这个循环的终止条件是 i 不再 <=5。条件首次成立时 i 的值是6。因此,输出显示的是循环结束时 i 的最终值。
解决方案:
我们使用 IIFE 在每次迭代时都创建一个新的作用
域。换句话说,每次迭代我们都需要一个块作用域。第 3 章介绍了 let 声明,可以用来劫持块作用域,并且在这个块作用域中声明一个变量。
for (var i=1; i<=5; i++) {let j = i; // 是的,闭包的块作用域!setTimeout( function timer() {console.log( j );
}, j*1000 );
}
5.5 模块
function CoolModule() {var something = "cool";var another = [1, 2, 3];function doSomething() {console.log( something );
}function doAnother() {console.log( another.join( " ! " ) );
}return {doSomething: doSomething,doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
这个模式在 JavaScript 中被称为模块。最常见的实现模块模式的方法通常被称为模块暴露,这里展示的是其变体。
模块模式需要具备两个必要条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
一个具有函数属性的对象本身并不是真正的模块。从方便观察的角度看,一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块。
附录A:动态作用域
附录B:块作用域的替代方案
附录A:this词法
JavaScript笔记 | 作用域和闭包 |《你不知道的JavaScript(上卷)》第一部分相关推荐
- 你不知道的 JavaScript 笔记——作用域和闭包
第一章:作用域是什么 程序中变量存储在哪里,需要是怎么找到它,这就需要设计一套存储以及能方便的找到它的规则,这个规则就是作用域 编译原理 JavaScript 是一门编译语言,它与传统编译语言不同,但 ...
- JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...
- 重温JavaScript(lesson4):作用域和闭包(2)
在lesson3中我们重温了JS作用域有关的内容,理解了JS作用域再来看闭包就非常easy了. 1.闭包的概念 先来补充一个知识点(PS:如果你觉得不好理解,就看之后代码吧~),词法作用域:" ...
- javascript函数作用域与闭包
8.8. 函数作用域与闭包 如第四章所述,JavaScript函数的函数体在局部作用域中执行,局部作用域不同于全局作用域.本章将解释这些内容和相关的作用域问题,包括闭包.[*] [*] ...
- JavaScript的作用域与闭包
JavaScript的作用域以函数为界,不同的函数拥有相对独立的作用域.函数内部可以声明和访问全局变量,也可以声明局部变量(使用var关键字,函数的参数也是局部变量),但函数外部无法访问内部的局部变量 ...
- JavaScript从作用域到闭包
目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包 作用域(scope) 全局作用域 ...
- javaScript的作用域、闭包
前言 JavaScript 中的闭包是相当重要的概念,并且与作用域相关知识的指向密切相关. 那么, JavaScript 中的作用域是什么? 闭包会在哪些场景中使用? 作用域 JavaScript 的 ...
- 你不懂的JS学习笔记(作用域和闭包)
You don't KnowJS 引语:你不懂的JS这本书github上已经有了7w的star最近也是张野大大给我推荐了一波,阅读过之后感觉对js的基础又有了更好的理解.本来我是从来不这种读书笔记的, ...
- JavaScript笔记(二)——HTML中的JavaScript
JavaScript笔记(二) 读书笔记 HTML中的JavaScript 内容概要 2.1 <\script\>元素 2.1.1 标签位置 2.1.2 推迟执行脚本 2.1.3 异步执行 ...
最新文章
- mysql show作用_MySQL show的用法
- 前端基础之设计一个个人工作室介绍界面
- JavaScript tips and tricks - 4
- JZOJ 5924. 【NOIP2018模拟10.23】Queue
- 解决react-native 运行报错:Entry, :CFBundleIdentifier, Does Not Exist
- python实现栈_Python 栈
- 【java基础知识】java.util.LinkedHashMap cannot be cast to com.XXX.XXX
- Django基础之中间件
- 【爬虫剑谱】一卷3章 软件篇-Anaconda的安装及配置
- HDU-1796 How many integers can you find 容斥定理
- 汇编学习--7.10--程序编写
- Xcode 8.1 : Unable to read from device
- python3.5.4安装_linux-centos系统下安装python3.5.4步骤
- android使用cpu软解,Android 使用自带的MediaCodec 框架进行本地视频压缩,速度嗖嗖的,亲测有效!!!...
- 在命令行中快速移动光标的快捷键
- win7旗舰版安装vs2005
- nginx学习十 ngx_cycle_t 、ngx_connection_t 和ngx_listening_t
- js 面试题之---数组、字符串操作
- python联机麻将_python麻将和牌算法
- 不要陷入「完美主义者」的陷阱,你必须填的坑