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学习笔记(十)
Jan 17 Javascript
cnblogs中在闪存中屏蔽某人的实现代码
Nov 14 Javascript
JavaScript获取IP获取的是IPV6 如何校验
Jun 12 Javascript
简单实现轮播图效果的实例
Jul 15 Javascript
用js写的一个路由(简单实例)
Sep 24 Javascript
javascript简单链式调用案例分析
May 10 Javascript
eslint 的三大通用规则详解
May 16 Javascript
JS中的一些常用的函数式编程术语
Jun 15 Javascript
js实现html滑动图片拼图验证
Jun 24 Javascript
jQuery+ThinkPHP实现图片上传
Jul 23 jQuery
vue设置全局访问接口API地址操作
Aug 14 Javascript
浅析JavaScript中的变量提升
Jun 01 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实现获取及设置用户访问页面语言类
2014/09/24 PHP
PHP PDO fetch 模式各种参数的输出结果一览
2015/01/07 PHP
php 反斜杠处理函数addslashes()和stripslashes()实例详解
2016/12/25 PHP
PHP单元测试配置与使用方法详解
2019/12/27 PHP
javascript 带有滚动条的表格,标题固定,带排序功能.
2009/11/13 Javascript
IE中createElement需要注意的一个问题
2010/07/13 Javascript
Javascript学习笔记一 之 数据类型
2010/12/15 Javascript
浅析javascript闭包 实例分析
2010/12/25 Javascript
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
2012/06/14 Javascript
JS和jquery获取各种屏幕的宽度和高度的代码
2013/08/02 Javascript
深入探讨JavaScript、JQuery屏蔽网页鼠标右键菜单及禁止选择复制
2014/06/10 Javascript
原生js实现图片层叠轮播切换效果
2016/02/02 Javascript
如何使用jquery实现文字上下滚动效果
2016/10/12 Javascript
Angular2  NgModule 模块详解
2016/10/19 Javascript
JavaScript实现自定义媒体播放器方法介绍
2017/01/03 Javascript
详解React中的组件通信问题
2017/07/31 Javascript
vue router使用query和params传参的使用和区别
2017/11/13 Javascript
Vue表单及表单绑定方法
2018/09/04 Javascript
JQuery实现ul中添加LI和删除指定的Li元素功能完整示例
2019/10/16 jQuery
微信小程序中限制激励式视频广告位显示次数(实现思路)
2019/12/06 Javascript
解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题
2020/11/04 Javascript
用Python抢过年的火车票附源码
2015/12/07 Python
pandas实现DataFrame显示最大行列,不省略显示实例
2019/12/26 Python
python如何实现递归转非递归
2021/02/25 Python
银行见习期自我鉴定
2014/01/29 职场文书
协议书模板
2014/04/23 职场文书
优秀教师先进个人事迹材料
2014/08/31 职场文书
报效祖国演讲稿
2014/09/15 职场文书
岗位聘任报告
2015/03/02 职场文书
学生党支部工作总结2015
2015/05/26 职场文书
就业证明函
2015/06/17 职场文书
python tkinter实现定时关机
2021/04/21 Python
HTML+VUE分页实现炫酷物联网大屏功能
2021/05/27 Vue.js
pandas提升计算效率的一些方法汇总
2021/05/30 Python
python计算列表元素与乘积详情
2022/08/05 Python
Python使用pandas导入xlsx格式的excel文件内容操作代码
2022/12/24 Python