这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了使用 AngularJS 编写较为优美的 JavaScript 代码指南, 包括控制器和封装等进阶技巧上的编程建议, 倾力推荐! 需要的朋友可以参考下
本文示例代码下载:modulePattern.zip - 所有的 4 个 html 文件 以及 panacea.js - 1.6 KB
介绍
AngularJS 的库里面有很多东西,但本文中我只想专注于小的,针对特定主题的库,我相信通过它们能对 Angular 有一个较好的介绍. 理解这篇文章并不需要你有任何 Angular 相关的,甚至是 JavaScript 的经验。希望你能从本文中看到一些使用 Angular 的好处,并乐于动手尝试. 背景
我使用 Angular 有一段时间了,而在学习 Angular 的时候,我也喜欢构建一些样例,所以当我一开始深入进去的时候,对于模块或者 JavaScript 的设计模式,我也没有多想,那样对保持代码组织和条理性有帮助. 那就是所有的重点:保持代码的组织和条理性. 因此,现在我回过头来,创建了这个极其小巧的样例,以展示使用模块可以有多简单. 一路走来,我希望它能够成为一篇好的对 Angular 的介绍.
(大多数)文章在阐述模式时的问题
大多数时候人们都会尝试去在读者知道模式是啥概念之前就开始阐述一个模式,而这基本上误导了每一个人. 这里要努力使得本文尽量简单,让我们首先来看一看这个问题吧。哪个问题呢?就是有关默认会在全局内存空间被创建的所有东西的 Javascript 的问题.
下面就是我所说的意思. JavaScript 默认的全局问题
设想你的 HTML 中有下面这样一段脚本.
- <script>
- var isDoingWork = false;
- </script>
范围
你清楚这个变量的范围么? 是的,它是全局的。这个布尔值实际上被添加到了浏览器的全局窗口对象中.
把它设置到 Action 中
这里你可以看到它在 Action 中是怎样的.
这可能会造成一些名字冲突,也会导致一些严重的 bug. 这也许对你而言有点杞人忧天了,是不? 但是请设想你是决定要去实现某一个新的 JS 库,它每分每秒都可以被创建出来. 假设你发现了这个叫做 Panacea.js 的很棒的库,它将解决你所有的问题.
因此你向下面这样在你的页面中引用了它:
- <script src="panacea.js"></script>
如此简单,你就已经解决之前你遇到的所有问题. 然而,因为它是一个庞大的库,而你只想要解决方法,却不回去深挖这个庞大(几千行代码)源文件里的每一行代码. 而深埋在 Panacea.js 里面某个角落的确实下面这样的代码:
- var isDoingWork = false;
- setInterval(function() {
- isDoingWork = !isDoingWork;
- },
- 3000);
这代码真是酷,你知道吗
每个 3 秒,它都会将这个布尔值设置成相对的值。啊!
自己动手看看
如果你想要自己动手验证下这个东西,你可以做下面这几步:
那这是不是很棒呢? 我的第一个观点 : 模块模式是很有用的
我需要为此做出解释,为了要向你展示为什么 JavaScript 的模块模式是很有用的. 我得想你展示 JavaScript 的模块模式,那样我就可以告诉你它是如何在 AngularJS 中被使用或实现的了. 模块模式:封装
如此,实际就是,模块模式基本上就是封装了. 封装听起来很熟悉,如果你有点面向对象编程经历的话 -- 而我也希望你能有点这个经验. 封装是面向对象编程的三原则之一。封装的另外一个说法就是数据隐藏。在经典的面向对象编程中——它不同于 JavaScript 所依赖的原型化 OOP -- 数据隐藏是构建一个类模板的内在组成部分.
例如在 C# 中, Animal 类的封装 -- 隐藏数据 -- 特定的值被关联到 Animal 对象. 那样,如果某人决定变更那些值,他或他必须明确的通过初始化一个 Animal 对象并设置这个对象的值来达到目的. 在 JavaScript 中,我们则可以随意的在全局窗口对象中设置值.
- public class Animal
- {
- // constructor allows user to set the commonName
- public animal(string name)
- {
- this.commonName = name;
- }
- // making commonName private hides (encapsulates) it in the class
- private string commonName;
- //explicitly exposing the value for reading only
- public string CommonName get { return this.commonName }
- }
在 JavaScript 中,模块已经被创建用来模拟这种封装行为了,如此我们就不会去将我们的变量组织到一个全局的命名空间中,并造成了隐藏很深的难以被发现和修复的问题.
现在你知道为什么了,让我们来看看如何会是这样的.
函数被立即调用的表达式 (IIFE)
看上去就好像每次我们向前推进一步,我们都要走点旁门左道. 因为要获得能让我们创建模块模式的 JavaScript 语法,我们就得去了解一种叫做函数被立即调用的表达式语法,也叫做 IIFE (IIFE 发音是"iffy").
最基础的 IIFE 看起来像这样:
- (function(){
- // lines
- // of
- // code
- }());
如果你从来没有看到过像这样的东西,那你就有点说不过去了. 立即被调用
首先,这个名称的第一部分叫做立即被调用的原因是,一般包含这个特殊函数的源文件被加载好了,那么包含在这个函数中的代码就会运行. 对 IIFE 语法更加仔细的观察
你可以看到这个语法的最中心是一个函数。看一下这个代码块,我已经将代码分段并将一些行标上了号,如此我们就可以探讨它了.
- ( // 1.
- function() //2.
- { // 3.
- // 一行一行
- // 的
- // 代码
- }() // 4.
- ); // 5.
首先,看看上面脚本的第 2 行。这一行通常看来就是一个匿名(也就是没有命名)的函数声明. 而后,第 3 一直到第 4 则是这个函数的主题部分。最后,第 4 行最后以一对括弧结束,这对 括弧会告诉 JavaScript 解释器去调用这个函数。最终, 所有这些都会被包在一个不归属任何部分的括弧(第 1 和第 5 行)中, 而这对括弧会告诉解释器要调用这个外部的匿名函数,它包含了我们所定义的函数.
IIFE 可以带上参数
这段奇怪的语法会在带上参数之后,看起来会更加的奇怪. 它看起来会像是下面这样
- (function(thing1, thing2){
- // lines
- // of
- // code
- }("in string", 382));
现在,你可以看到这个函数可以带上两个会被内部的函数引用的 thing1, thing2 参数. 被传入值,在示例中是 "in string" 和 382.
现在我们理解了 IIFE 语法,让我们来创建另外一个代码示例,我们将运行这段代码来看看封装是如何运作的.
- (function(){
- var isDoingWork = false;
- console.log("isDoingWork value : " + isDoingWork);
- }());
自己动手看看
为了看看是怎么运行的,你可以做下面这几步:
当方法被调用时 -- 这会在代码被 JavaScript 解释器加载支护立即发生 -- 而后函数会创建 isDoingWork 变量,并调用 console.log() 来在控制台输出这个变量的值.
现在,让我们使用开发工具中的控制台来试试我们之前所尝试过的步骤:
输入: isDoingWork 然后回车 <ENTER>
当你这样做了之后,你将会看到 浏览器不再相信 isDoingWork 这个值被定义过。即使是你尝试从全局窗口对象中获取这个值, 浏览器也不认为 isDoingWork 这个值在此对象中被定义了. 你所看到的错误消息看起来会像接下来这张图片中所展示的这样.
函数是一个对象:它创建了范围
这是因为现在你已经把 isDoingWork 这个变量创建在了一个函数里面 -- 也就是我们们的匿名 IIFE 中 -- 而如此这个变量就只能通过这个函数才能访问到. 有趣的是 Javascript 中的所有函数都是第一类对象. 那很简明的意味着函数是一个对象,它可能通过一个变量被访问到. 或者说,另外一种描述的方式是你存储了指向 函数的一个引用,并在稍后的某个时间获取其变量.
在我们第一个示例中,我们的问题是并没有保存一个指向我们匿名函数的引用,所以我们永远也不能再获取到 isDoingWork 这个值。这就是我们下一个示例要改进的地方.
函数是一个对象 : 使用 this
因为每一个函数都是一个对象,所以每个函数都会有一个 this 变量,这个变量向开发者提供了指向当前对象的引用. 为了提供在从外部大我们的函数及其范围的访问,我们可以返回这个 this 变量 -- 而它将会提供一个指向当前对象的引用.
然后,除非我们将这个私有的 isDoingWork 变量添加到函数引用(this) 上,我们也不能够引用这个变量。为此我们要对之前的示例做一下轻微的改动。它看起来会像下面这样:
- thing = (function(){ // 1.
- this.isDoingWork = false; // 2.
- console.log("isDoingWork value : " + isDoingWork);
- return this; // 3.
- }());
你可以看到第一行我们加入了一个新的全局变量 thing,它包含了从匿名函数返回的值。从示例代码的开头跳到第三行,你可以看到我们返回了 this 变量。那就意味着我们返回了一个指向匿名函数的引用.
在第二行我们也已经将 isDoingWork 加入了 this 引用中,那样我们就可以使用语法 thing.isDoingWork 来从外部引用到这个值了. 自己动手看看
为了看看的运行,你可以做下面这几步:
模块模式总结
在最后这个示例中,变量值被成功的封装了,而其他的 JavaScript 库则可以明确的引用 thing 对象来获取这个值. 好像不大可能,而这帮助了我们保持全局命名空间的干净,并且在看起看来是更好的代码组织形式. 这也使得我们代码的维护更容易. 最终,我们用上了 AngularJS
因为使用模块模式是一个最佳实践,AngularJS 的开发者就将一个模块系统构建到了库中. Plunker 代码
首先你可以通过到这个 Plunker 上 (http://plnkr.co/edit/js8rbKpIuAuePzLF2DcP?p=preview - 在一个新的窗口或 Tab 页打开) 获取整个 AngularJS 示例.
而我们在这里展示出代码,那样我们就可以更方便的谈论它了.
首先,让我们看看这个 HTML.
- <!DOCTYPE html>
- <html ng-app="mainApp">
- <head>
- <meta charset="utf-8" />
- <title>
- Angular Module Example
- </title>
- <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.20/angular.js"
- data-semver="1.2.20">
- </script>
- <script src="mainCtrl.js">
- </script>
- <script src="secondCtrl.js">
- </script>
- </head>
- <body>
- <div ng-controller="MainCtrl as mc">
- <p>
- mc refers to MainCtrl which has been added to the angular app module
- </p>
- <p>
- Hello {{mc.name}}!
- </p>
- <ol>
- <li ng-repeat="a in mc.allThings">
- {{a}}
- </li>
- </ol>
- </div>
- <div ng-controller="SecondCtrl as sc">
- <p>
- Hello {{sc.name}}
- </p>
- <ol>
- <li ng-repeat="a in sc.allThings">
- {{a}}
- </li>
- </ol>
- </div>
- </body>
- </html>
Angular 指令 : ng-app
Angular 所定义和使用的东西叫做指令。这些指令基本上就是由 Angular 定义属性,而 AngularJS 编译器(Angular 的 JavaScript)会将它们转换成其他的东西.
我们应用了 ng-app 指令,为我们的 Angular 应用定义了一个名称,叫做 mainApp.
mainApp 就是我们稍后会看到的模块模式的起点. 被引入的脚本 : 每个都是一个模块
现在,请注意有三个脚本被引入到了这个 HTML 中.
第一个是必须的 AngularJS 库.
而其他两个则是作为模块被实现的 Angular 控制器.
它们被作为模块实现以保持代码彼此,还有从这个应用上看,都是独立的.
AngularJS : 创建 score
在往下看,你将会看到两个以如下代码开头的 div:
- <div ng-controller="MainCtrl as mc">
- <div ng-controller="SecondCtrl as sc">
这是在为 div 的每一个都设置上 ng-controller. 这些 div 中的每一个都有其各自的范围. 第一个控制器的名字叫做 MainCtrl,第二个叫做 SecondCtrl.
AngularJS 编译器会在你提供(引入)的代码中用这两个名称查找对应的函数.
如果 AngularJS 编译器没有找个这两个名称对应的函数,它就会抛出一个错误.
mainCtrl.js : 第一个控制器
让我们来看看 mainCtrl.js 文件里面有些啥东西.
你可以在 Plunker 页面的左侧点击它在 Plunker 中将其打开.
当你打开了它,你将会看到一些看上去很熟悉的代码。好吧,你至少会看出来它们都是被包在一个 IIFE 中的.
- (function() {
- var app = angular.module('mainApp', []);
- app.controller('MainCtrl', function() {
- console.log("in MainCtrl...");
- // vt = virtual this - just shorthand
- vt = this;
- vt.name = 'MainCtrl';
- vt.allThings = ["first", "second", "third"];
- });
- })();
那是因为我们需要这些代码在文件 mainCtrl.js 被加载时就运行.
现在,请注意在这个 IIFE 中的第一行代码.
- var app = angular.module('mainApp', []);
这行代码是 Angular 将一个模块添加到其命名空间的方式. 在这里,我们添加了一个将用来展示我们应用程序的模块. 这是应用程序的模块,而我们已经将其命名为 itmainApp, 它跟 HTML 页面上 ng-app 所指定的值是一样的.
我们也创建了一个叫做 app 的(只在 IIFE 本地可见的)本地变量,以便我们将可以在这个函数内部用来再次添加一个控制器.
奇怪的 Angular 语法
请你也要再仔细看看第一行。你会注意到我们是首次创建 mainApp 模块,而如果是首次,则我们必须提供以字符串数组的形式提供其可能需要的任何依赖 (,表示出依赖库的名称). 不过,在这里对于这个简单的示例而言,我们不需要任何的依赖。但 Angular 仍然需要我们传入一个空的数组,以便它知晓我们正在创建新的模块,而不是去试图加载一个已经被创建好了的模块.
提示: 你将会看到我们会在 secondCtrl.js 里加载 mainApp 模块,而上面所提的数组将会有更多的作用.
我们一把 mainApp 创建好,就需要向其添加我们的控制器. 这些就是 Angular 预期我们在 HTML(的 div 中)加入的控制器. 将控制器添加到 App 模块
添加控制器的代码看起来像下面这样:
- app.controller('MainCtrl', function() {
- console.log("in MainCtrl...");
- // vt = virtual this - just shorthand
- vt = this;
- vt.name = 'MainCtrl';
- vt.allThings = ["first", "second", "third"];
- });
为了添加我们的控制器函数,我们向 app.controller() 函数提供了一个控制器名称和一个函数. 在此处我们提供了一个匿名函数.
所以,我们的控制器主体代码就是下面这几行了:
- console.log("in MainCtrl...");
- // vt = virtual this - just shorthand
- vt = this;
- vt.name = 'MainCtrl';
- vt.allThings = ["first", "second", "third"];
这里,当我们的控制器运行时,会向控制台输出一行. 然后,我们将 this 变量重命名为 vt(方便起见,就叫他虚拟的 this) ,而后我天为其添加了一个 name 属性和一个叫做 allThings 的字符串数组.
控制器和封装
那就是当控制器被 Angular 调用时会运行的代码. 那个控制器会在文件被加载时运行起来,也就是一开始 HTML 被加载的时候. 这意味着控制器会被加载到 app 模块中,而这些属性会被添加到控制器对象(函数)中。因为我们想 this 变量添加了属性,我们就可以在稍后获取这些属性,但它们是被封装了起来的,因此它们不可以被每个人随意的更改.
现在,让我们跳到 HTML 中控制器被引用和使用的地方. 第一个 Div
这是我们的 MainCtrl 控制器被引用和使用的第一个 Div。它看起来就像下面这样:
- <div ng-controller="MainCtrl as mc">
- <p>
- mc refers to MainCtrl which has been added to the angular app module
- </p>
- <p>
- Hello {{mc.name}}!
- </p>
- <ol>
- <li ng-repeat="a in mc.allThings">
- {{a}}
- </li>
- </ol>
- </div>
这个 div 输出我们的 web 页面的如下部分,看起来就是接下来这张图片上所展示的那样.
输出被使用 Angular 指令来创建
不过,它使用了一种特殊的方式创建那个输出,它使用了两种 Angular 指令:
- {
- {
- mc.name
- }
- }
- ng - repeat
第一个指令被关联到了 Div 那一行上面 MainCtrl 的声明和引用. 我们告诉 Angular,说我们想以 mc 这个名称引用我们的 MainCtrl 函数(对象)。那就是 Angular 提供的一个很棒的缩写功能.
现在,因为我们将一个属性放到了 MainCtrl 的 this 对象上,我们现在就可以通过 mc 和属性的名称来引用那些东西了。我们将那些东西包含特殊的双大括号 {{}} 里面,如此 Angular 编译器就懂得那是可以运行的代码,你就会瞧见 Angular 将其转换成了 HTML:
- <p>
- Hello {{mc.name}}!
- </p>
编程了下面这一:
- Hello MainCtrl!
之后,我们设置了一个漂亮的无需列表,并使用了 ng-repeat 指令来迭代输出数组中的每一行.
然后 Angular 跌倒了整个 allThings 数组,并将其装换成了下面的 HTML
- <li ng-repeat="a in mc.allThings">
- {{a}}
- </li>
变成了如下的输出
- 1. first
- 2. second
- 3. third
就那么简单。这就是模块化的所有东西,我们的值再也不会被任何人动手动脚了.
SecondCtrl : 几乎就是同样的东西
这里有 SecondCtrl 的代码. 代码机会就是一样的,除了我们获取我满原来的 app 模块处有点不一样——不是第一次创建它了.
- (function() {
- var app = angular.module('mainApp');
- app.controller('SecondCtrl', function() {
- console.log("in SecondCtrl...");
- // vt = virtual this - just shorthand
- vt = this;
- vt.name = 'SecondCtrl';
- vt.allThings = ["bacon", "lettuce", "tomato"];
- });
- })();
仔细看看下面这一行:
- var app = angular.module('mainApp');
唯一的不同就是我们没有提供引用数组.
那是因为 mainApp 已经是存在了的,而我们只是想向其添加另外一个新模块 (SecondCtrl) . 总结:最佳实践
所有其它的脚本中的代码,以及 HTML 基本上是一样的, 而此处最重要的是所有的代码都被模块化了,数据也被封装了起来,以便更好的组织我们的代码. 这是 Google 软件开发者遵循的一个最佳实践,也是我们应该遵循的。请学习他,运用它,并与它同在吧(阿门).
来源: http://www.phperz.com/article/17/0410/270767.html