javascript作用域和闭包使用详解


Posted in Javascript onApril 25, 2014

作用域的嵌套将形成作用域链,函数的嵌套将形成闭包。闭包与作用域链是 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 的当前值)成为了其内层函数的状态的一部分被保存了下来。

Javascript 相关文章推荐
通过JS获取用户本地图片路径并显示的代码
Feb 16 Javascript
JQuery筛选器全系列介绍
Aug 27 Javascript
js快速排序的实现代码
Dec 08 Javascript
深入了解Node.js中的一些特性
Sep 25 Javascript
javascript 自定义回调函数示例代码
Sep 26 Javascript
javascript父子页面通讯实例详解
Jul 17 Javascript
JavaScript中实现Map的示例代码
Sep 09 Javascript
Javascript字符串常用方法详解
Jul 21 Javascript
详解本地Node.js服务器作为api服务器的解决办法
Feb 28 Javascript
详解node+express+ejs+bootstrap构建项目
Sep 27 Javascript
IE11下使用canvas.toDataURL报SecurityError错误的解决方法
Nov 19 Javascript
在微信小程序中使用图表的方法示例
Apr 25 Javascript
jQuery选择器简明总结(含用法实例,一目了然)
Apr 25 #Javascript
jquery选择器排除某个DOM元素的方法(实例演示)
Apr 25 #Javascript
js动态移动滚动条至底部示例代码
Apr 24 #Javascript
javaScript如何处理从java后台返回的list
Apr 24 #Javascript
jquery如何扑捉回车键触发的事件
Apr 24 #Javascript
用unescape反编码得出汉字示例
Apr 24 #Javascript
标题过长使用javascript按字节截取字符串
Apr 24 #Javascript
You might like
php 函数使用方法与函数定义方法
2010/05/09 PHP
第四章 php数学运算
2011/12/30 PHP
PHP最常用的正则表达式
2017/02/13 PHP
PHP调用Mailgun发送邮件的方法
2017/05/04 PHP
php实现的mongoDB单例模式操作类
2018/01/20 PHP
使用jQuery避免鼠标双击的解决方案
2013/08/21 Javascript
多种方法判断Javascript对象是否存在
2013/09/22 Javascript
javascript中定义类的方法详解
2015/02/10 Javascript
本人自用的global.js库源码分享
2015/02/28 Javascript
简介可以自动完成UI的AngularJS工具angular-smarty
2015/06/23 Javascript
js实现简单折叠、展开菜单的方法
2015/08/28 Javascript
jQuery+css实现炫目的动态块漂移效果
2016/01/28 Javascript
jquery表单提交带错误信息提示效果
2017/03/09 Javascript
详解vue 组件之间使用eventbus传值
2017/10/25 Javascript
jQuery读取本地的json文件(实例讲解)
2017/10/31 jQuery
在vue项目中使用sass语法问题
2019/07/18 Javascript
js实现的格式化数字和金额功能简单示例
2019/07/30 Javascript
在Linux系统上通过uWSGI配置Nginx+Python环境的教程
2015/12/25 Python
Python将图片批量从png格式转换至WebP格式
2020/08/22 Python
基于python(urlparse)模板的使用方法总结
2017/10/13 Python
对python中词典的values值的修改或新增KEY详解
2019/01/20 Python
Python列表常见操作详解(获取,增加,删除,修改,排序等)
2019/02/18 Python
Python字符串内置函数功能与用法总结
2019/04/16 Python
Python3 sys.argv[ ]用法详解
2019/10/24 Python
Python实现投影法分割图像示例(一)
2020/01/17 Python
浅谈Pycharm最有必要改的几个默认设置项
2020/02/14 Python
python numpy生成等差数列、等比数列的实例
2020/02/25 Python
Python读写压缩文件的方法
2020/07/30 Python
Python操作word文档插入图片和表格的实例演示
2020/10/25 Python
三维科技面试题
2013/07/27 面试题
下面代码从性能上考虑,有什么问题
2015/04/03 面试题
vue路由实现登录拦截
2021/03/24 Vue.js
教师节活动总结
2014/08/29 职场文书
困难补助申请报告
2015/05/19 职场文书
原来闭幕词是这样写的呀!
2019/07/01 职场文书
利用Pycharm连接服务器的全过程记录
2021/07/01 Python