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写的日历(代码部分网摘)
Sep 20 Javascript
jQuery中:only-child选择器用法实例
Jan 03 Javascript
JavaScript中Cookies的相关使用教程
Jun 04 Javascript
JavaScript实现点击单元格改变背景色的方法
Feb 12 Javascript
微信小程序上传图片到服务器实例代码
Nov 07 Javascript
vue-router history模式下的微信分享小结
Jul 05 Javascript
Vue组件教程之Toast(Vue.extend 方式)详解
Jan 27 Javascript
详解js 创建对象的几种方法
Mar 08 Javascript
使用 Vue 实现一个虚拟列表的方法
Aug 20 Javascript
JavaScript设计模式之门面模式原理与实现方法分析
Mar 09 Javascript
js实现盒子滚动动画效果
Aug 09 Javascript
TypeScript实用技巧 Nominal Typing名义类型详解
Sep 23 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
2020年4月放送!《Princess Connect Re:Dive》制作组 & 角色声优公开!
2020/03/06 日漫
我的论坛源代码(三)
2006/10/09 PHP
php+mysql事务rollback&amp;commit示例
2010/02/08 PHP
destoon整合ucenter后注册页面不跳转的解决方法
2014/06/21 PHP
php面向对象中static静态属性与方法的内存位置分析
2015/02/08 PHP
PHP连接access数据库
2015/03/27 PHP
php+croppic.js实现剪切上传图片功能
2018/08/14 PHP
PHP自定义错误处理的方法分析
2018/12/19 PHP
PHP实现一个按钮点击上传多个图片操作示例
2020/01/23 PHP
jQuery的一些注意
2006/12/06 Javascript
JavaScript 高级篇之闭包、模拟类,继承(五)
2012/04/07 Javascript
裁剪字符串trim()自定义改进版
2013/04/10 Javascript
5秒后跳转效果(setInterval/SetTimeOut)
2013/05/03 Javascript
在JavaScript中判断整型的N种方法示例介绍
2014/06/18 Javascript
Node.js开启Https的实践详解
2016/10/25 Javascript
BootStrap Table 获取同行不同列元素的方法
2016/12/19 Javascript
vue2.0实战之基础入门(1)
2017/03/27 Javascript
Vue2.0 组件传值通讯的示例代码
2017/08/01 Javascript
react配置antd按需加载的使用
2019/02/11 Javascript
nodejs提示:cross-device link not permitted, rename错误的解决方法
2019/06/10 NodeJs
微信小程序全局变量GLOBALDATA的定义和调用过程解析
2019/09/23 Javascript
jQuery 筛选器简单操作示例
2019/10/02 jQuery
vue表单数据交互提交演示教程
2019/11/13 Javascript
js 实现碰撞检测的示例
2020/10/28 Javascript
在类Unix系统上开始Python3编程入门
2015/08/20 Python
numpy中以文本的方式存储以及读取数据方法
2018/06/04 Python
PYQT5实现控制台显示功能的方法
2019/06/25 Python
PyQt5 QTableView设置某一列不可编辑的方法
2019/06/25 Python
python利用re,bs4,requests模块获取股票数据
2019/07/29 Python
python openCV实现摄像头获取人脸图片
2020/08/20 Python
Selenium关闭INFO:CONSOLE提示的解决
2020/12/07 Python
selenium携带cookies模拟登陆CSDN的实现
2021/01/19 Python
html5+css3进度条倒计时动画特效代码【推荐】
2016/03/08 HTML / CSS
夏威夷咖啡公司:Hawaii Coffee Company
2019/09/19 全球购物
Fanatics官网:运动服装、球衣、运动装备
2020/10/12 全球购物
2016年大学生实习单位评语
2015/12/01 职场文书