
已解决问题
谷歌lmk5212001用户在2016.04.29提交了关于“女童地*获救使用AngularJS编写较为优美的J**aScript代码指南”的提问,欢迎大家涌跃发表自己的观点。目前共有1个回答,最后更新于2025-02-27T03:14:31。希望大家能够帮助她。详细问题描述及疑问:期待您的答案,感谢你,我会记得你对我的好的 !
详细问题描述及疑问:期待您的答案,感谢你,我会记得你对我的好的 !
本文示例代码下载:mo**lePattern.zip-所有的4
介绍
AngularJS的库里面有很多
背景
我使用A
(大多数)文章在阐述模式时的问题
大多数时候人们都会尝
下面就是我所
J**aScript默认
设想你的h**TML中有下面这样一段脚本.
?
123<script>varisDoingWork=false;</scr范围?
你清楚这个变量的范围么?
是的,它是全局的。这个布尔值实际上被添加到了浏览器的全局窗口对象中.
把它设置到Action中
这里你可以看到它在Action中是怎样的.
下载本文的代码样例.
在你的
打开浏览器开发
在浏览
你会看到输出的值为false.
现在输入:isDoingWork=true
如此下载的值就为true了.你已经改变
你可以看到这个值已经通过输入doi
这可能会造成一些
因此你向下面这样在你的页面中引用了它:
?
1<scriptsrc="panacea.js"></script>如此简单,你就已经解决之前你遇到的所有问题.然而,因为它是一个庞大的库,而你只想要解决方法,却不回去深挖这个庞大(几千行代码)源文件里的每一行代码.而深埋在Panacea.js里面某个角落的确实下面这样的代码:
?
123varisDoingWork=false;setInterval(function(){isDoingWork=!isDoingWork;},3000);这代码真是酷,你知道吗?
每个3秒,它都会将这个布尔值设置成相对的值。啊!
自己动手看看
如果你想要自己动手验下这个东西,你可以做下面这几步:
下载本文的样例代码.
在你的浏览器中打开mo**lePattern2.htm.
打开浏览器开发工具--F12(Chrome,IE)或者Ctrl-Shift-I(Opera)--(这样你就可以看到控制台了)
在浏览器开发工具的控制台下,输入:isDoingWork然后回车
将第4步多重复几次,你将会发现isDoingWork的值会每个大约3秒钟变化一次.
那这是不是很棒呢?
我的第一个观点:模块模式是很有用的
我需要为此做出解释,为了要向你展示为什么J**aScript的模块模式是很有用的.我得想你展示J**aScript的模块模式,那样我就可以告诉你它是如何在AngularJS中被使用或实现的了.
模块模式:封装
如此,实际就是,模块模式基本上就是封装了.封装听起来很熟悉,如果你有点面向对象编程经历的话--而我也希望你能有点这个经验.封装是面向对象编程的三原则之一。封装的另外一个说法就是**隐藏。在经典的面向对象编程中——它不同于J**aScript所依赖的原型化OOP--**隐藏使建一个类模板的内在组成部分.
例如在C#中,Animal类的封装--隐藏**--特定的值被关联到Animal对象.那样,如果某人决定变更那些值,他或他必须明确的通过初始化一个Animal对象并设置这个对象的值来达到目的.在J**aScript中,我们则可以随意的在全局窗口对象中设置值.
?
1234567891011121**ublicclassAnimal{//constructorallowsusertosetthecommonNamepublicanimal(stringname){this.commonName=name;}//m**ingcommonNameprivatehides(encapsulates)itintheclassprivatestringcommonName;//explicitlyexposingthevalueforreadingonlypublicstringCommonNameget{returnthis.commonName}}在J**aScript中,模块已经被创建用来模拟这种封装行为了,如此我们就不会去将我们的变量组织到一个全局的命名空间中,并造成了隐藏很深的难以被发现和修复的问题.
现在你知道为什么了,让我们来看看如何会是这样的.
函数被立即调用的表达式(IIFE)
看上去就好像每次我们向前推进一步,我们都要走点旁门左道.因为要获得能让我们创建模块模式的J**aScript语法,我们就得去了解一种叫做函数被立即调用的表达式语法,也叫做IIFE(IIFE发音是"iffy").
最基础的IIFE看起来像这样:
?
12345(function(){//lines//of//code}());如果你从来没有看到过像这样的东西,那你就有点说不过去了.
立即被调用
首先,这个名称的第一部分叫做立即被调用的原因是,一般包含这个特殊函数的源文件被加载好了,那么包含在这个函数中的代码就会运行.
对IIFE语法更加仔细的观察
你可以看到这个语法的最中心是一个函数。看一下这个代码块,我已经将代码分段并将一些行标上了号,如此我们就可以探讨它了.
?
12345678(//1.function()//2.{//3.//一行一行//的//代码}()//4.);//5.首先,看看上面脚本的第2行。这一行通常看来就是一个匿名(也就是没有命名)的函数声明.而后,第3一直到第4则是这个函数的主题部分。最后,第4行最后以一对括弧结束,这对括弧会告诉J**aScript解释器去调用这个函数。最终,所有这些都会被包在一个不归属任何部分的括弧(第1和第5行)中,而这对括弧会告诉解释器要调用这个外部的匿名函数,它包含了我们所定义的函数.
IIFE可以带上参数
这段奇怪的语法会在带上参数之后,看起来会更加的奇怪.它看起来会像是下面这样
?
12345(function(thing1,thing2){//lines//of//code}("instring",382));现在,你可以看到这个函数可以带上两个会被**的函数引用的thing1,thing2参数.被传入值,在示例中是"instring"和382.
现在我们理解了IIFE语法,让我们来创建另外一个代码示例,我们将运行这段代码来看看封装是如何运作的.
?
1234(function(){varisDoingWork=false;console.log("isDoingWorkvalue:"+isDoingWork);}());自己动手看看
为了看看是怎么运行的,你可以做下面这几步:
下载本文的源代码.
在你的浏览器中打开mo**lePattern3.htm.
打开浏览器的开发工具--F12(Chrome,IE)或者Ctrl-Shift-I(Opera)--(这样你就可以看到控制台了)
你可以看到很像下面这样图片中所展示出来的东西
当方法被调用时--这会在代码被J**aScript解释器加载支护立即发生--而后函数会创建isDoingWork变量,并调用console.log()来在控制台输出这个变量的值.
现在,让我们使用开发工具中的控制台来试试我们之前所尝试过的步骤:
输入:isDoingWork然后回车
当你这样做了之后,你将会看到浏览器不再相信isDoingWork这个值被定义过。即使是你尝试从全局窗口对象中获取这个值,浏览器也不认为isDoingWork这个值在此对象中被定义了.你所看到的错误消息看起来会像接下来这张图片中所展示的这样.
函数是一个对象:它创建了范围
这是因为现在你已经把isDoingWork这个变量创建在了一个函数里面--也就是我们们的匿名IIFE中--而如此这个变量就只能通过这个函数才能访问到.有趣的是J**ascript中的所有函数都是第一类对象.那很简明的意味着函数是一个对象,它可能通过一个变量被访问到.或者说,另外一种描述的方式是你存储了指向函数的一个引用,并在稍后的某个时间获取其变量.
在我们第一个示例中,我们的问题是并没有保存一个指向我们匿名函数的引用,所以我们永远也不能再获取到isDoingWork这个值。这就是我们下一个示例要改进的地方.
函数是一个对象:使用this
因为每一个函数都是一个对象,所以每个函数都会有一个this变量,这个变量向开发者提供了指向当前对象的引用.为了提供在从外部大我们的函数及其范围的访问,我们可以返回这个this变量--而它将会提供一个指向当前对象的引用.
然后,除非我们将这个私有的isDoingWork变量添加到函数引用(this)上,我们也不能够引用这个变量。为此我们要对之前的示例做一下轻微的改动。它看起来会像下面这样:
?
12345thing=(function(){//1.this.isDoingWork=false;//2.console.log("isDoingWorkvalue:"+isDoingWork);returnthis;//3.}());你可以看到第一行我们加入了一个新的全局变量thing,它包含了从匿名函数返回的值。从示例代码的开头跳到第三行,你可以看到我们返回了this变量。那就意味着我们返回了一个指向匿名函数的引用.
在第二行我们也已经将isDoingWork加入了this引用中,那样我们就可以使用语法thing.isDoingWork来从外部引用到这个值了.
自己动手看看
为了看看的运行,你可以做下面这几步:
下载本文的示例代码.
在你的浏览器中打开mo**lePattern4.htm.
打开浏览器开发工具--F12(Chrome,IE)或者Ctrl-Shift-I(Opera)--(那样你就可以看到控制台了)
你将会看到isDoingWork的值会输出到控制台,就像最开始那个示例中你看到的那样.
不过,现在你得输入thing.isDoingWork才能或者这个值.
模块模式总结
在最后这个示例中,变量值被成功的封装了,而其他的J**aScript库则可以明确的引用thing对象来获取这个值.好像不大可能,而这帮助了我们保持全局命名空间的干净,并且在看起看来是更好的代码组织形式.这也使得我们代码的维护更容易.
最终,我们用上了AngularJS
因为使用模块模式是一个最佳实践,AngularJS的开发者就将一个模块系统构建到了库中.
Plunker代码
首先你可以通过到这个Plunker上(http://***.co/edit/js8rbKpIuAuePzLF2DcP?p=preview-在一个新的窗口或Tab页打开)获取整个AngularJS示例.
而我们在这里展示出代码,那样我们就可以更方便的谈论它了.
首先,让我们看看这个h**TML.
?
123456789101112131415161718192021222324252627<!DOCTYPEhtml><htmlng-app="mainApp"><head><metacharset="utf-8"/><title>AngularMo**leExample</title><scriptdata-require="angular.js@1.2.x"src="https://code.***.org/1.2.20/angular.js"data-semver="1.2.20"></script><scriptsrc="mainCtrl.js"></script><scriptsrc="secondCtrl.js"></script></head><body><divng-controller="MainCtrlasmc"><p>mcreferstoMainCtrlwhichhasbeenaddedtotheangularappmo**le</p><p>h**ello{{***.name}}!</p><ol><ling-repeat="ainmc.allThings">{{a}}</li></ol></div><divng-controller="SecondCtrlassc"><p>h**ello{{***.name}}</p><ol><ling-repeat="ainsc.allThings">{{a}}</li></ol></div></body></html>Angular指令:ng-app
Angular所定义和使用的东西叫做指令。这些指令基本上就是由Angular定义属性,而AngularJS编译器(Angular的J**aScript)会将它们转换成其他的东西.
我们应用了ng-app指令,为我们的Angular应用定义了一个名称,叫做mainApp.
mainApp就是我们稍后会看到的模块模式的起点.
被引入的脚本:每个都是一个模块
现在,请注意有三个脚本被引入到了这个h**TML中.
第一个是必须的AngularJS库.
而其他两个则是作为模块被实现的Angular控制器.
它们被作为模块实现以保持代码彼此,还有从这个应用上看,都是独立的.
AngularJS:创建score
在往下看,你将会看到两个以如下代码开头的div:
?
123<divng-controller="MainCtrlasmc"><divng-controller="SecondCtrlassc">这是在为div的每一个都设置上ng-controller.这些div中的每一个都有其各自的范围.第一个控制器的名字叫做MainCtrl,第二个叫做SecondCtrl.
AngularJS编译器会在你提供(引入)的代码中用这两个名称查找对应的函数.
如果AngularJS编译器没有找个这两个名称对应的函数,它就会抛出一个错误.
mainCtrl.js:第一个控制器
让我们来看看mainCtrl.js文件里面有些啥东西.
你可以在Plunker页面的左侧点击它在Plunker中将其打开.
当你打开了它,你将会看到一些看上去很熟悉的代码。好吧,你至少会看出来它们都是被包在一个IIFE中的.
?
12345678910111213(function(){varapp=angular.mo**le('mainApp',[]);app.controller('MainCtrl',function(){console.log("inMainCtrl");//vt=virtualthis-justshorthandvt=this;***.name='MainCtrl';vt.allThings=["first","second","third"];});})();那是因为我们需要这些代码在文件mainCtrl.js被加载时就运行.
现在,请注意在这个IIFE中的第一行代码.
?
1varapp=angular.mo**le('mainApp',[]);这行代码是Angular将一个模块添加到其命名空间的方式.在这里,我们添加了一个将用来展示我们应用程序的模块.这是应用程序的模块,而我们已经将其命名为itmainApp,它跟h**TML页面上ng-app所指定的值是一样的.
我们也创建了一个叫做app的(只在IIFE本地可见的)本地变量,以便我们将可以在这个函数**用来再次添加一个控制器.
奇怪的Angular语法
请你也要再仔细看看第一行。你会注意到我们是首次创建mainApp模块,而如果是首次,则我们必须提供以字符串数组的形式提供其可能需要的任何依赖(,表示出依赖库的名称).不过,在这里对于这个简单的示例而言,我们不需要任何的依赖。但Angular仍然需要我们传入一个空的数组,以便它知晓我们正在创建新的模块,而不是去试图加载一个已经被创建好了的模块.
提示:你将会看到我们会在secondCtrl.js里加载mainApp模块,而上面所提的数组将会有更多的作用.
我们一把mainApp创建好,就需要向其添加我们的控制器.这些就是Angular预期我们在h**TML(的div中)加入的控制器.
将控制器添加到App模块
添加控制器的代码看起来像下面这样:
?
12345678app.controller('MainCtrl',function(){console.log("inMainCtrl");//vt=virtualthis-justshorthandvt=this;***.name='MainCtrl';vt.allThings=["first","second","third"];});为了添加我们的控制器函数,我们向app.controller()函数提供了一个控制器名称和一个函数.在此处我们提供了一个匿名函数.
所以,我们的控制器主体代码就是下面这几行了:
?
12345console.log("inMainCtrl");//vt=virtualthis-justshorthandvt=this;***.name='MainCtrl';vt.allThings=["first","second","third"];这里,当我们的控制器运行时,会向控制台输出一行.然后,我们将this变量重命名为vt(方便起见,就叫他虚拟的this),而后我天为其添加了一个name属性和一个叫做allThings的字符串数组.
控制器和封装
那就是当控制器被Angular调用时会运行的代码.那个控制器会在文件被加载时运行起来,也就是一开始h**TML被加载的时候.这意味着控制器会被加载到app模块中,而这些属性会被添加到控制器对象(函数)中。因为我们想this变量添加了属性,我们就可以在稍后获取这些属性,但它们是被封装了起来的,因此它们不可以被每个人随意的更改.
现在,让我们跳到h**TML中控制器被引用和使用的地方.
第一个Div
这是我们的MainCtrl控制器被引用和使用的第一个Div。它看起来就像下面这样:
?
123456<divng-controller="MainCtrlasmc"><p>mcreferstoMainCtrlwhichhasbeenaddedtotheangularappmo**le</p><p>h**ello{{***.name}}!</p><ol><ling-repeat="ainmc.allThings">{{a}}</li></ol></div>这个div输出我们的web页面的如下部分,看起来就是接下来这张图片上所展示的那样.
输出被使用Angular指令来创建
不过,它使用了一种特殊的方式创建那个输出,它使用了两种Angular指令:
?
123{{***.name}}ng-repeat第一个指令被关联到了Div那一行上面MainCtrl的声明和引用.我们告诉Angular,说我们想以mc这个名称引用我们的MainCtrl函数(对象)。那就是Angular提供的一个很棒的缩写功能.
现在,因为我们将一个属性放到了MainCtrl的this对象上,我们现在就可以通过mc和属性的名称来引用那些东西了。我们将那些东西包含特殊的双大括号{{}}里面,如此Angular编译器就懂得那是可以运行的代码,你就会瞧见Angular将其转换成了h**TML:
?
12<p>h**ello{{***.name}}!</p>编程了下面这一:
?
1h**elloMainCtrl!之后,我们设置了一个漂亮的无需列表,并使用了ng-repeat指令来迭代输出数组中的每一行.
然后Angular跌倒了整个allThings数组,并将其装换成了下面的h**TML
?
1<ling-repeat="ainmc.allThings">{{a}}</li>变成了如下的输出
?
1231.first2.second3.third就那么简单。这就是模块化的所有东西,我们的值再也不会被任何人动手动脚了.
SecondCtrl:几乎就是同样的东西
这里有SecondCtrl的代码.代码机会就是一样的,除了我们获取我满原来的app模块处有点不一样——不是第一次创建它了.
?
12345678910111213(function(){varapp=angular.mo**le('mainApp');app.controller('SecondCtrl',function(){console.log("inSecondCtrl");//vt=virtualthis-justshorthandvt=this;***.name='SecondCtrl';vt.allThings=["bacon","lettuce","tomato"];});})();仔细看看下面这一行:
?
1varapp=angular.mo**le('mainApp');唯一的不同就是我们没有提供引用数组.
那是因为mainApp已经是存在了的,而我们只是想向其添加另外一个新模块(SecondCtrl).
总结:最佳实践
所有其它的脚本中的代码,以及h**TML基本上是一样的,而此处最重要的是所有的代码都被模块化了,**也被封装了起来,以便更好的组织我们的代码.这是Google软件开发者遵循的一个最佳实践,也是我们应该遵循的。请学习他,运用它,并与它同在吧(阿门).