作用域的嵌套将形成作用域链,函数的嵌套将形成闭包。闭包与作用域链是 JavaScript 区别于其它语言的重要特性之一。

作用域

JavaScript 中有两种作用域:函数作用域和全局作用域。

在一个函数中声明的变量以及该函数的参数享有同一个作用域,即函数作用域。一个简单的函数作用域的例子:

function foo() {
var bar = 1;
{
var bar = 2;
}
return bar; // 2
}

不同于C等其它有块作用域的语言,这里将始终返回 2 。

全局作用域,对于浏览器来说可以理解为 window 对象(Node.js则是 global):

var bar = 1;
function foo() {}
alert(window.bar); // 1
alert(window.foo); // "function foo() {}"

对于变量 bar 和函数 foo 都属于全局作用域,都是 window 的一个属性。

作用域链

在 JavaScript 中访问一个变量时,将从本地变量和参数开始,逐级向上遍历作用域直到全局作用域。

var scope = 0, zero = "global-scope";
(function(){
var scope = 1, one = "scope-1";
(function(){
var scope = 2, two = "scope-2";
(function(){
var scope = 3, three = "scope-3";
// scope-3 scope-2 scope-1 global-scope
console.log([three, two, one, zero].join(" "));
console.log(scope); // 3
})();
console.log(typeof three); // undefined
console.log(scope); // 2
})();
console.log(typeof two); // undefined
console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0

在最里层的函数中,各个变量都能被逐级遍历并输出。而倒数第二层的函数中,变量 three 无法遍历找到,所以输出了 undefined 。

举一个通俗点的例子,你准备要花钱买点东西时,会先摸摸自己的钱包,没了你可以找你爸要,你爸也没有就再找你爷爷,... 。而你爸没钱买东西时,他并不会来找你要。

闭包

在一个函数中,定义另一个函数,称为函数嵌套。函数的嵌套将形成一个闭包。

闭包与作用域链相辅相成,函数的嵌套在产生了链式关系的多个作用域的同时,也形成了一个闭包。

function bind(func, target) {
return function() {
func.apply(target, arguments);
};
}

那么怎么理解闭包呢?

外部函数不能访问内嵌函数
外部函数也不能访问内嵌函数的参数和变量
而内嵌函数可以访问外部函数的参数和变量
换一个说法:内嵌函数包含了外部函数的作用域
我们再看看之前讲述的作用域链的例子,这次从闭包的角度来理解下:

var scope = 0, zero = "global-scope";
(function(){
var scope = 1, one = "scope-1";
(function(){
var scope = 2, two = "scope-2";
(function(){
var scope = 3, three = "scope-3";
// scope-3 scope-2 scope-1 global-scope
console.log([three, two, one, zero].join(" "));
console.log(scope); // 3
})();
console.log(typeof three); // undefined
console.log(scope); // 2
})();
console.log(typeof two); // undefined
console.log(scope); // 1
})();
console.log(typeof one); // undefined
console.log(scope); // 0

最里层的函数能访问到其内部和外部定义的所有变量。而倒数第二层的函数无法访问到最里层的变量,同时,最里层的 scope = 3 这个赋值操作并没有对其外部的同名变量产生影响。

再换个角度来理解闭包:

每次外部函数的调用,内嵌函数都会被创建一次
在它被创建时,外部函数的作用域(包括任何本地变量、参数等上下文), 会成为每个内嵌函数对象的内部状态的一部分,即使在外部函数执行完并退出后
看下面的例子:

var i, list = [];
for (i = 0; i < 2; i += 1) {
list.push(function(){
console.log(i);
});
}
list.forEach(function(func){
func();
});

我们将得到两次 "2" ,而不是预期的 "1" 和 "2" ,这是因为在 list 中的两个函数访问的变量 i 都是其上一层作用域的同一个变量。

我们改动下代码,以利用闭包来解决这个问题:

var i, list = [];
for (i = 0; i < 2; i += 1) {
list.push((function(j){
return function(){
console.log(j);
};
})(i));
}
list.forEach(function(func){
func();
});

外层的“立即执行函数”接收了一个参数变量 i ,在其函数内以参数 j 的形式存在,它与被返回的内层函数中的名称 j 指向同一个引用。外层函数执行并退出后,参数 j (此时它的值为 i 的当前值)成为了其内层函数的状态的一部分被保存了下来。

Js_闭包跟作用域的更多相关文章

  1. js闭包的作用域以及闭包案列的介绍:

    转载▼ 标签: it   js闭包的作用域以及闭包案列的介绍:   首先我们根据前面的介绍来分析js闭包有什么作用,他会给我们编程带来什么好处? 闭包是为了更方便我们在处理js函数的时候会遇到以下的几 ...

  2. 那些年我们一起过的JS闭包,作用域,this,让我们一起划上完美的句号。

    之前有写过闭包,作用域,this方面的文章,但现在想想当时写的真是废话太多了,以至于绕来绕去的,让新手反而更难理解了,所以就有了此篇文章,也好和闭包,作用域,this告一段落. 第一个问题:什么是闭包 ...

  3. Javascript闭包与作用域

    摘自开源中国 闭包和作用域是js中比较重要的知识,自己理解起来也有一定的难度 1.Javascript的作用域是函数作用域而非块级作用域 ? 1 2 3 4 5 6 7 8 9 10 11 12 // ...

  4. Javascript中闭包的作用域链

    作用域定义了在当前上下文中能够被访问到的成员,在Javascript中分为全局作用域和函数作用域,通过函数嵌套可以实现嵌套作用域. 闭包一般发生在嵌套作用域中.闭包是JavaScript最强大的特性之 ...

  5. 读书时间《JavaScript高级程序设计》三:函数,闭包,作用域

    上一次看了第6章,面向对象.这里接着看第7章. 第7章:函数表达式 定义函数有两种方式:函数声明.函数表达式 //函数声明 function functionName(arg0,arg1,arg2){ ...

  6. Python闭包及其作用域

    Python闭包及其作用域 关于Python作用域的知识在python作用域有相应的笔记,这个笔记是关于Python闭包及其作用域的详细的笔记 如果在一个内部函数里,对一个外部作用域(但不是全局作用域 ...

  7. js 闭包,作用域,this 终结篇(转)

    之前有写过闭包,作用域,this方面的文章,但现在想想当时写的真是废话太多了,以至于绕来绕去的,让新手反而更难理解了,所以就有了此篇文章,也好和闭包,作用域,this告一段落. 第一个问题:什么是闭包 ...

  8. [ JS 进阶 ] 闭包,作用域链,垃圾回收,内存泄露

    原网址:https://segmentfault.com/a/1190000002778015 1. 什么是闭包? 来看一些关于闭包的定义: 闭包是指有权访问另一个函数作用域中变量的函数 --< ...

  9. JS闭包、作用域链、垃圾回收、内存泄露相关知识小结

    补充: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 闭包的三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变 ...

随机推荐

  1. env :让系统决定你命令的位置

    如果我们使用lua 运行脚本程序,或者使用python执行程序.我们不用明确 python的位置,可以使用env 命令来运行: env 程序介绍: NAME env - run a program i ...

  2. TiDB数据库集群安装以及注意事项

    今天尝试安装tidb集群.详细的安装步骤我们参考:https://pingcap.com/docs-cn/op-guide/ansible-deployment/ . 不过安装之前需要一些注意事项. ...

  3. Asp.net core 2.0.1 Razor 的使用学习笔记(二)

    ASP.net core 2.0.1 中 asp.net identity 2.0.1 的基本使用(一)—启用用户管理 一.修改和启用默认的用户账户管理和角色管理 在Data目录中添加Applicat ...

  4. 3.5星|《订阅》:Youtube对用户喜好的发现与应对

    订阅:数字时代的商业变现路径 主要内容我总结是Youtube对用户喜好的发现与应对.可以认为很多时候作者说的是Youtubu官方的态度与想法.穿插了许多Youtube上的成功创作者的故事. Youtu ...

  5. sql点滴45—mysql中group_concat用法

    group_concat(),手册上说明:该函数返回带有来自一个组的连接的非NULL值的字符串结果.比较抽象,难以理解. 通俗点理解,其实是这样的:group_concat()会计算哪些行属于同一组, ...

  6. windows下安装python3 新手上路

    本文只针对刚刚拿到“驾照”的实习生 老司机回去开车.. 下载python 地址:https://www.python.org/ 选择Downloads下的windows 选择自己合适的版本  下面的是 ...

  7. 4.Dubbo2.5.3集群容错和负载均衡

    转载请出自出处:http://www.cnblogs.com/hd3013779515/ 1.集群容错和负载均衡原理 各节点关系: 这里的Invoker是Provider的一个可调用Service的抽 ...

  8. 将Vue-cli搭建的项目改造成多页面应用时对项目结构和配置的调整

    创建项目 首先初始化一个Vue项目模板,之后在模板下载时候会弹出如下配置选项 vue init webpack demo 配置好后按下回车就构建完成了Vue脚手架,之后cd进入项目,并且进行node模 ...

  9. python第二十九课——文件读写(写数据的操作)

    演示写数据的操作: 结论:往文件中写入数据,如果文件不存在,先创建文件,再写入内容 #1.打开文件 fw=open(r'd.txt','w',encoding='utf-8') #2.写数据操作 fw ...

  10. c++ ActiveX基础1:使用VS2010创建MFC ActiveX工程项目

    1.ActiveX的基本概念 ActiveX控件可以看作是一个极小的服务器应用程序,它不能独立运行,必须嵌入到某个容器程序中,与该容器一起运行.这个容器包括WEB网页,应用程序窗体等... Activ ...