>웹 프론트엔드 >JS 튜토리얼 >AngularJS_AngularJS를 사용하여 더욱 아름다운 JavaScript 코드를 작성하기 위한 가이드

AngularJS_AngularJS를 사용하여 더욱 아름다운 JavaScript 코드를 작성하기 위한 가이드

WBOY
WBOY원래의
2016-05-16 15:54:071307검색

이 기사의 샘플 코드 다운로드: modulePattern.zip - HTML 파일 4개 모두 및 panacea.js - 1.6KB

소개

AngularJS 라이브러리에는 많은 것들이 있지만 이 기사에서는 Angular에 대한 좋은 소개를 제공할 것이라고 생각되는 소규모 주제별 라이브러리에 중점을 두고 싶습니다. 이 기사를 이해할 필요는 없습니다. Angular 관련 또는 JavaScript 경험. 이 기사를 통해 Angular 사용의 이점을 확인하고 기꺼이 사용해 보시기 바랍니다.
배경

저는 한동안 Angular를 사용해왔고 Angular를 배울 때 샘플을 만드는 것도 좋아해서 처음 접했을 때는 모듈이나 JavaScript 디자인 패턴에 대해 많이 생각하지 않았습니다. 코드를 체계적으로 정리하는 것이 핵심입니다. 이제 다시 돌아와서 모듈을 사용하는 것이 얼마나 쉬운지 보여드리기 위해 아주 작은 샘플을 만들었습니다. Angular에 대한 좋은 입문이 될 것입니다.

패턴 설명에 있어 (대부분) 기사의 문제

대부분의 사람들은 독자가 패턴이 무엇인지 알기도 전에 패턴을 설명하려고 시도하며 이는 기본적으로 모든 사람을 오해하게 만듭니다. 이 기사를 최대한 단순하게 유지하기 위해 첫 번째 질문을 살펴보겠습니다. 어떤 질문인가요? 기본적으로 전역 메모리 공간에 모든 것이 생성되는 자바스크립트에 대한 질문입니다.

제 말은 이렇습니다.
JavaScript 기본 전역 문제

HTML에 다음 스크립트가 있다고 가정해 보세요.

<script>
   var isDoingWork = false;
</script>

범위?

이 변수의 범위를 알고 계시나요?
예, 글로벌합니다. 이 부울 값은 실제로 브라우저의 전역 창 개체에 추가됩니다.

행동으로 설정

여기에서 실제로 어떻게 보이는지 확인할 수 있습니다.

  • 이 기사의 코드 샘플을 다운로드하세요.
  • 브라우저에서 modulePattern.htm을 엽니다.
  • 브라우저 개발 도구 열기 -- F12(Chrome, IE) 또는 Ctrl-Shift-I(Opera) -- (그러면 콘솔이 표시됩니다)
  • 브라우저 도구 콘솔에 isDoingWork를 입력한 다음 Enter917e69edaf2e1f1874848629aa911d32
  • 를 누르세요.
  • 출력값이 false인 것을 확인할 수 있습니다.
  • 이제 isDoingWork = true를 입력하고 Enter917e69edaf2e1f1874848629aa911d32
  • 를 누르세요.
  • 다운로드한 값이 사실입니다.
  • doingwindow.isDoingWork = true를 입력하고 를 누르면 이 값이 전역 창 개체에 추가된 것을 확인할 수 있습니다.
이로 인해 일부 이름 충돌이 발생하고 심각한 버그가 발생할 수 있습니다. 하지만 새로운 JS 라이브러리를 구현하기로 결정했다고 가정해 보세요. Panacea.js라는 이 멋진 라이브러리는 여러분의 모든 문제를 해결해 줄 것입니다.

따라서 페이지에서 다음과 같이 참조하세요.



<script src="panacea.js"></script>
아주 간단합니다. 이전에 겪었던 모든 문제를 해결했습니다. 그러나 거대한 라이브러리이기 때문에 돌아가서 거대한(수천 줄의 코드) 소스를 파헤쳐 보지 않고도 솔루션을 원할 수 있습니다. 그리고 Panacea.js의 한구석에 정확히 다음 코드가 묻혀 있습니다.



var isDoingWork = false;
 
setInterval(function(){isDoingWork = !isDoingWork;}, 3000);

这代码真是酷,你知道吗?

每个3秒,它都会将这个布尔值设置成相对的值。啊!

自己动手看看

如果你想要自己动手验证下这个东西,你可以做下面这几步:

  •     下载本文的样例代码.
  •     在你的浏览器中打开 modulePattern2.htm .
  •     打开浏览器开发工具 -- F12(Chrome, IE) 或者 Ctrl-Shift-I (Opera) -- (这样你就可以看到控制台了)
  •     在浏览器开发工具的控制台下,输入 : isDoingWork 然后回车917e69edaf2e1f1874848629aa911d32
  •     将第4步多重复几次,你将会发现isDoingWork的值会每个大约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);
}());

自己动手看看

为了看看是怎么运行的,你可以做下面这几步:

  •     下载本文的源代码.
  •     在你的浏览器中打开 modulePattern3.htm.
  •     打开浏览器的开发工具 -- F12(Chrome, IE) 或者 Ctrl-Shift-I (Opera) -- (这样你就可以看到控制台了)
  •     你可以看到很像下面这样图片中所展示出来的东西

201561993538699.png (471×103)

当方法被调用时 -- 这会在代码被JavaScript解释器加载支护立即发生 -- 而后函数会创建 isDoingWork 变量,并调用console.log()来在控制台输出这个变量的值.


现在,让我们使用开发工具中的控制台来试试我们之前所尝试过的步骤:

    输入: isDoingWork然后回车917e69edaf2e1f1874848629aa911d32

当你这样做了之后,你将会看到 浏览器不再相信isDoingWork这个值被定义过。即使是你尝试从全局窗口对象中获取这个值, 浏览器也不认为 isDoingWork 这个值在此对象中被定义了.  你所看到的错误消息看起来会像接下来这张图片中所展示的这样.

201561993603313.png (391×136)

函数是一个对象:它创建了范围

这是因为现在你已经把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来从外部引用到这个值了.
自己动手看看

为了看看的运行,你可以做下面这几步:

  •     下载本文的示例代码.
  •     在你的浏览器中打开 modulePattern4.htm.
  •     打开浏览器开发工具 -- F12(Chrome, IE) 或者 Ctrl-Shift-I (Opera) -- (那样你就可以看到控制台了)
  •     你将会看到isDoingWork的值会输出到控制台,就像最开始那个示例中你看到的那样.
  •     不过,现在你得输入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(&apos;mainApp&apos;, []);
 
 app.controller(&apos;MainCtrl&apos;, function() {
  console.log("in MainCtrl...");
  // vt = virtual this - just shorthand
  vt = this;
  vt.name = &apos;MainCtrl&apos;;
  vt.allThings = ["first", "second", "third"];
 
 });
})();

那是因为我们需要这些代码在文件mainCtrl.js被加载时就运行.

现在,请注意在这个IIFE中的第一行代码.
 

var app = angular.module(&apos;mainApp&apos;, []);

这行代码是Angular将一个模块添加到其命名空间的方式.  在这里,我们添加了一个将用来展示我们应用程序的模块.  这是应用程序的模块,而我们已经将其命名为 itmainApp, 它跟HTML页面上ng-app所指定的值是一样的.

我们也创建了一个叫做app的(只在IIFE本地可见的)本地变量,以便我们将可以在这个函数内部用来再次添加一个控制器. 

奇怪的 Angular 语法

请你也要再仔细看看第一行。你会注意到我们是首次创建mainApp模块,而如果是首次,则我们必须提供以字符串数组的形式提供其可能需要的任何依赖(,表示出依赖库的名称). 不过,在这里对于这个简单的示例而言,我们不需要任何的依赖。但Angular仍然需要我们传入一个空的数组,以便它知晓我们正在创建新的模块,而不是去试图加载一个已经被创建好了的模块.

提示: 你将会看到我们会在secondCtrl.js里加载mainApp模块,而上面所提的数组将会有更多的作用.

我们一把mainApp创建好,就需要向其添加我们的控制器. 这些就是Angular预期我们在HTML(的div中)加入的控制器.
 
将控制器添加到App模块

添加控制器的代码看起来像下面这样:
 

app.controller(&apos;MainCtrl&apos;, function() {
  console.log("in MainCtrl...");
  // vt = virtual this - just shorthand
  vt = this;
  vt.name = &apos;MainCtrl&apos;;
  vt.allThings = ["first", "second", "third"];
 
 });

为了添加我们的控制器函数,我们向app.controller()函数提供了一个控制器名称和一个函数. 在此处我们提供了一个匿名函数.

所以,我们的控制器主体代码就是下面这几行了:
 

console.log("in MainCtrl...");
  // vt = virtual this - just shorthand
  vt = this;
  vt.name = &apos;MainCtrl&apos;;
  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页面的如下部分,看起来就是接下来这张图片上所展示的那样.

201561993642776.png (331×158)

输出被使用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(&apos;mainApp&apos;);
 
 app.controller(&apos;SecondCtrl&apos;, function() {
  console.log("in SecondCtrl...");
  // vt = virtual this - just shorthand
  vt = this;
  vt.name = &apos;SecondCtrl&apos;;
  vt.allThings = ["bacon", "lettuce", "tomato"];
 
 });
})();

仔细看看下面这一行:
 

var app = angular.module(&apos;mainApp&apos;);

唯一的不同就是我们没有提供引用数组.

那是因为mainApp已经是存在了的,而我们只是想向其添加另外一个新模块 (SecondCtrl) .
总结:最佳实践

所有其它的脚本中的代码,以及HTML基本上是一样的,而此处最重要的是所有的代码都被模块化了,数据也被封装了起来,以便更好的组织我们的代码. 这是Google软件开发者遵循的一个最佳实践,也是我们应该遵循的。请学习他,运用它,并与它同在吧(阿门). 

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.