1.作用域

作用域是根据名称找变量的一套规则。

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

引擎在查找变量时执行怎样的查找,会影响最终的查找结果。

当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询:

  • console.log(a)对a的引用是一个RHS引用,这里a并没有赋予任何值。相应地,需要查找并取得a的值,这样才能将值传递给console.log(..)

  • a = 2对a的引用是LHS引用,因为实际上我们并不关心当前的值是什么,只是 为=2这个赋值操作找到一个目标。

当一个块或函数嵌套在另一块或函数中时,就发生了作用域的嵌套。在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域为止。

2.函数作用域

2.1 函数中的作用域

function foo(a){
var b = 2;
function bar(){
//...
}
//更多代码
var c = 3;
}

foo(...)的作用域气泡中包含了标识符a、b、c和bar。

bar(...)拥有自己的作用域气泡。全局作用域也有自己的作用域气泡,它只包含了一个标识符foo。

由于a、b、c和bar都附属于foo(...)的作用域气泡,因此无法从foo(...)的外部对它们进行访问。

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。

2.2 隐藏内部实现

“隐藏”作用域中的变量和函数好处是可以避免标识符之间的冲突。

当程序中加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发变量冲突。

这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象的属性。

var MyReallyCoolLibrary = {
awesome:"stuff",
doSomething:function(){
//...
},
doAnotherThing:function(){
//...
}
};

2.3 函数作用域

函数声明函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。

var a = 2;
//函数声明
function foo(){
var a = 3;
console.log(a);//3
}
foo();
console.log(a);//2
var a = 2;
//函数表达式
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2

(function foo(){...})作为函数表达式意味着foo只能在...所代表的位置中被访问,外部作用域则不行。

匿名函数表达式:

setTimeout(function(){
console.log("I waited 1 second!");
},1000);

函数表达式可以是匿名的,而函数声明则不可以省略函数名。

匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。

始终给函数表达式命名是一个最佳实践。

setTimeout(function() timeoutHandler{
console.log("I waited 1 second!");
},1000);

IIFE(Immediately-invoked function expression),即立即执行函数表达式

var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2

由于函数被包含在一对()括号内部,因此成为了一个表达式,通过在末尾加上另外一个()可以立即执行这个函数。

IIFE还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行之后当作参数传递进去。

var a = 2;
(function IIFE(def){
def(window);
})(function def(global){
var a = 3;
console.log(a);//3
console.log(global.a);//2
});

2.4 提升

引擎会在解释JavaScript代码之前先对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

a = 2;
var a;
console.log(a);//输出结果为2 /*
等价于
var a;
a = 2;
console.log(a);//输出结果为2
*/
console.log(a);//输出结果为undefined
var a = 2; /*
等价于
var a;
console.log(a);//输出结果为undefined
a = 2;
*/

上面的处理过程好像变量和函数声明从它们在代码中出现的位置被“移动”到了最上面,这个过程就叫作提升

每个作用域都会进行提升操作。

函数声明和变量都会被提升。但是一个值得注意的细节是函数会首先被提升,然后才是变量

foo();//1
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}; /*
等价于
function foo(){
console.log(1);
}
foo();//1
foo = function(){
console.log(2);
};
*/

注意:var foo尽管出现在function foo()...的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

尽管重复的var声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

foo();//3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
};
function foo(){
console.log(3);
}

在同一个作用域中进行重复定义是非常糟糕的,而且经常会导致各种奇怪的问题。

3.作用域闭包

JavaScript中闭包无处不在,你只需要能够识别并拥抱它。

闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包。

function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2--这就是闭包的效果

函数bar()的词法作用域能够访问foo()的内部作用域,在foo()执行后,其返回值(也就是内部的bar()函数)赋值给变量baz并调用baz()

foo()执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去foo()的内容不会再被使用,所以很自然地会考虑对其进行回收。

bar()拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。

bar()依然持有对该作用域的引用,而这个引用就叫作闭包

function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();//这就是闭包!
}
var fn;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
fn = baz;//将baz分配给全局变量
}
function bar(){
fn();//这就是闭包!
}
foo();
bar();//2

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

  1. JS之作用域与闭包

    JS之作用域与闭包   作用域在JS中同样也是一个重要的概念.它不复杂,因为ES5中只有全局作用域和函数作用域,我们都知道他没有块级作用域.但在ES6中多了一个let,他可以保证外层块不受内层块的影响 ...

  2. 解析js中作用域、闭包——从一道经典的面试题开始

    如何理解js中的作用域,闭包,私有变量,this对象概念呢? 就从一道经典的面试题开始吧! 题目:创建10个<a>标签,点击时候弹出相应的序号 先思考一下,再打开看看 //先思考一下你会怎 ...

  3. 你不知道的JS之作用域和闭包 附录

     原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...

  4. 你不知道的JS之作用域和闭包(五)作用域闭包

    原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...

  5. JS(作用域和闭包)

    1.对变量提升的理解 1.变量定义(上下文) 2.函数声明 2.说明 this 几种不同的使用场景 常见用法 1.作为构造函数执行 2.作为对象属性执行 3.作为普通函数执行(this === win ...

  6. 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域

      原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...

  7. js中作用域和闭包

    作用域链实例   (1) function example() { var age = 23; alert(age) } var age = 25; example(); alert(age); // ...

  8. js变量作用域和闭包的示例

    <script> /* js是函数级作用域,在函数内部的变量,内部都能访问, 外部不能访问内部的,但是内部可以访问外部的变量 闭包就是拿到本不该属于他的东西,闭包会造成内存泄漏,你不知道什 ...

  9. 你不知道的JS之作用域和闭包(四)(声明)提升

    原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...

随机推荐

  1. 铁轨(rails, ACM/ICPC CERC 1997,Uva 514)

    铁轨(rails, ACM/ICPC CERC 1997,Uva 514) 题目描述 某城市有一个火车站,铁轨铺设如图所示.有n节车厢从A方向驶入车站,按进站顺序编号为1~n.你的任务是让它们按照某种 ...

  2. 多tomcat 同一个浏览器 多个项目 会导致session覆盖

    1,多tomcat 同一个浏览器 同一个项目 会导致session覆盖 个人猜测:一个服务器中有多个Tomcat服务器多个项目,每个服务器占用不同的端口号,当在同一个浏览器里面同时打开2个系统时,一个 ...

  3. ubuntu下修改nginx的进程数

    1. 进入nginx配置文件:vim /etc/nginx/nginx.conf2. 将events下的worker_processes 修改为 你希望的数字,保存文件并退出3. 重启nginx: s ...

  4. gulp-babel 阻止了js文件编译的进程?

    现象 :   编译打包的js没有输出到目标文件夹里,只是单单的生成了一个目标目录,目录里没有文件 解决方法:gulp-babel ^8.0.0 使用了 ^7.0.1的 依赖插件.统一gulp-babe ...

  5. PHP中的文件包含

    在PHP中,包含文件有两种方式:include和require.这两种方式的功能一样,只有一个区别,就是使用require包含一个文件时,如果出现错误,脚本不会继续执行:而如果使用include包含, ...

  6. (第二周)scrum站立会议

    敏捷流程scrum中的很重要的一个制度之一每日站立会议 了解的内容: 问题:为啥不用email汇报代替每日会议? E-mail不能取代每日会议,E-mail只会增加沟通成本,而且不能提供细节信息或者给 ...

  7. MathExam小学一二年级计算题生成器V1.0

    MathExam小学一二年级计算题生成器v1.0 一.预估与实际 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning ...

  8. FileInputStream 读取文件数据的输入字节流

    package com.inputstream; /* File类: 用于描述一个文件或者文件夹的. 通过File对象我们可以读取文件或者文件夹的属性数据,如果我们需要读取文件的内容数据,那么我们需要 ...

  9. 个人作业2--APP案例分析

    产品 选择产品:酷狗音乐播放器 版本:Android版 选择理由:是我高中就开始用的音乐播放软件,在平时使用频率比较高,平时喜欢在累的时候听音乐放松. 调研 第一次上手体验 第一次使用的时候,感觉整个 ...

  10. Java 经典 书籍

    1.<你的灯还亮着么> 方法论 2.<程序员修炼之道 从小工到专家> 方法论 3.<发布!软件的设计与部署> 案例&经验总结 4.<思考,快与慢> ...