本篇是《你不知道的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. windows下SVN服务器搭建--VisualSVN与TortoiseSVN的配置安装

    在讲解之前,我们来思考两个问题: 1.什么是版本控制 2.为什么要用版本控制工具 ----------------------------------------------------- 版本控制工 ...

  2. 干货 | 10分钟玩转PWA

    关于PWA PWA(Progressive Web App), 即渐进式web应用.PWA本质上是web应用,目的是通过多项新技术,在安全.性能.体验等方面给用户原生应用的体验.而且无需像原生应用那样 ...

  3. Python3创建项目时创建了一个叫做“keyword"的包,运行项目时报ImportError: cannot import name 'iskeyword'错误

    导致该问题的原因为在Python3中keyword是python的关键字包,所以在给包命名时应避免使用关键字进行命名.解决方法,将keword包名称修改为'keywords'就可以了.

  4. Vue 获取登录用户名

    本来是打算登录的时候把用户名传过去,试了几次都没成功,然后改成用cookie保存用户名,然后在读取就行了, 登录时候设置cookie setCookie(c_name,c_pwd,exdays) { ...

  5. 1111. Online Map (30)

    Input our current position and a destination, an online map can recommend several paths. Now your jo ...

  6. 谷歌浏览器运行Flash

    最近有人问我谷歌浏览器的flash总是要点击手动运行才可以使用.看了很多网上很多教程,并没有比较好的解决方案. 自己找了相关资料后,找到了一个比较好的完整的.特此在这边放出来给大家使用. 新建记事本, ...

  7. javascript之原型链

    JavaScript 中,万物皆对象!(对于编程而言,可以说万物皆对象.) js中的原型链的作用时什么呢? 我自己的理解是,给一个人赋予一些技能, function people(name,age,s ...

  8. django的视图函数

    一.视图函数view 视图函数是接收一个请求(request对象),并返回响应的函数 1. HttpResponse响应请求 这个方法是返回字符串一类的,可以识别标签 2. render响应请求 re ...

  9. 探索微信小程序之路

    记录一下每日的知识点,时不时温习一下. 视图与渲染对于页面中的数据,以json的方式存放在js文件的data中 判断的使用: <view wx:if='{{true}}'> 为真时显示 & ...

  10. Qt打包发布exe

    1.首先以 release 方式编译源代码,然后将生成的a. exe 程序放到一个单独的文件夹中. 2.再从开始菜单打开 Qt 命令行工具. 3.在命令行中,进入到第一步中a. exe 程序所在的文件 ...