本篇是《你不知道的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. HTML5新规范和CSS3新特性

    1.HTML5最新规范(HTML5是HTML.XHTML以及HTML.DOM的新标准) ①用于绘画canvas元素 ②用于媒介回放的video和audio元素 ③对本地离线存储的更好支持 ④新的特俗内 ...

  2. [精华][推荐]CAS SSO实现单点登录框架学习源码

    1.通过下载稳定版本的方式下载cas的相关源码包,如下: 直接选择4.2.1的稳定代码即可 2.我们项目中的版本版本使用maven apereo远程库去下载 通过远程maven库下载cas-serve ...

  3. MySQL 导入导出数据

    导入数据 1. 使用 MySQl Workbench 界面操作 导入 csv JSON 格式文件 2 使用 load data 命令 load data 命令官网教程 https://dev.mysq ...

  4. mac电脑Git提交代码到Github提示git-credential-osxkeychain 验证解决方案

    ## 啊哈哈 这个简单,直接给出当前mac电脑登录账号密码即可,^_*,拿走不谢!!

  5. layer.tips属性

    layer.tips(新加的内容,'选择节点',{time: 0, area: ['20%', '20%'], skin: 'layui-layer-rim', tips: [3, '#ffffff' ...

  6. Sublime Text 3安装emmet(ZenCoding)

    1.安装 Package Ctrol: 使用 ctrl + - 打开控制台,输入以下代码 import urllib.request,os; pf = 'Package Control.sublime ...

  7. 浅谈nodejs和php

    现在,Web开发公司和开发人员可以选择多种技术栈来构建Web应用程序.早期网络发展,不同的技术被用于前端和后端开发.但是,随着Node.js的发布,布局发生了变化,因为它允许开发人员使用 JavaSc ...

  8. spring学习(二)---依赖注入

    spring第二个特性是依赖注入. 学习依赖注入,首先应该明白两个问题:1,谁依赖谁:2,谁注入,注入什么? 首先还是看代码: 还是这个bean: package testSpring.busines ...

  9. MySQL系统变量 sql_mode 详解

    转载自:http://tech.it168.com/a2012/0822/1388/000001388401_all.shtml MySQL数据类型:SQL_MODE设置不容忽视 SQL_MODE可能 ...

  10. LAPM 相关实验01

    目录 lab1 静态.动态资源的区别lab2 部署phpMyadminlab3 部署wordpresslab4 编译安装php-Xcache加速器lab5 fcgi实现lamp lab1 静态.动态资 ...