刚学习 JS 的时候,总是听过一个名词:命名空间。当时对这个东西并不能够很好的理解,命名为什么还会有空间呢?加上空间能有什么用处?
其实在很多编程语言中,都有命名空间这个概念,它们自身就已经具备了很好的文件和包管理机制,通过 import
include
require
等等命令,能够引入其他的模块、包或者文件,从而避免文件之间的变量命名重复问题。
然而在 JS 中,脚本文件是通过 script
标签引入的,同时由于 JS 的变量和函数声明的特殊性,一不小心就会造成环境污染,将一个变量或者属性暴露在全局,或者被别的文件中的同名变量所覆盖,于是就有了命名空间这样一个概念。
在 js 中,声明变量时加上关键字和不加关键字是不一样的,
var arg = 1
这样的声明,arg 的作用域只在当前页面或者当前的方法中;但是如果声明时不加var
而是直接声明arg = 1
,js 就会认为这是一个全局变量,即使它是在一个函数体中声明的。
/* a.js */
var people = 'xiaoming'
/* b.js */
var people = {
name: 'xiaohong',
age: 18
}
/* index.html */
<script src="./a.js"></script>
<script src="./b.js"></script>
console.log(people === 'xiaoming') // false
在上面的代码块中,后引入页面的
b.js
文件的people
变量就覆盖了a.js
的people
变量
上面这种情况,通过命名空间我们就可以很好地解决:
// 如果在 window 上没有 ns 属性,就将 ns 属性挂载到 window 上,作为根空间存在
if (!window.ns) window.ns = {}
// 当我们需要声明一个新的变量或者方法时,就可以添加到 ns 中,但是如果在 ns 上随意添加,依然会出现覆盖的情况,所以可以以文件为区分单位来添加
/* a.js */
var a = {}
a.people = 'xiaoming'
a.getPeopleName = function () {
return a.people
}
ns.a = a
/* b.js */
var b = {}
b.people = {
name: 'xiaohong',
age: 18
}
ns.b = b
// 在页面上引入上面两个文件后,我们可以通过下面的方式来分开使用两个 people
console.log(ns.a.people)
console.log(ns.b.people)
// 通过上面的方法,也可以实现在已经引入的脚本中调用其他脚本的变量和方法
/* c.js */
// 如果 ns.a 存在,则调用它的 getPeopleName 方法
ns.a && ns.a.getPeopleName()
上面的代码块已经简单的说明了命名空间的书写方式,当然你也可以通过其他方式来确定命名空间的各级空间命名方式,比如根节点就可以模仿其他语言的方式,通过倒写的域名来表示 com.google.www
既然命名空间的主要作用是用来防止变量被覆盖、污染全局环境,那么有没有其他方式来实现这一的作用呢?
最简单的,通过立即执行函数就可以比较好的解决这个问题(前提是变量要用 var const let) 声明:
const moduleA = function () {
// 私有变量、方法写在函数体中
const a = 1
function func1 () {...}
// 需要暴露出来的方法通过返回一个对象写在 return 中
return {
b: 2,
func2: () => {...}
}
} ()
moduleA.a // 报错
moduleA.b // 2
或者通过现在普遍使用的 require.js 等第三方库,ES6 支持的 import 语法来实现模块的引入,也可以防止模块之间的变量覆盖等情况出现。