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 相关文章推荐
jQuery 1.4 15个你应该知道的新特性(译)
Jan 24 Javascript
jQuery+jqmodal弹出窗口实现代码分明
Jun 14 Javascript
jquery struts 验证唯一标识(公用方法)
Mar 27 Javascript
jquery mobile的触控点击事件会多次触发问题的解决方法
May 08 Javascript
Jquery easyui 实现动态树
Nov 17 Javascript
Ubuntu 16.04 64位中搭建Node.js开发环境教程
Oct 19 Javascript
Vue2.0表单校验组件vee-validate的使用详解
May 02 Javascript
完美解决浏览器跨域的几种方法(汇总)
May 08 Javascript
vue项目中vue-i18n和element-ui国际化开发实现过程
Apr 25 Javascript
详解微信小程序框架wepy踩坑记录(与vue对比)
Mar 12 Javascript
JavaScript中的ES6 Proxy的具体使用
Jun 16 Javascript
一起深入理解js中的事件对象
Feb 06 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
建立动态的WML站点(三)
2006/10/09 PHP
php,ajax实现分页
2008/03/27 PHP
php自定义截取中文字符串-utf8版
2017/02/27 PHP
Laravel框架分页实现方法分析
2018/06/12 PHP
PHP常见过waf webshell以及最简单的检测方法
2019/05/21 PHP
JavaScript库 开发规则
2009/01/31 Javascript
Whatever:hover 无需javascript让IE支持丰富伪类
2010/06/29 Javascript
js正文内容高亮效果的实现方法
2013/06/30 Javascript
javascript实现表格排序 编辑 拖拽 缩放
2015/01/02 Javascript
javascript文本框内输入文字倒计数的方法
2015/02/24 Javascript
在JavaScript中操作时间之setYear()方法的使用
2015/06/12 Javascript
javascript九宫格图片随机打乱位置的实现方法
2017/03/15 Javascript
NodeJs入门教程之定时器和队列
2019/03/08 NodeJs
javascript实现的字符串转换成数组操作示例
2019/06/13 Javascript
mapboxgl区划标签避让不遮盖实现的代码详解
2020/07/01 Javascript
Python读写txt文本文件的操作方法全解析
2016/06/26 Python
配置 Pycharm 默认 Test runner 的图文教程
2018/11/30 Python
win8.1安装Python 2.7版环境图文详解
2019/07/01 Python
django将网络中的图片,保存成model中的ImageField的实例
2019/08/07 Python
解决python 读取 log日志的编码问题
2019/12/24 Python
Python描述符descriptor使用原理解析
2020/03/21 Python
使用Keras实现简单线性回归模型操作
2020/06/12 Python
如何实现一个python函数装饰器(Decorator)
2020/10/12 Python
Django中使用Celery的方法步骤
2020/12/07 Python
HTML5 Geolocation API的正确使用方法
2018/12/04 HTML / CSS
Urban Outfitters美国官网:美国生活方式品牌
2016/08/26 全球购物
英国计算机产品零售商:Novatech(定制个人电脑、笔记本电脑、工作站和服务器)
2018/01/28 全球购物
澳大利亚工具仓库:Tools Warehouse
2018/10/15 全球购物
耐克奥地利官网:Nike奥地利
2019/08/16 全球购物
如何将一个描述日期或日期/时间的字符串转换为一个Date对象
2015/10/13 面试题
测绘工程个人的自我评价
2013/11/10 职场文书
《青海高原一株柳》教学反思
2014/04/25 职场文书
525心理活动总结
2014/07/04 职场文书
行政复议决定书
2015/06/24 职场文书
2016年感恩父亲节活动总结
2016/04/01 职场文书
六个好看实用的 HTML + CSS 后台登录入口页面
2022/04/28 HTML / CSS