JS命名空间模式解析
简介
在SF上看到这样一个提问:
如题,因为不得已的原因,需要写若干个全局函数。但又不想这样:
window.a = function(){}
window.b = function(){}
window.c = function(){}
题主问有什么好的写法?
解答:
如果你用 jQuery,你可以这样写
$.extend(window, {
a: function() {},
b: function() {},
c: function() {}
});
如果你不用 jQuery,可以直接实现类似的 extend:
(() => {
var defining = {
a: function() { },
b: function() { },
c: function() { }
}; Object.keys(defining).forEach(key => {
window[key] = defining[key];
});
})();
在JavaScript中,命名空间可以帮助我们防止与全局命名空间下的其他对象或变量产生冲突。命名空间也有助于组织代码,有更强的可维护性和可读性。本文旨在探讨JavaScript里的几种常见命名空间模式,为我们提供一个思路。
一般命名空间的实现都以window为根。当向window申请a.b.c的命名空间时,首先在window中查看是否存在a这个成员,如果没有则在 window下新建一个名为a的空关联数组,如果已经存在a,则继续在window.a中查看b是否存在,以此类推。下面分别是Atlas和YUI中的实现方法。
Atlas命名空间的实现方法:
Function.registerNamespace =function(namespacePath){
//以window为根
var rootObject =window;
//对命名空间路径拆分成数组
var namespaceParts =namespacePath.split('.');
for (var i =0;i <namespaceParts.length;i++) {
var currentPart =namespaceParts[i];
//如果当前命名空间下不存在,则新建一个Object对象,等效于一个关联数组。
if(!rootObject[currentPart]) {
rootObject[currentPart]=new Object();
}
rootObject =rootObject[currentPart];
}
}
YUI命名空间的实现方法:
var YAHOO = window.YAHOO || {};
YAHOO.namespace = function(ns) {
if (!ns || !ns.length) {
return null;
}
var levels = ns.split(".");
var nsobj = YAHOO; //如果申请的命名空间是在YAHOO下的,则必须忽略它,否则就成了YAHOO.YAHOO了
for (var i=(levels[0] == "YAHOO") ? 1 : 0; i<levels.length; ++i) { //如果当前命名空间下不存在,则新建一个关联数组。
nsobj[levels[i]] = nsobj[levels[i]] || {};
nsobj = nsobj[levels[i]];
} //返回所申请命名空间的一个引用;
return nsobj;
};
YUI把所有申请的命名空间都放在了window.YAHOO下面,这样有什么好处呢?假如Yahoo和其他公司有合作关系, 需要嵌入对方的脚本时,这样能保证它自己编写的代码都在YAHOO这个空间下面,而其他公司不大可能在这个空间下面编码,就基本不会出现命名冲突的情况。
我们一般的实现方式:
namespace = function(){
var argus = arguments;
for(var i = 0; i < argus.length; i++){
var objs = argus[i].split(".");
var obj = window;
for(var j = 0; j < objs.length; j++){
obj[objs[j]] = obj[objs[j]] || {};
obj = obj[objs[j]];
}
}
return obj;
}; namespace("tools.base");
当然我们也常常利用js对象字面量的方式来实现js的命名空间:
var school = {
addClass:function(classnum){
console.log(classnum);
},
addStudent:function(stuId){
console.log(stuId);
}
}
school.addClass("2");
在全局作用域中声明的任何变量和函数都是window对象的属性,当名称有冲突时,就会产生一些不可控的问题。全局变量会带来以下问题:
- 命名冲突
- 代码的脆弱性
- 难以测试
在编程开发中合理的使用命名空间,可以避免相同的变量或对象名称产生的冲突。而且,命名空间也有助于组织代码,有更强的可维护性和可读性。JavaScript中虽然没有提供原生的命名空间支持,但我们可以使用其他的方法(对象和闭包)实现类似的效果。下面就是一些常见的命名空间模式:
1.单一全局变量
JavaScript中一个流行的命名空间模式是选择一个全局变量作为主要的引用对象。因为每个可能的全局变量都成为唯一全局变量的属性,也就不用再创建多个全局变量,那么也就避免了和其他声明的冲突。
单一全局变量模式已经在不少的JavaScript类库中使用,如:
- YUI定义了唯一的YUI全局对象
- jQuery定义了$和jQuery,$由其他类库使用时使用jQuery
- Dojo定义了一个Dojo全局变量
- Closure类库定义了一个goog全局对象
- Underscore类库定义了一个_ 全局对象
示例如下:
var myApplication = (function() {
var count = 1;
function funcur() {
console.log(count);
};
return {
funcur:funcur
}
})();
myApplication.funcur();
虽然单一全局变量模式适合某些情况,但其最大的挑战是确保单一全局变量在页面中是唯一使用的,不会发生命名冲突
2.命名空间前缀
命名空间前缀模式其思路非常清晰,就是选择一个独特的命名空间,然后在其后面声明声明变量、方法和对象。示例如下:
var myApplication_propertyA = {};
var myApplication_propertyB = {}; function myApplication_myMethod() {
// ***
}
从某种程度上来说,它确实减少了命名冲突的发生概率,但其并没有减少全局变量的数目。当应用程序规模扩大时,就会产生很多的全局变量。在全局命名空间内,这种模式对其他人都没有使用的这个前缀有很强的依赖,而且有些时候也不好判断是否有人已经使用某个特殊前缀,在使用这种模式时一定要特别注意。
3.对象字面量表示法
对象字面量模式可以认为是包含一组键值对的对象,每一对键和值由冒号分隔,键也可以是代码新的命名空间。示例如下:
var myApplication = {
// 可以很容易的为对象字面量定义功能
getInfo:function() {
// ***
}, // 可以进一步支撑对象命名空间
models:{},
views:{
pages:{}
},
collections:{}
};
与为对象添加属性一样,我们也可以直接将属性添加到命名空间。对象字面量方法不会污染全局命名空间,并在逻辑上协助组织代码和参数。并且,这种方式可读性和可维护性非常强,当然我们在使用时应当进行同名变量的存在性测试,以此来避免冲突。下面是一些常用的检测方法:
var myApplication = myApplication || {}; if(!myApplication) {
myApplication = {};
} window.myApplication || (window.myApplication || {}); // 针对jQuery
var myApplication = $.fn.myApplication = function() {}; var myApplication = myApplication === undefined ? {} :myApplication;
对象字面量为我们提供了优雅的键/值语法,我们可以非常便捷的组织代码,封装不同的逻辑或功能,而且可读性、可维护性、可扩展性极强。
4.嵌套命名空间
嵌套命名空间模式可以说是对象字面量模式的升级版,它也是一种有效的避免冲突模式,因为即使一个命名空间存在,它也不太可能拥有同样的嵌套子对象。示例如下:
var myApplication = myApplication || {}; // 定义嵌套子对象
myApplication.routers = myApplication.routers || {};
myApplication.routers.test = myApplication.routers.test || {};
当然,我们也可以选择声明新的嵌套命名空间或属性作为索引属性,如:
myApplication['routers'] = myApplication['routers'] || {};
使用嵌套命名空间模式,可以使代码易读且有组织性,而且相对安全,不易产生冲突。其弱点是,如果我们的命名空间嵌套过多,会增加浏览器的查询工作量,我们可以把要多次访问的子对象进行局部缓存,以此来减少查询时间。
5.立即调用的函数表达式
立即调用函数(IIFE)实际上就是匿名函数,被定义后立即被调用。在JavaScript中,由于变量和函数都是在这样一个只能在内部进行访问的上下文中被显式地定义,函数调用提供了一种实现私有变量和方法的便捷方式。IIFE是用于封装应用程序逻辑的常用方法,以保护它免受全局名称空间的影响,其在命名空间方面也可以发挥其特殊的作用。示例如下:
;(function (namespace, undefined) { // 私有属性
var foo = "foo";
bar = "bar"; // 公有方法和属性
namespace.foobar = "foobar";
namespace.sayHello = function () {
say("Hello World!");
}; // 私有方法
function say(str) {
console.log("You said:" + str);
};
})(window.namespace = window.namespace || {});
console.log(namespace.foobar); //foobar
可扩展性是任何可伸缩命名空间模式的关键,使用IIFE可以轻松实现这一目的,我们可以再次使用IIFE给命名空间添加更多的功能。
6.命名空间注入
命名空间注入是IIFE的另一个变体,从函数包装器内部为一个特定的命名空间“注入”方法和属性,使用this作为命名空间代理。这种模式的优点是可以将功能行为应用到多个对象或命名空间。示例如下:
var myApplication = myApplication || {};
myApplication.utils = {}; ;(function () {
var value = 5; this.getValue = function () {
return value;
} // 定义新的子命名空间
this.tools = {};
}).apply(myApplication.utils); (function () {
this.diagnose = function () {
return "diagnose";
}
}).apply(myApplication.utils.tools); // 同样的方式在普通的IIFE上扩展功能,仅仅将上下文作为参数传递并修改,而不是仅仅使用this
还有一种使用API来实现上下文和参数自然分离的方法,该模式感觉更像是一个模块的创建者,但作为模块,它还提供了一个封装解决方案。示例如下:
var ns = ns || {},
ns1 = ns1 || {}; // 模块、命名空间创建者
var creator = function (val) {
var val = val || 0; this.next = function () {
return val ++ ;
}; this.reset = function () {
val = 0;
}
} creator.call(ns);
// ns.next, ns.reset 此时已经存在 creator.call(ns1, 5000);
// ns1包含相同的方法,但值被重写为5000了 console.log(ns.next()); //
console.log(ns1.next());//
命名空间注入是用于为多个模块或命名空间指定一个类似的功能基本集,但最好是在声明私有变量或者方法时再使用它,其他时候使用嵌套命名空间已经足以满足需要了。
7.自动嵌套的命名空间
嵌套命名空间模式可以为代码单元提供有组织的结构层级,但每次创建一个层级时,我们也得确保其有相应的父层级。当层级数量很大时,会给我们带来很大的麻烦,我们不能快速便捷的创建想创建的层级。那么如何解决这个问题呢?Stoyan Stefanov提出,创建一个方法,其接收字符串参数作为一个嵌套,解析它,并自动用所需的对象填充基本名称空间。下面是这种模式的一种实现:
function extend(ns, nsStr) {
var parts = nsStr.split("."),
parent = ns,
pl; pl = parts.length; for (var i = 0; i < pl; i++) {
// 属性如果不存在,则创建它
if (typeof parent[parts[i]] === "undefined") {
parent[prats[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
} // 用法
var myApplication = myApplication || {};
var mod = extend(myApplication, "module.module2");
以前我们必须为其命名空间将各种嵌套显式声明为对象,现在用上述更简洁、优雅的方式就实现了。
参考地址:JavaScript之命名空间模式 浅析
JS命名空间模式解析的更多相关文章
- Django url配置 正则表达式详解 分组命名匹配 命名URL 别名 和URL反向解析 命名空间模式
Django基础二之URL路由系统 本节目录 一 URL配置 二 正则表达式详解 三 分组命名匹配 四 命名URL(别名)和URL反向解析 五 命名空间模式 一 URL配置 Django 1.11版本 ...
- python 之 Django框架(路由系统、include、命名URL和URL反向解析、命名空间模式)
12.36 Django的路由系统 基本格式: from django.conf.urls import url urlpatterns = [ url(正则表达式, views视图函数,参数,别名) ...
- Js模块模式
模块模式 索引 引子 什么是模块模式 命名空间模式 声明依赖 私有和特权成员 即时函数 揭示模块模式 结语 引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中 ...
- django基础2: 路由配置系统,URLconf的正则字符串参数,命名空间模式,View(视图),Request对象,Response对象,JsonResponse对象,Template模板系统
Django基础二 request request这个参数1. 封装了所有跟请求相关的数据,是一个对象 2. 目前我们学过1. request.method GET,POST ...2. reques ...
- JavaScript之命名空间模式 浅析
来源于:http://www.cnblogs.com/syfwhu/p/4885628.html 前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数 ...
- zepto.js 源码解析
http://www.runoob.com/w3cnote/zepto-js-source-analysis.html Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jqu ...
- Javascript 命名空间模式
命名空间是通过为项目或库创建一个全局对象,然后将所有功能添加到该全局变量中.通过减少程序中全局变量的数量,实现单全局变量,从而在具有大量函数.对象和其他变量的情况下不会造成全局污染,同时也避免了命名冲 ...
- 精读JavaScript模式(七),命名空间模式,私有成员与静态成员
一.前言 惰性十足,这篇2月19号就开始写了,拖到了现在,就是不愿意花时间把看过的东西整理一下,其它的任何事都比写博客要有吸引力,我要反省自己. 从这篇开始,是关于JS对象创建模式的探讨,JS语言简单 ...
- JavaScript之命名空间模式
前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象.在JavaScript中,命名空间可以帮 ...
随机推荐
- python写的翻译代码
# -*- coding: utf-8 -*- from Tkinter import *import difflibimport urllib2import urllib # python2.7才需 ...
- Git使用详细教程(7):.gitignore使用详解
Git提供了一个.gitignore文件,帮助我们忽略掉一些不想或者不能提交到版本控制器中的文件.这个文件的使用时必须要掌握的. *.a # 忽略所有目录下的.a结尾的文件 !lib.a # 但lib ...
- Mybatis框架六:关联查询
这里搞一个测试场景: 用户和订单,一位用户可以有多个订单,而每个订单只属于一位用户 以用户为中心,相对于订单:一对多 以订单为中心,当对于用户:一对一 两张表结构: 订单表: 用户表: 对应的POJO ...
- 免费开源的diff软件“meld”-替代beyond compare的神器
命令行直接对比文件 meld dir1 dir2 & 1,安装 mld, 可以选择windows, linux, macos系统,都有提供安装. ubuntu 中安装: sudo apt-ge ...
- Testing - 自动化测试的几个基础概念
自动化测试框架与模型 一个自动化测试框架就是一个集成体系,在这一体系中包含测试功能的函数库.测试数据源.测试对象识别标准,以及种可重用的模块. 自动化测试框架在发展的过程中经历了几个阶段,模块驱动测试 ...
- 1.Git起步-Git的三种状态以及三种工作区域、CVCS与DVCS的区别、Git基本工作流程
1.Git基础 版本控制系统是一种用于记录一个或多个文件内容变化,以便将来查阅恢复特定版本修订情况的系统. Git是一种分布式版本控制系统(Distributed Version Control Sy ...
- [转]MySQL 清空慢查询文件
概述 本章主要写当慢查询文件很大的时候怎样在线生成一个新的慢查询文件. 测试环境:mysql 5.6.21 步骤 配置慢查询 默认的my.cnf文件在/etc/目录下 vim /etc/my.cnf ...
- elasticsearch 操作
文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html 客户端:https://www.elasti ...
- 【web开发】docker中的数据库
注:自从开始使用docker,部署方面的事情就简单多了.使用docker构建的数据库容器不用直接安装,开启后就可以使用,也比以前方便很多.下面将一些要点记录下来. 下面的例子使用以下环境: - 系统( ...
- Centos-7修改yum源为国内的yum源
以centos7为例 ,以 修改为阿里的yum源 1. 备份本地yum源 [root@localhost yum.repos.d]# cp CentOS-Base.repo CentOS-Base.r ...