JavaScript:作用域:函数作用域/全局变量污染/命名空间

更多
2020年07月06日 00点58分 作者:叶飞 修改

在C#中我们都基本上不讲作用域,因为一切都是自然而然的(用语言描述反而有些困难)。但JavaScript的作用域,让人非常头大!


局部变量

如果一个变量在函数体内部(用var)声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。(另见:let

不同函数内部的同名变量互相独立,互不影响。

这样被声明的变量被称之为:局部变量。

function scope() {
    var sname = '李志博';
    console.log('in function:' + sname);
}
scope();
console.log('out function:' + sname);


全局变量“污染”

不在任何函数内定义的变量具有全局作用域,被称之为全局变量。

演示:

如果script只是在HTML页面中使用,全局变量还可以接受(而且比较方便);

但随着JavaScript规模扩大,一个项目可能引用多个(第三方/其他人写的)类库(js文件),各个文件间的名称冲突就越来越难以避免,给开发/维护带来极大的问题!这就被称之为“全局变量污染”。

词法作用域

观察以下代码:

        var sname = "飞哥";
        function smart() {      
            alert(`${sname}最帅`);
        }

        function reallySmart() {
            var sname = '子祥';   
            smart();
        }
        reallySmart();

reallySmart()调用smart(),smart()中需要sname但又没有声明sname,怎么办?

  • 使用reallySmart()中定义的'子祥',还是
  • 全局变量"飞哥"

由var声明的变量的作用范围,其作用域由是由其源代码的书写位置,(而不是在哪里执行)决定的。


JavaScript的函数中不仅可以使用全局变量,还可以使用该函数外部的函数所声明的变量:
        function outFunc(sname) {
            var age = 100;
            function innerFunc() {
                alert(age);   //age定义在innerFunc()之外
            }
            innerFunc();
        }

这样就形成了一个作用域链:JavaScript会沿着这个链条由内向外查找,直到undefined。

@想一想@:如何剪切/粘贴第一个函数,才能显示:子祥最帅?


名称空间(namespace)

其他成熟的工程化语言内置了名称空间(namespace)来解决这个问题,比如:

//虽然都是“源栈”,但他们显然是不一样的:
China.Chongqing.Luckystack
China.Bejin.Luckystack
US.NewYork.Luckstack

JavaScript只能模拟:

  1. 先定义一个唯一的全局变量(对象)
  2. 其他变量都写出是上述全局变量的成员(属性和方法)
  3. 还可以多层嵌套,最后形成名称空间一样的“样式”
var China = {};
China.Chongqing = {};
China.Chongqing.LuckyStack = {};
China.Chongqing.LuckyStack.wpz = function () {

JQuery等类库就是这样做的。(演示)


strict模式(ES5)

把我们之前的代码稍作改动:

function scope() {
    /*var*/ sname = '李志博';   //注释掉var
@猜一猜@:会有什么结果?


如果在JavaScript的函数中声明变量,不使用var,该变量就具有全局作用域!——特性超级坑爹的一个“特性(bug)”,尤其是在代码review的时候,你根本不知道这是在:

  • 使用一个已声明的变量,还是
  • 要声明一个全局变量

所以从ES5开始,JavaScript就引入了所谓的“严格”模式,在代码顶部添加一行:

'use strict';  -- 如果浏览器不支持?

使用严格模式,就能强制JavaScript声明变量时必须使用:var;否则会报错。(以及其他约束)

演示:调试窗口报错


官方推荐总是使用严格模式。但是,如果

  • a.js 文件上声明了'use strict'
  • b.js 文件没有声明'use strict'且没有按照严格模式书写代码

  • 在html文件中先引用了a.js,然后再引用了 b.js

@想一想@:会出现什么情况?

所以,更多时候,我们不得不把'use strict'声明在函数顶部。


体会:JavaScript在处理大型项目,进行工程化开发方面先天不足!由此诞生了很多“奇巧淫技”,以及不断进化的ES标准。

但你以为这样就结束了?too young too simple啊!作为一个“先天严重不足,后天各种补丁”的语言,这一切才刚刚开始……



作业

  1. 使用“模拟名称空间”技术,构建一个函数函数yz.fei.get(number);

  2. yz.fei.get(number)除number以外,还可以接受任意多个回调函数做参数
    1. 这些回调函数能对number进行运算,并返回bool值的,比如has9()/has8()/has6()
    2. get()函数依次运行它的回调函数,只要回调函数运行结果为真,就累加计数
    3. 最后返回累加值
    让yz.fei.get(number)调用has9()/has8()/has6(),实现之前“统计含9/8/6数字个数”的作业



作用域 全局变量污染 词法 IIFE
赞: 0 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0

前端基础


HTML和CSS

HTML最常用的标签和属性,含HTML5的语义标签和新属性,但不包含需要JavaScript操作的HTML5 API,以及CSS基础(简述CSS 3动画相关),以及bootstrap.js以外的Bootstrap的内容。

Javascript基础和JQuery

Javascript的基本语法,JQuery类库(含Ajax),以及Bootstrap的JavaScript组件部分

进阶和ES6

借助于ES6,讲解JavaScript中一些更复杂的语法特性,如作用域、闭包、面向对象、原型链、this变化、module等

全部
关键字



帮助

反馈