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 相关文章推荐
浅谈javascript的原型继承
Jul 25 Javascript
JavaScript实现把rgb颜色转换成16进制颜色的方法
Jun 01 Javascript
D3.js实现折线图的方法详解
Sep 21 Javascript
jQuery.uploadify文件上传组件实例讲解
Sep 23 Javascript
微信小程序 location API接口详解及实例代码
Oct 12 Javascript
js 提取某()特殊字符串长度的实例
Dec 06 Javascript
JavaScript中set与get方法用法示例
Aug 15 Javascript
javascript闭包的使用之按钮切换功能
Aug 30 Javascript
VUE 实现滚动监听 导航栏置顶的方法
Sep 11 Javascript
Vue通过ref父子组件拿值方法
Sep 12 Javascript
微信小程序如何使用globalData的方法
Jun 06 Javascript
vue中后端做Excel导出功能返回数据流前端的处理操作
Sep 08 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
vBulletin Forum 2.3.xx SQL Injection
2006/10/09 PHP
FleaPHP的安全设置方法
2008/09/15 PHP
基于PHP遍历数组的方法汇总分析
2013/06/08 PHP
PHP strip_tags() 去字符串中的 HTML、XML 以及 PHP 标签的函数
2016/05/22 PHP
jquery 操作DOM案例代码分享
2012/04/05 Javascript
jQuery+CSS实现菜单滑动伸展收缩(仿淘宝)
2013/03/22 Javascript
JavaScript 垃圾回收机制分析
2013/10/10 Javascript
js showModalDialog参数的使用详解
2014/01/07 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
基于jquery css3实现点击动画弹出表单源码特效
2015/08/31 Javascript
简单的jQuery banner图片轮播实例代码
2016/03/04 Javascript
javascript与jquery动态创建html元素示例
2016/07/25 Javascript
利用js判断手机是否安装某个app的多种方案
2017/02/13 Javascript
详解react使用react-bootstrap当轮子造车
2017/08/15 Javascript
使用JSON格式提交数据到服务端的实例代码
2018/04/01 Javascript
webuploader实现上传图片到服务器功能
2018/08/16 Javascript
JavaScript 中 JSON.parse 函数 和 JSON.stringify 函数
2018/12/05 Javascript
微信小程序上传图片到php服务器的方法
2019/05/23 Javascript
React 父子组件通信的实现方法
2019/12/05 Javascript
python调用java模块SmartXLS和jpype修改excel文件的方法
2015/04/28 Python
python遍历数组的方法小结
2015/04/30 Python
PyQt实现界面翻转切换效果
2018/04/20 Python
python 解压pkl文件的方法
2018/10/25 Python
Python中的 ansible 动态Inventory 脚本
2020/01/19 Python
对tensorflow 中tile函数的使用详解
2020/02/07 Python
python GUI库图形界面开发之PyQt5时间控件QTimer详细使用方法与实例
2020/02/26 Python
python计算Content-MD5并获取文件的Content-MD5值方式
2020/04/03 Python
Python3 搭建Qt5 环境的方法示例
2020/07/16 Python
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
Haglöfs瑞典官方网站:haglofs火柴棍,欧洲顶级户外品牌
2018/10/18 全球购物
《黄河颂》教学反思
2014/02/07 职场文书
委托书样本
2014/04/02 职场文书
节约每一滴水演讲稿
2014/09/09 职场文书
一些让Python代码简洁的实用技巧总结
2021/08/23 Python
Vue如何清空对象
2022/03/03 Vue.js
Java无向树分析 实现最小高度树
2022/04/09 Javascript