本篇是《你不知道的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;

定义在编译阶段进行,赋值留在原地等待执行阶段,这个过程就叫做提升

需要注意的是:

  1. 函数声明会被提升,但是函数表达式不会被提升
    foo1(); // 'foo1'
foo2(); // TypeError : foo2 is not a function 此处的foo2未被赋值,为undefined function foo1(){
console.log('foo1');
}
var foo2 = function (){
console.log('foo2');
}
  1. 函数会首先被提升,然后才是变量
    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。有以下处理方式。

  1. 在定时器外创建一层作用域,使每次循环产生的timer使用的i都不一样。
    for(var i = 1; i <= 5 ; i++) {
(function(j){
setTimeout(function timer() {
console.log(j);
},j * 1000);
})(i)
}
  1. 使用块级作用域 - let
    for(let i = 1; i <= 5 ; i++) {
setTimeout(function timer() {
console.log(i);
},i * 1000);
}

块级作用域会使每次创建定时器的作用域都不一样。而且语言特性会使循环时记住上一次i的值。

你不知道的JavaScript --- 作用域相关的更多相关文章

  1. 你不知道的JavaScript演示代码Github地址

    你不知道的JavaScript博文相关代码托管至Github,每次写完博客会把代码提交上去. 代码地址:https://github.com/rongbo-j/you-dont-know-js 点击D ...

  2. 你不知道的javaScript上卷(第一章 作用域是什么)

    在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...

  3. 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)

    原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...

  4. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

  5. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  6. 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

    你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...

  7. 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)

    github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...

  8. JavaScript作用域闭包(你不知道的JavaScript)

    JavaScript闭包.是JS开发project师必须深入了解的知识. 3月份自己曾撰写博客<JavaScript闭包>.博客中仅仅是简单阐述了闭包的工作过程和列举了几个演示样例,并没有 ...

  9. 你不知道的JavaScript之作用域

    什么是作用域 编译原理 分词/词法分析 这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代 码块被称为词法单元(token) 解析/语法分析 这个过程是将词法单元流(数组)转 ...

随机推荐

  1. python基础之Day20part2

    面向过程:核心是过程,解决问题的步骤,先干嘛后干嘛,机械流水线 复杂问题流程化简单化但扩展性差 面向对象:核心是对象,是特征与对象的集合体,基于该思想编写程序,就好比上帝造世界,上帝式的思维方式 扩展 ...

  2. IT人保护视力的10个有效方法

    保护视力锻炼十法   有关资料表明:近年来,我国中小学生的近视率呈逐年增长的趋势,1993年底已高达60左右.众所周知,近视除了与遗传有一定联系外,还与光线不足.近距离阅读过多等眼睛过度疲劳密切相关. ...

  3. 使用electron开发指静脉客户端遇到的问题总结

    使用electron 使用nodejs 的ffi模块调用dll文件 总结1.electron 与nodejs版本不需要一致,甚至nodejs版本应该高于electron的node版本2.要安装 Vis ...

  4. Linux下mysql定时自动备份并FTP到远程脚本

    1.添加backupmysqleveryday.sh(vi /data/shell/backupmysqleveryday.sh) #!/bin/sh #this shell is user for ...

  5. redis学习-集合set常用命令

    redis学习-集合set常用命令   1.sadd:添加一个元素到集合中(集合中的元素无序的并且唯一) 2.smembers:查看集合中所有的元素(上图事例) 3.srem:删除结合中指定的元素 4 ...

  6. 组件内守卫beforeRouteEnter和beforeRouteLeave

    beforeRouteEnter用法和其他守卫差不多. 有个注意的地方就是beforeRouteEnter不能用this获取组件内收据. 在next()方法内存入vm这个参数,获取组件内数据. bef ...

  7. 【转】mysqldump与innobackupex知多少

    作者:罗小波 [目录] 1. 先看mysqldump 1.1 mysqldump备份过程解读 1.2 mysqldump备份过程中的关键步骤 1.2.1 FLUSH TABLES和FLUSH TABL ...

  8. MySQL数据库(三)索引总结

    一.什么是索引?  索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-树的形式保存. 如果没有索引,执行查询时MySQL必须从第一个记录开始扫描整个表的所有记录,直至找到符合要求的记录 ...

  9. Yaf 完全精通

    bugs 这样 _Bootstrap 的话,会导致严重的后果,cpu 100%

  10. socketserver 实现并发

    基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环 socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题) server类: reque ...