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写了一个清除“logo1_.exe”的杀毒工具(可扫描目录)
Feb 09 Javascript
js格式化时间和js格式化时间戳示例
Feb 10 Javascript
简介JavaScript中getUTCMonth()方法的使用
Jun 10 Javascript
JQuery ztree带筛选、异步加载实例讲解
Feb 25 Javascript
jQuery实现区域打印功能代码详解
Jun 17 Javascript
js动态获取子复选项并设计全选及提交的实现方法
Jun 24 Javascript
AngularJS中的缓存使用
Jan 11 Javascript
谈谈JS中的!!
Dec 07 Javascript
Angular实现的进度条功能示例
Feb 18 Javascript
浅谈React的最大亮点之虚拟DOM
May 29 Javascript
Windows下Node爬虫神器Puppeteer安装记
Jan 09 Javascript
详解key在Vue列表渲染时究竟起到了什么作用
Apr 20 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/04/22 PHP
php获取网卡的MAC地址支持WIN/LINUX系统
2014/04/30 PHP
PHP弱类型语言中类型判断操作实例详解
2017/08/10 PHP
PHP操作Redis常用技巧总结
2018/04/24 PHP
jQuery+ajax实现鼠标单击修改内容的思路
2014/06/29 Javascript
使用phantomjs进行网页抓取的实现代码
2014/09/29 Javascript
推荐10 个很棒的 jQuery 特效代码
2015/10/04 Javascript
jQuery实现调整表格单列顺序完整实例
2016/06/20 Javascript
JS中微信小程序自定义底部弹出框
2016/12/22 Javascript
Vue.js中兄弟组件之间互相传值实例
2017/06/01 Javascript
详解vue-router和vue-cli以及组件之间的传值
2017/07/04 Javascript
详解如何在react中搭建d3力导向图
2018/01/12 Javascript
浅谈Redux中间件的实践
2018/07/27 Javascript
移动端底部导航固定配合vue-router实现组件切换功能
2019/06/13 Javascript
vue配置文件实现代理v2版本的方法
2019/06/21 Javascript
vue+elementUI 复杂表单的验证、数据提交方案问题
2019/06/24 Javascript
微信小程序 多行文本显示...+显示更多按钮和收起更多按钮功能
2019/09/26 Javascript
js实现随机div颜色位置 类似满天星效果
2019/10/24 Javascript
Python列表append和+的区别浅析
2015/02/02 Python
python监控网站运行异常并发送邮件的方法
2015/03/13 Python
Python二元算术运算常用方法解析
2020/09/15 Python
CSS3中box-shadow的用法介绍
2015/07/15 HTML / CSS
html5设计原理(推荐收藏)
2014/05/17 HTML / CSS
雪花秀美国官方网站:韩国著名草本护肤化妆品品牌
2016/10/19 全球购物
Etam德国:内衣精品店
2019/08/25 全球购物
十佳大学生村官事迹
2014/01/09 职场文书
建筑安全员岗位职责
2014/03/13 职场文书
文案策划求职信
2014/04/14 职场文书
《青蛙看海》教学反思
2014/04/23 职场文书
镇人大副主席民主生活会对照检查材料思想汇报
2014/10/01 职场文书
四川省传达学习贯彻党的群众路线教育实践活动总结大会精神新闻稿
2014/10/26 职场文书
贪污受贿检讨书范文
2014/11/19 职场文书
二手车转让协议书
2015/01/29 职场文书
秋收起义观后感
2015/06/11 职场文书
排球赛新闻稿
2015/07/17 职场文书
用python删除文件夹中的重复图片(图片去重)
2021/05/12 Python