本篇是《你不知道的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. 从 Basic Paxos 到 Multi Paxos 到 Raft

    在朴素Paxos算法中, 各个节点经过 Prepare 和 Accept 阶段, 会达成一个值, 这个值一旦达成, 就不能被修改, 如下例子: 图示1 上面的操作几乎没有任何实用价值, 于是演变成下面 ...

  2. xbee3的先进性功能用法

    xbee3以及xbee3 PRO 是digi无线模块的又一大突破:不仅实现了所有2.4G的模块整合,更在以后的程序更新中会增加蓝牙功能:它打通了xbee系列1和系列2之间的壁垒:不同于xbee S2C ...

  3. opencv的安装

    网上搜了好多文章安装opencv3.2.0都未能成功,写的也个不相同,后来找到了opencv官网的教程,看了后才发现,这上面才是最详细的. 于是按照opencv官网教程安装,安装的一半就中断了.经过苦 ...

  4. WindowsPE权威指南 第二章 小工具 pedump代码的C语言实现

    2016-11-16 16:29:07 主程序代码 pedump.c #include <windows.h> #include <Richedit.h> #include & ...

  5. 贪心-Wooden Sticks

    先将火柴按照长度(或重量)优先排序,在不断遍历数组,找出其中重量(长度)递增子序列,并标记 Problem Description There is a pile of n wooden sticks ...

  6. cosfuture logs

    1,RESTClient用于调试接口的插件 2, PHP_AUTH_USER如何发送 $a = base64_encode("username:password"); 注意中间是冒 ...

  7. Unity使用代码动态给按钮赋值各个状态下的图片

    一个小知识点,怕忘记,所以记录下.废话不多说,直接上代码: 未赋值之前: 使用下面代码赋值: using UnityEngine; using UnityEngine.UI; public class ...

  8. mysql利用LAST_INSERT_ID实现id生成器

    首先了解 LAST_INSERT_ID LAST_INSERT_ID 有自己的存储空间,能存一个数字 不带参数时返回最近insert的那行记录的自增字段值.带参数时会将自己存储的数字刷成参数给定的值 ...

  9. scrapy的基础概念和流程

    1. 什么是scrapy Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取. Scrapy 使用了Twisted['twɪstɪd]异步网 ...

  10. Yaf 完全精通

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