一、作用域

  1. 作用域共有两种主要的工作模型:第一种是最为普遍的,被大多数编程语言所采用的词法作用域,另外一种叫作动态作用域;
  2. JavaScript所采用的作用域模式是词法作用域。

1.词法作用域

  1. 词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。
  2. JavaScript 中有两个机制可以“欺骗”词法作用域:

    • eval(..):可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在运行时) ;
    • with:通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时) 。
    • 这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。

2.函数作用域和块级作用域

  1. 函数作用域: 函数是 JavaScript 中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,即函数内定于的函数和变量为该函数私有;
  2. 块级作用域:

    • 块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指 { .. } 内部)
    • ES6前在JavaScript中并不存在块级作用域( 例外:try/catch 结构在 catch 分句中具有块作用域);
    • 在 ES6 中引入了 let 关键字( var 关键字的表亲) ,用来在任意代码块中声明变量。 if(..) { let a = 2; } 会声明一个劫持了 if 的 { .. } 块的变量,并且将变量添加到这个块中(另外常量定义const也具有块级作用域)。

3.函数和变量的提升

(1)、提升

  1. 函数作用域和块作用域的行为是一样的,即,某个作用域内的变量,都将附属于这个作用域。
  2. 引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来;
  3. 因此包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理;
  4. 当看到 var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明: var a; 和 a = 2; 。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。

    • 这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程就叫作提升
  5. 每个作用域都会进行提升操作;

(2)、函数优先

  1. 函数声明和变量声明都会被提升。但是函数会首先被提升,然后才是变量。

foo(); // 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
};
  • 会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:

function foo() {
console.log( 1 );
}
foo(); // 1
foo = function() {
console.log( 2 );
};
  • var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽略了) ,因为函数声明会被提升到普通变量之前。
  • 尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

二、作用域闭包

(1)、理解闭包

  • 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
  1. 在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
  2. 在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

(2)、闭包的用途

  1. 可以读取函数内部的变量;
  2. 让变量的值始终保持在内存中。

(3)、闭包的产生实例

  1. 可以读取函数内部的变量

function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包的效果。
  • 在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间;
  • 闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收,因为 bar() 本身在使用;
  • 拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。
  • bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
  1. 循环和闭包:

for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
  • 正常情况下,我们对这段代码行为的预期是分别输出数字 1~5,每秒一次,每次一个。但实际上,这段代码在运行时会以每秒一次的频率输出五次 6:

    • 延迟函数的回调会在循环结束时才执行。事实上,当定时器运行时即使每个迭代中执行的是 setTimeout(.., 0) ,所有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个 6 出来。
    • 实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i,即所有函数共享一个 i 的引用 。
  • 解决方案:使用 IIFE在每次迭代中将本次迭代的i传入创建的作用域并封闭起来;

for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
  • 在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。

(4)、使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

    • 解决方案:在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

JavaScript之作用域和闭包的更多相关文章

  1. 我认知的javascript之作用域和闭包

    说到javascript,就不得不说javascript的作用域和闭包:当然,还是那句老话,javascript在网上都说得很透彻了,我也就不过多的强调了: 作用域:javascript并没有像其他的 ...

  2. 剖析JavaScript函数作用域与闭包

    在我们写代码写到一定阶段的时候,就会想深究一下js,javascript是一种弱类型的编程语言,而js中一个最为重要的概念就是执行环境,或者说作用域.作用域重要性体现在哪呢?首先,函数在执行时会创建作 ...

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

    作用域和闭包 ・作用域 引擎:从头到尾负责整个JavaScript的编译及执行过程. 编译器:负责语法分析及代码生成等. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非 ...

  4. JavaScript 函数作用域和闭包

    函数作用域和闭包  词法作用域   它们在定义它们的作用域里运行,而不是在执行的作用域运行,但是只有在运行时,作用域链中的属性才被 定义(调用对象),此时,可访问任何当前的绑定.   调用对象     ...

  5. JavaScript从作用域到闭包

    目录 作用域 全局作用域和局部作用域 块作用域与函数作用域 作用域中的声明提前 作用域链 函数声明与赋值 声明式函数.赋值式函数与匿名函数 代码块 自执行函数 闭包  作用域(scope) 全局作用域 ...

  6. javascript的作用域和闭包(三)闭包与模块

    一些很重要的说明:前面三篇博客详细的介绍了,引擎与编译器和作用域的关系,重点需要理解的是编译器中的分词与词法分析,JavaScript的特有的“赋值操作的左右侧”引用操作:编译阶段的词法作用域的工作原 ...

  7. JavaScript的作用域与闭包

    JavaScript的作用域以函数为界,不同的函数拥有相对独立的作用域.函数内部可以声明和访问全局变量,也可以声明局部变量(使用var关键字,函数的参数也是局部变量),但函数外部无法访问内部的局部变量 ...

  8. 前端知识体系:JavaScript基础-作用域和闭包-JavaScript的作用域和作用域链

    JavaScript的作用域和作用域链 作用域: 变量的作用域无非两种:全局作用域和局部作用域 全局作用域: 最外层函数定义的变量拥有全局作用域.即对任何内部函数来说都是可以访问的. <scri ...

  9. Javascript的作用域和闭包(一)

    一.作用域是什么? 几乎所有的编程语言最基本的功能之一,就是能够存储变量的值,并且能访问和修改这些值. 修改变量值的过程我们通常在程序执行时,称为改变一个对象的状态.有了状态,让程序变得有非常有趣. ...

随机推荐

  1. React 事件 传参

    绑定事件用 bind方法,第一个参数是 this,第二个参数是需要传入的值如下: <a onClick={this.updateLabel.bind(this,'参数1','参数2')} > ...

  2. [Swift]编程语言:文档修订历史

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  3. python中threading模块中的Join类

    join类是threading中用于堵塞当前主线程的类,其作用是阻止全部的线程继续运行,直到被调用的线程执行完毕或者超时.具体代码如下: import threading,time def doWai ...

  4. git基本操作(入门)

    下面以一个最简单的开发过程,呈现git最基本的操作命令 1.下载代码(以code命名仓库为例) git clone xxxxx/code.git cd code 2.查看所有分支 git branch ...

  5. poj 2299 Ultra-QuickSort 归并排序求逆序数对

    题目链接: http://poj.org/problem?id=2299 题目描述: 给一个有n(n<=500000)个数的杂乱序列,问:如果用冒泡排序,把这n个数排成升序,需要交换几次? 解题 ...

  6. Lomsat gelral cf-600e

    http://codeforces.com/contest/600/problem/E 暴力启发式合并就行了 提示:set的swap的复杂度是常数,这方面可以放心 我先打了一个很naive的算法 #i ...

  7. 贪心 Codeforces Round #273 (Div. 2) C. Table Decorations

    题目传送门 /* 贪心:排序后,当a[3] > 2 * (a[1] + a[2]), 可以最多的2个,其他的都是1个,ggr,ggb, ggr... ans = a[1] + a[2]; 或先2 ...

  8. post和get提交服务器编码过程

    参考资料:http://blog.csdn.net/z55887/article/details/46975679 先说出一个知识点: 如果浏览器端编码是UTF-8,那在服务器端解决乱码问题的方法有两 ...

  9. IOS利用Core Text对文字进行排版 - 转

    原贴地址:http://hi.baidu.com/jwq359699768/blog/item/5df305c893413d0a7e3e6f7b.html core text 这个包默认是没有的,要自 ...

  10. ORA-28002错误原因及解决办法

    在oracle database 11g中,默认在default概要文件中设置了“PASSWORD_LIFE_TIME=180天”所导致.密码过期后,业务进程连接数据库异常,影响业务使用.数据库密码过 ...