Javascript学习笔记之函数篇(六) : 作用域与命名空间


Posted in Javascript onNovember 23, 2014

在之前的介绍中,我们已经知道 Javascript 没有块级作用,只有函数级作用域。

function test() { // a scope

    for(var i = 0; i < 10; i++) { // not a scope

        // count

    }

    console.log(i); // 10

}

Javascript 中也没有显示的命名空间,这就意味着一切都定义在全局作用域中。每一次引用一个变量时,Javascript 会往上遍历整个全局作用域直到找到该变量。如果遍历完整个全局作用域仍然没有找到该变量,则抛出一个 ReferenceError 错误。

请输入图片描述

隐式全局变量

// script A

foo = '42';

// script B

var foo = '42'

上面的两个例子产生不一样的效果。第一个将在全局作用域中定义变量 foo,而第二个则在当前作用域定义变量 foo。
我们一定要注意,如果不使用关键字 var 将会带来意想不到的影响。

// global scope

var foo = 42;

function test() {

    // local scope

    foo = 21;

}

test();

foo; // 21

由于在函数 test 内没用 var 来定义变量 foo,所以将覆盖函数外部的全局变量 foo。尽管看上去不是什么大问题,但是如果有成千上万行代码时,这将是个难以追踪的 bug。

// global scope

var items = [/* some list */];

for(var i = 0; i < 10; i++) {

    subLoop();

}

function subLoop() {

    // scope of subLoop

    for(i = 0; i < 10; i++) { // missing var statement

        // do amazing stuff!

    }

}

上例中,外部的循环将会在执行第一次的时候就停止,这是因为 subloop 函数内部的变量 i 将会覆盖外部的全局变量 i。我们只需要在函数内部加上一个 var 就可以避免这个错误,所以我们在定义变量时一定不要忘记加上关键字 var。除非我们确实希望对外部的全局变量造成影响。

局部变量

Javascript 中局部变量只可以通过两个方式产生,一是通过关键字 var 来声明,一是作为函数的形参。

// global scope

var foo = 1;

var bar = 2;

var i = 2;

function test(i) {

    // local scope of the function test

    i = 5;

    var foo = 3;

    bar = 4;

}

test(10);

此时,函数 test 内部的变量 i 和 foo 是局部变量,而 bar 则会覆盖外部的全局变量 bar。

提升(Hoisting)

Javascript 将会提升变量声明,这就意味着 var 表达式和函数声明都将被提升到作用域的顶部。

bar();

var bar = function() {};

var someValue = 42;

test();

function test(data) {

    if (false) {

        goo = 1;

    } else {

        var goo = 2;

    }

    for(var i = 0; i < 100; i++) {

        var e = data[i];

    }

}

上面的代码在运行之前, var 表达式和函数 test 的声明都将提升至顶部,因此程序将正常运行并不会报错。

// var statements got moved here

var bar, someValue; // default to 'undefined'

// the function declaration got moved up too

function test(data) {

    var goo, i, e; // missing block scope moves these here

    if (false) {

        goo = 1;

    } else {

        goo = 2;

    }

    for(i = 0; i < 100; i++) {

        e = data[i];

    }

}

bar(); // fails with a TypeError since bar is still 'undefined'

someValue = 42; // assignments are not affected by hoisting

bar = function() {};

test();

由于 Javascript 没有块级作用域,这不仅将提升 var 表达式,同时也会使得 if 结构变得不够直观。
在上例中,尽管看上去 if 在对全局变量 goo 进行操作,实际上,由于变量 goo 被提升,所以修改的是局部变量。
如果没有对提升规则有所了解,你可能会认为下面的代码将会抛出 ReferenceError 错误。

// check whether SomeImportantThing has been initialized

if (!SomeImportantThing) {

    var SomeImportantThing = {};

}

当然上面的代码是没有错误的,因为在代码在运行前,var 表达式已经被提升到顶部。

var SomeImportantThing;

// other code might initialize SomeImportantThing here, or not

// make sure it's there

if (!SomeImportantThing) {

    SomeImportantThing = {};

}

这里要推荐下 @nightire 凡哥的博文 《理解 JavaScript(二)》,里面对提升的讲解非常透彻。
名称解析顺序

当尝试在一个函数作用域内访问一个 foo 变量时,Javascript 将会按照下面的顺序查找:

当前作用域内是否有 var foo 的定义。
函数形参中是否有 foo 变量。
函数自身的名称是否为 foo。
跳到外层定义域,再从第一部开始查找起。
命名空间

一个最常见的问题就是命名冲突,这是因为 Javascript 只有一个全局作用域所带来的。但这个问题可以通过匿名的外部函数解决。

(function() {

    // a self contained "namespace"

    window.foo = function() {

        // an exposed closure

    };

})(); // execute the function immediately

上例中的匿名函数被认为是表达式,所以它们会被执行。

( // evaluate the function inside the parentheses

function() {}

) // and return the function object

() // call the result of the evaluation

当然我们也可以用其他方式来调用函数表达式,不同的结构,但是同样的效果。

// A few other styles for directly invoking the 

!function(){}()

+function(){}()

(function(){}());

// and so on...

总结

建议大家使用匿名的外部函数来将代码封装到空间内,这样不仅可以解决命名空间的冲突,同时也有利于程序的模块化。
此外,使用全局变量不是一个好习惯,这将带来高成本的维护代价而且容易产生错误。

命名空间同类型、函数、变量、模板等都属于实体(entity)。
实体的主要的共性是,可以具有名称。(此外,标签也可以具有名称,但它不是实体。)
而命名空间作用域是作用域中的一类统称,和块作用域、类作用域、函数原型作用域、函数作用域(仅对标签有效)并列。命名空间内声明的名称在命名空间作用域中。全局名称被认为在隐含的全局命名空间作用域中。

命名空间作用确实就是作用域,但是,他又不同于简单的作用域,你可以分多次在多处声明同一个命名空间,但是里面的内容不能重定义,他们最终都会合成一个命名空间,就像std,到处宏定义

Javascript 相关文章推荐
让iframe框架网页在任何浏览器下自动伸缩
Aug 18 Javascript
使用javascript:将其它类型值转换成布尔类型值的解决方法详解
May 07 Javascript
JavaScript学习心得之概述
Jan 20 Javascript
简述JavaScript对传统文档对象模型的支持
Jun 16 Javascript
使用ngView配合AngularJS应用实现动画效果的方法
Jun 19 Javascript
JavaScript获取中英文混合字符串长度的方法示例
Feb 04 Javascript
详解探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 构建记事本应用
Jun 16 Javascript
微信小程序商品到详情的实现
Jun 27 Javascript
简单快速的实现js计算器功能
Aug 17 Javascript
基于Vue实现关键词实时搜索高亮显示关键词
Jul 21 Javascript
vuex 解决报错this.$store.commit is not a function的方法
Dec 17 Javascript
AJAX检测用户名是否存在的方法
Mar 24 Javascript
Javascript学习笔记之函数篇(五) : 构造函数
Nov 23 #Javascript
Javascript学习笔记之函数篇(四):arguments 对象
Nov 23 #Javascript
Javascript学习笔记之 函数篇(三) : 闭包和引用
Nov 23 #Javascript
js实例属性和原型属性示例详解
Nov 23 #Javascript
JS常用函数使用指南
Nov 23 #Javascript
浅谈JSON和JSONP区别及jQuery的ajax jsonp的使用
Nov 23 #Javascript
理解jQuery stop()方法
Nov 21 #Javascript
You might like
PHP编程与应用
2006/10/09 PHP
PHP正则表达式之定界符和原子介绍
2012/10/05 PHP
php实现随机生成易于记忆的密码
2015/06/19 PHP
在laravel中实现ORM模型使用第二个数据库设置
2019/10/24 PHP
Javascript YUI 读码日记之 YAHOO.util.Dom - Part.3
2008/03/22 Javascript
用JavaScript玩转游戏物理(一)运动学模拟与粒子系统
2010/06/19 Javascript
js中获取事件对象的方法小结
2011/03/13 Javascript
jQuery中的height innerHeight outerHeight区别示例介绍
2014/06/15 Javascript
jquery合并表格中相同文本的相邻单元格
2015/07/17 Javascript
jQuery Html控件基本操作(日常收集整理)
2016/03/11 Javascript
vue,angular,avalon这三种MVVM框架优缺点
2016/04/27 Javascript
javaScript如何跳出多重循环break、continue
2016/09/01 Javascript
jQuery实现的多张图无缝滚动效果【测试可用】
2016/09/12 Javascript
理解JavaScript原型链
2016/10/25 Javascript
vue.js实现仿原生ios时间选择组件实例代码
2016/12/21 Javascript
vue+webpack 打包文件 404 页面空白的解决方法
2018/02/28 Javascript
JS中利用FileReader实现上传图片前本地预览功能
2018/03/02 Javascript
node中的cookie的具体使用
2018/09/13 Javascript
layui实现根据table数据判断按钮显示情况的方法
2019/09/26 Javascript
JQuery常用选择器功能与用法实例分析
2019/12/23 jQuery
探索浏览器页面关闭window.close()的使用详解
2020/08/21 Javascript
vue将文件/图片批量打包下载zip的教程
2020/10/21 Javascript
elementui实现预览图片组件二次封装
2020/12/29 Javascript
Python中获取对象信息的方法
2015/04/27 Python
连接Python程序与MySQL的教程
2015/04/29 Python
python获取元素在数组中索引号的方法
2015/07/15 Python
使用Python批量修改文件名的代码实例
2019/01/24 Python
Python 3.6 -win64环境安装PIL模块的教程
2019/06/20 Python
python+selenium+chrome批量文件下载并自动创建文件夹实例
2020/04/27 Python
python实现图像随机裁剪的示例代码
2020/12/10 Python
HTML5中的进度条progress元素简介及兼容性处理
2016/06/02 HTML / CSS
EJB与JAVA BEAN的区别
2016/08/29 面试题
物流管理专业职业生涯规划书
2014/01/06 职场文书
大三学生做职业规划:给未来找个方向
2014/02/24 职场文书
党的群众路线教育学习材料
2014/05/12 职场文书
2019如何书写演讲稿?
2019/07/01 职场文书