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 chili图片远处放大插件
Nov 30 Javascript
window.open 以post方式传递参数示例代码
Feb 27 Javascript
JQuery实现级联下拉框效果实例讲解
Sep 17 Javascript
深入理解$.each和$(selector).each
May 15 Javascript
通过jquery-ui中的sortable来实现拖拽排序的简单实例
May 24 Javascript
详解Vue2.0里过滤器容易踩到的坑
Jun 01 Javascript
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
Aug 23 jQuery
关于axios如何全局注册浅析
Jan 14 Javascript
JavaScript中使用import 和require打包后实现原理分析
Mar 07 Javascript
在Vue项目中引入腾讯验证码服务的教程
Apr 03 Javascript
详解关于webpack多入口热加载很慢的原因
Apr 24 Javascript
JavaScript实现筛选数组
Mar 02 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定时发送服务的解决办法
2017/04/23 PHP
PHP网页安全认证的实例详解
2017/09/28 PHP
php中的钩子理解及应用实例分析
2019/08/30 PHP
PHP设计模式入门之状态模式原理与实现方法分析
2020/04/26 PHP
Javascript 中的 call 和 apply使用介绍
2012/02/22 Javascript
javascript对select标签的控制(option选项/select)
2013/01/31 Javascript
js 程序执行与顺序实现详解
2013/05/13 Javascript
js 遍历json返回的map内容示例代码
2013/10/29 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
2014/07/10 Javascript
jquery动态加载js/css文件方法(自写小函数)
2014/10/11 Javascript
node.js中的http.createClient方法使用说明
2014/12/15 Javascript
JavaScript实现可拖拽的拖动层Div实例
2015/08/05 Javascript
javascript中加var和不加var的区别 你真的懂吗
2016/01/06 Javascript
浅谈在js传递参数中含加号(+)的处理方式
2016/10/11 Javascript
原生JS实现图片无缝滚动方法(附带封装的运动框架)
2017/10/01 Javascript
ES6中的Promise代码详解
2017/10/09 Javascript
用js简单提供增删改查接口
2019/05/12 Javascript
微信小程序实现页面分享onShareAppMessage
2019/08/12 Javascript
聊聊鉴权那些事(推荐)
2019/08/22 Javascript
vue实现百度搜索功能
2020/12/28 Javascript
自动化Nginx服务器的反向代理的配置方法
2015/06/28 Python
Python3.6正式版新特性预览
2016/12/15 Python
Python元组操作实例分析【创建、赋值、更新、删除等】
2017/07/24 Python
python生成器,可迭代对象,迭代器区别和联系
2018/02/04 Python
django的ORM模型的实现原理
2019/03/04 Python
pycharm创建一个python包方法图解
2019/04/10 Python
Python 解析库json及jsonpath pickle的实现
2020/08/17 Python
美国家喻户晓的保健品品牌:Vitamin World(维他命世界)
2016/08/19 全球购物
巴西手表购物网站:eclock
2019/03/19 全球购物
大学生护理专业自荐信
2013/10/03 职场文书
小学生打架检讨书
2014/01/26 职场文书
红旗团支部事迹材料
2014/01/27 职场文书
《草虫的村落》教学反思
2014/02/16 职场文书
2015年小学语文教学工作总结
2015/05/25 职场文书
民间借贷纠纷答辩状
2015/08/03 职场文书
pytorch中的model=model.to(device)使用说明
2021/05/24 Python