JavaScript 命名空间
一、命名空间
管理变量,防止污染全局,适用于模块化开发
命名空间的问题是些小散碎的知识点,是用以前的知识点解决新的问题。
命名空间问题,是企业开发的一个实际的困扰性问题,比如开发一个淘宝网首页都是很多个人来做,你做导航栏、我做输入框、他做下面商品展示,下面的页脚链接,等等一些列的再做整体渲染。
一个淘宝事业部大概两千多人,有很多人去做就涉及一个问题,大家做完的东西得拼到一起,因为是一个HTML页面,拼到一起就会发生很多的冲突,HTML的冲突、CSS的冲突都是次要的。
我们先讲JavaScript的冲突,比如你写的一块有个功能,我写的一块也有功能,把咱们两的功能拼到一起,虽然写的文件是你一个js文档、我一个js文档,但最后总要落到一个HTML文件里去吧,都要一个HTML文档往里引入很多了js。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="Glee.js"></script> <script src="LiLi.js"></script> <script src="Fang.js"></script> </head> <body> </body> </html>
引入很多js不是操作一个东西,比如我写了一小块html,我的js就是操作我的HTML的,你写的一点就操作你的,再加上基础数据、基础变量、数组什么的,一堆js拼到一个页面里面去,大家的代码是这样去整合的。
大家把这些script标签放的一个页面里面,放到一个页面里有个问题,比如我命名一个变量a写在全局,他也写了一个变量名字也是a
var a = 100; var a = 200; alert(a);
还没等用到我变量就给覆盖了,两个人的变量就有冲突了。
大家在公司里都是经过专业入职培训的,培训完结果是大家代码格式、语法变量用的都非常标准(精准),比如说一个广告的功能大家都用变量基本都是ad,导航栏功能都用nav(Navigation),所以同样的一个功能用的变量名基本是一致的,保持高度一致好维护,因为规范大家都这么写。
保持一致就好玩了,比如你功能里面可能和我的功能里面东西比较像,我要实现功能定义这个变量,你要实现功能也定义这个变量,单词一共就那么几个,重复率就非常高。
大家一起协调配合时没法配合,变量都重复了怎么办?后来想一个笨方法,在个人变量前面加上自己名字的缩写。
比如定义一个变量name,加上自己的名字YanYanDiFangName,这样重复的概率就少了!有人名子起的飘逸点四个字,所以名字这个并不靠谱。
var YanYanDiFangName = "远远的地方";
再后来衍生了一个新的东西叫"命名空间"。
1. 命名空间
大家写代码的时候规定一个命名空间,把变量全放到命名空间里面,这个命名空间名字很高大上,其实内容都学过的就是对象。
比如定义一个对象叫org,给对象起一个名字org,对象起到什么作用呢?
1). 这个对象org在全局主域,org是总js里面的一个对象。对象里面可以有对象,属性值是随意的。
2). 我以后用的变量就在对象里面写,在里面定义一些列我想要的变量和方法,都定义到对象里面。
这样的话至少能保障,命名的不冲突,而且能非常的规整化,谁在部门1(department1)里面的谁下面有什么样的变量,部门2(department2)里面的谁下面有什么样的变量。
var org = { department1 : { YiEn : { name : "abc", age : 123 }, LiLi : { } }, department2 : { Glee : { }, FangJing : { } } } console.log(org.department1.YiEn.name); // 用的时候这样用
看着难受,用的时候也难受,能不能简单点!
又给简化了一下,我想用自己变量的时候,先定义一个变量YiEn,把org.department1.YiEn这个引用赋给YiEn,引用值赋值给一个短语是可以的,这样YiEn就相当于我的空间了。
var org = { department1 : { YiEn : { name : "abc", age : 123 }, LiLi : { } }, department2 : { Glee : { }, FangJing : { } } } var YiEn = org.department1.YiEn; console.log(YiEn.name); // 这样用就简单点了。
这个方法看一眼(知道)就行了,现在不这么用了,工具越来越复杂,越来越强大了根本就不需要这样了。
十年前(2007年)在解决这个问题的时候,确实用到了命名空间,也确实是这么用的,就是防止大家的命名不冲突,因为你对象里面的名字和我对象里面的名字即使一样也不冲突,"a.name"和"b.name"虽然都叫name无所谓不冲突,但在全局范围命名内两个name就会覆盖了。
这是老办法解决命名冲突的问题,这个办法一定记住叫"命名空间",就是对象模拟命名空间。现在真正解决的办法是webpack,包括很多同步化开放的软件。现在不讲这些东西,讲一个目前能接触到的,一个防止命名空间的一个问题,非常强大!
二、模块化开发,防止污染全局变量
这是一个项目包,项目包里有很多东西,component是一个组件,大家每一个人写一部分HTML,然后把HTML拼到一起,最后通过软件拼到主页面index.html里面。
主页面index.html里面"<!--online[component/news-ad.html]-->",看着是注释其实不是注释,这是后期的一个语法,把这些组件都拼到一起。
sc_topic_news_la.js这是js文件,一开始的作者是lxk他主要推进的,然后大家来配合,一个init就是一个人写的initAd、initTouPiao...
这个项目不是一个人写一个js文件,然后大家拼到一起了,而是在别人写完的js上去改东西,更直观的暴露一个问题,命名别冲突。怎么办的呢?用闭包来写。
大家没在全局上写,我通过"initAd = (function (){})"这样的方法,因为我写的是一个功能,功能等待被调用,在哪调用的呢?
在initAd()里面调用,于是initAd()功能实现的变量的调用、方法的实现,就写到一个独立的空间里面,就是闭包里面,怎么来写的?
这么来写的,变量initAd等于一个立即执行函数,这个立即执行函数执行完之后,会返回一个函数,等待被调用。
在立即执行函数里定义了一大堆变量、一大堆函数,但这些都是在立即执行函数里定义的,立即执行函数执行完之后自己就销毁了,然而想让这个功能保存到外部,等待被调用的话,保存了一个函数出来,把这一个函数保存出来之后,这一个函数会和刚才的域形成一个闭包。
然后在被保存出来的函数里面,写上方法的调用就可以了。
把这个简化一下,写一个小demo。比如要实现一个init()功能,这个功能是打印里面的属性name的值。
var name = "LiLi"; var init = (function(){ var name = "Glee"; function callName(){ console.log(name); } // 1. return function函数出来之后,init就相当于这个函数了。 // 2. 为什么这么来写 return function(){ callName(); // 3.因为callName()是为了开启init里面的功能,init里面所有的功能都是为了callName()函数的 } // 4. "return functon (){}函数"被保存到了外面init的身上,init()就代表了callName()这个功能 }()); init(); // 5. 所以执行init(),就相当于调用立即执行函数里面的东西 console.log(name); // 6. 在外面也定义一个name变量,和立即执行函数里面的name,它俩互相不影响
自执行函数形成一个闭包,闭包能变量私有化,不会污染全局变量。不污染全局变量就可以把特定的功能,写到这样一个闭包里面去,然后留出来一个接口方便去启动它。
怎么留接口?留出来一个"return function () {}",然后这个function里面去执行立即执行里面的一个函数(callName())作为中转。因为要实现一个功能,肯定要基于函数,函数调用函数,再调用函数,然后在使用变量。
// init对象里面的这段代码就是接口 return function(){ callName(); }
自执行函数里面定义好一系列复杂的功能,然后留出一个函数作为接口传给init。这个init被调用的时候,就会启动里面的callName()函数。
然而在这个过程中,功能被实现了函数也能被启用,但是有一个好处是,并不污染全局变量,里面定义的变量跟外面的一点关系也没有,但是功能也实现了,这是通过闭包来实现私有化的一个过程。
比如,我写的一个功能init,需要我定义一个变量,name等一系列东西。可能又有一个人又写一个功能initLiu,他也定义了一个变量name,也callName方法。
var name = "LiLi"; var init = (function(){ var name = "Glee"; function callName(){ console.log(name); } return function(){ callName(); } }()); var initLu = (function(){ var name = "Yan"; function callName(){ console.log(name); } return function(){ callName(); } }()); init(); // 打印Glee实现我功能 initDeng(); // 打印Yan实现他的功能
大家互不影响,但却各自实现了各自的功能,我的变量可以随便起名,别人的变量也可以随便起名。把在全局范围内要实现的功能,放到了一个局部里面,就是让它互相不污染,
以后要实现一个功能,这个功能是可以复用的(可以被重复使用的),以后开发中还会用到这个功能。就可以把这个功能提取到这样一个闭包里面去,再用的时候直接拷贝过来,根本不用担心变量被污染的问题。
污染全局变量的问题两种方法,老方法是命名空间(这个不用里),新方法就是用闭包,闭包是一种高端开发方法。
PS:
init是什么意思?
启到入口名字的作用,是初始化的意思。
在编程的时候,程序写的非常复杂,比如有一万行代码,想让别人读的懂是非常吃力的,因为别人不知道代码是从拿开始执行的。
为了能更好的找到入口函数,把入口函数最开始被启动的函数叫"init"。大家顺着init函数就知道,init函数里调用的谁谁,然后去找谁谁,然后依次能把函数的执行顺序读下来。init是约定俗成的一个用法,高端开发init。
然后再回过头看这个"var initAd = (function (){ })"就能理解了,立即执行函数里面用到了一大堆变量,又有一大堆函数,这些函数之间可能是互相调用、互相嵌套的。
然后不管里面是怎么样互相调用的,在return function(){}这启动,把这些addEventListener()、hideOnWeChat()、hideAd(),在return function(){}里启动起来。
这是闭包的第四点应用,模块化开发,防止污染全局变量。
三、思考问题
如何实现链式调用模式(模仿jquery)。
Obj.motion().drink().perm().motion().perm();
jquery是一个非常强大的库(复杂的文件包),这个文件包里面留出了很多方法接口,我们可以用它的方法实现自己的功能,它非常强大。
<div></div> $("div").css('background', 'red').width(100).height(100).html('加点内容').css('position' ,'absolute').css('left' , '100px').css('top' ,'100px');
jQuery有一个非常强大的链式操作,.css()是一个方法调用、.width()是一个方法调用里面传的是参数,jQuery可以一个方法接着一个方法的调用。
我们能不能这样模拟jQuery,调完一个方法再调一个方法!
这是一个对象上有三个方法,我们能不能连续调用!
var LiLi = { motion : function () { console.log('看足球,逛街'); }, drink : function () { console.log('喝茅台!'); }, perm : function () { console.log('做头发,护肤,做美甲!'); } } LiLi.motion(); LiLi.drink(); LiLi.perm();
对象上面有三个方法,现在只能这样deng.smoke(),然后是LiLi.drink(),然后是LiLi.perm()
现在想连续调用,试一下LiLi.smoke().drink()
var LiLi = { motion : function () { console.log('看足球,逛街'); // return undefined; }, drink : function () { console.log('喝茅台!'); }, perm : function () { console.log('做头发,护肤,做美甲!'); } } LiLi.motion().drink(); // 看足球,逛街 // Uncaught TypeError: Cannot read property 'drink' of undefined
第一个打印出来了,第二个报错"Uncaught TypeError: Cannot read property 'drink' of undefined",意思是undefined上面没有drink方法。
为什么会打印出这样的错误,因为motion执行完后会隐式的返回undefined,所以undefined不可能有drink属性的。
就想模拟模拟jQuery连着调用,jquery是怎么连续调用的?在函数里面"return LiLi"
var LiLi = { motion : function () { console.log('看足球,逛街'); return LiLi; }, drink : function () { console.log('喝茅台!'); return LiLi; }, perm : function () { console.log('做头发,护肤,做美甲!'); return LiLi; } } LiLi.motion().drink().perm().motion().perm();
"return LiLi"已经接近的正确答案了,有没有更好的,还能return其它东西吗?
在一个对象的函数里面,this指向的是第一人称我,this代表我"return this"更好。
var LiLi = { motion : function () { console.log('看足球,逛街'); return this; }, drink : function () { console.log('喝茅台!'); return this; }, perm : function () { console.log('做头发,护肤,做美甲!'); return this; } } LiLi.motion().drink().perm().motion().perm();
是可以"return LiLi"但要是把这个对象的构造权,放到构造函数里面呢?不知道构造出的对象叫什么名字,每次名字都会不一样,所以只能return this。
想实现一个方法的连续调用"return this"这是一个小技巧。再学一个小知识点,属性的表示方法