你不知道的JavaScript --- 作用域相关
本篇是《你不知道的JavaScript》的读书笔记
什么是作用域?
程序离不变量,那么变量存储在哪里?程序需要时如何找到他们?
这些问题说明需要一套设计良好的规则来存储变量, 并且之后可以方便地找到这些变量。这套规则被称为作用域。
作用域负责收集并维护由所有声明的标识符(变量) 组成的一系列查询, 并实施一套非常严格的规则, 确定当前执行的代码对这些标识符的访问权限。
作用域嵌套
当一个块或函数嵌套在另一个块或函数中时, 就发生了作用域的嵌套。 因此, 在当前作用域中无法找到某个变量时, 引擎就会在外层嵌套的作用域中继续查找, 直到找到该变量,或抵达最外层的作用域(也就是全局作用域) 为止。
function foo(a) {
console.log( a + b ); // foo的作用域中没有变量b,去外层找
}
var b = 2;
foo( 2 ); // 4
词法作用域
刚学的时候就知道JavaScript是词法作用域,那么究竟是什么意思?
JavaScript的源代码在执行之前会在编译器中经历词法分析、语法分析、代码生成等环节。
词法化的过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词语义。词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,因此当词法分析器处理代码时会保持作用域不变。
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。

作用域气泡由其对应的作用域块代码写在哪里决定, 它们是逐级包含的。
欺骗词法
正常情况下,词法作用域完全由写代码期间函数所声明的位置来定义。但是JavaScript也有两种机制可以在运行的时候来“修改”(也可以说欺骗)词法作用域。eval()和with。
JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但如果引擎在代码中发现了 eval(..) 或 with,它只能简单地假设关于标识符位置的判断都是无效的,因为无法在词法分析阶段明确知道 eval(..) 会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么。那么所有的优化可能都是无意义的,因此最简单的做法就是完全不做任何优化。
如果代码中大量使用 eval(..) 或 with ,那么运行起来一定会变得非常慢。无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行得更慢这个事实。
提升
先看个小栗子
console.log(a)
var a = 2;
直觉上认为,JavaScript是从上而下一行一行执行的,应该会报错ReferenceError. 但实际上这里会输出undefined.
引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
所以上述栗子可以理解为
var a;
console.log(a); // undefined
a = 2;
定义在编译阶段进行,赋值留在原地等待执行阶段,这个过程就叫做提升。
需要注意的是:
- 函数声明会被提升,但是函数表达式不会被提升
foo1(); // 'foo1'
foo2(); // TypeError : foo2 is not a function 此处的foo2未被赋值,为undefined
function foo1(){
console.log('foo1');
}
var foo2 = function (){
console.log('foo2');
}
- 函数会首先被提升,然后才是变量
foo(); //foo1 而不是TypeError 说明函数声明先被提升,然后才是变量提升,但是同名,所以变量的声明被忽略了
var foo = function (){
console.log('foo2');
}
function foo(){
console.log('foo1');
}
foo(); //foo2 执行赋值之后,foo函数输出foo2
闭包
闭包是基于词法作用域写代码时所产生的自然结果,闭包的创建和使用在代码中随处可见,我们需要的是根据自己的意愿来识别,拥抱和影响闭包的思维环境。
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前作用域之外执行。
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz() // 2 --- 闭包效果
函数baz(实际上就是bar的引用)可以访问到foo内部作用域,虽然是在foo作用域外部执行的。而正是由于bar的存在,所以foo函数执行后,内部作用域没有被销毁,bar会使用这个内部作用域。
bar依然持有对该作用域的引用,这个引用就叫做闭包。闭包使得函数可以继续访问定义时词法作用域。无论使用何种方式对函数类型的值进行传递,当函数在别处别调用时都可以观察到闭包。
闭包的一个经典问题
for(var i = 1; i <= 5 ; i++) {
setTimeout(function timer() {
console.log(i);
},i * 1000);
}
这里会每间隔一秒,打印一个6。每次循环都会创建一个timer函数传递个setTimeout。timer中使用的变量i都是上层作用域中定义的变量i(闭包),当循环执行完之后,i的值为6,所以会连续打印5个6.
如果想依次打印1到5。有以下处理方式。
- 在定时器外创建一层作用域,使每次循环产生的timer使用的i都不一样。
for(var i = 1; i <= 5 ; i++) {
(function(j){
setTimeout(function timer() {
console.log(j);
},j * 1000);
})(i)
}
- 使用块级作用域 - let
for(let i = 1; i <= 5 ; i++) {
setTimeout(function timer() {
console.log(i);
},i * 1000);
}
块级作用域会使每次创建定时器的作用域都不一样。而且语言特性会使循环时记住上一次i的值。
你不知道的JavaScript --- 作用域相关的更多相关文章
- 你不知道的JavaScript演示代码Github地址
你不知道的JavaScript博文相关代码托管至Github,每次写完博客会把代码提交上去. 代码地址:https://github.com/rongbo-j/you-dont-know-js 点击D ...
- 你不知道的javaScript上卷(第一章 作用域是什么)
在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...
- 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)
原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- 《你不知道的JavaScript》整理(一)——作用域、提升与闭包
最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...
- 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域
你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...
- 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)
github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...
- JavaScript作用域闭包(你不知道的JavaScript)
JavaScript闭包.是JS开发project师必须深入了解的知识. 3月份自己曾撰写博客<JavaScript闭包>.博客中仅仅是简单阐述了闭包的工作过程和列举了几个演示样例,并没有 ...
- 你不知道的JavaScript之作用域
什么是作用域 编译原理 分词/词法分析 这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代 码块被称为词法单元(token) 解析/语法分析 这个过程是将词法单元流(数组)转 ...
随机推荐
- java之路 把1到100之间的数的偶数相加
/** *把1到100之间的数的偶数相加 */ class Demo{ public static void main(String[] args){ int i =1; int sum = 0; d ...
- xbee3的先进性功能用法
xbee3以及xbee3 PRO 是digi无线模块的又一大突破:不仅实现了所有2.4G的模块整合,更在以后的程序更新中会增加蓝牙功能:它打通了xbee系列1和系列2之间的壁垒:不同于xbee S2C ...
- T-2-java面向对象
一.类 类对象的数据结构定义,方法是对象的行为. 类是数据类型. 一个类可以创建多个对象,这多个对象结构相同,数据不同. 类中可以包含:(1)成员变量(对象的共同特征,静的):(2)方法(对象的共同行 ...
- 在n个数字中求为k的和————Java
给出N个正整数组成的数组A,求能否从中选出若干个,使他们的和为K.如果可以,输出:"YES",否则输出"NO".用Java实现 import java.util ...
- OpenAL音频库例程
Windows下C++可用的OpenAL demo. 基于alut工具库的OpenAL例程,涵盖了基本的OpenAL指令,对部分作出了注释,并且可以播放(当然得把对应的音频文件放到正确的路径下). # ...
- python 方法
1.首先运行python交互模式 输入 python 2.定义一个有序的集合 相当于js中的数组它里面有一些增删改查的方法 1. 定义一个数组 >>> ww = ['1','2',' ...
- mycat跟踪分析
mycat版本1.6 192.168.5.66 从 192.168.5.67主 一个user表 验证主从 log4j2修改日志level为debug schema.xml配置 启动服务,打开日志tai ...
- Runtime "Apache Tomcat v6.0 (3)" is invalid. The JRE could not be found. Edit the server and change the JRE location解决方案
使用eclipse,启动Tomcat时出现The JRE could not be found ,Edit server and change teh JRE location的错误提示! 原因:重装 ...
- 简单了解Django
Django 是开源代码web应用的框架,由python完成,django的主要目的是简便,快速开发数据库驱动网站 主要用于测试,运维,自测. 1.下载Django. 个人建议使用命令pip inst ...
- MFC图片操作
根据MFC要操作图片的来源,可分为以下两类: 一.非动态显示图片(即图片先通过资源管理器载入,有一个固定ID) 二.动态载入图片(即只需要在程序中指定图片的路径即可载入) 一.非动态显示图片 1.传送 ...