为现代JavaScript开发作好准备

今天不管是在浏览器中仍是在浏览器外,JavaScript世界正在经历翻天覆地地变化。若是咱们谈论脚本加载、客户端的MVC框架、压缩器、AMD、Common.js还有Coffeescript……只会让你的脑子发昏。对于那些已经早就熟知这些技术的人而言,或许很难想象到如今为止还有不少JS开发者还不熟悉这些工具,甚至事实上,他们极可能如今还不想去尝试这些工具。 javascript

这篇文章将会介绍一些很基础的JS知识,以及当开发者想要尝试Backbone.js和Ember.js之类的工具以前须要知道一些内容。当你理解了这篇文章中的大部份内容的时候,你会更有信心去学习其余高级JavaScript知识的时候。这篇文章是假设你曾经使用过JavaScript的,因此若是你从没有接触过它,也许你须要先了解下更基础的知识。如今咱们开始吧! java

 

模块 git

有多少人在一个文件中写的JS像下面的代码块同样?(注意:我可没有说内嵌在HTML文件中哦): github

1
2
3
varsomeSharedValue = 10;
varmyFunction =function(){//do something }
varanotherImportantFunction =function() {//do more stuff }

若是你作到了这一点,那么颇有可能你正在写这样的代码。我不是在给你下定义,由于在至关长的一段时间里我也曾这么写程序。事实上这段代码有不少毛病,不过咱们会专一在讨论全局命名空间的污染问题上。这样的代码代码会把方法和变量都暴露在了全局中,咱们须要将让这些数据与全局命名空间独立开来,咱们将会采用模块模式(Module Pattern)来实现这个目的。模块中能够有不少不一样的形式达到咱们的目标,我会从最简单的方法开始说:匿名函数(Immediately Invoked Function Expression,简写为:IIFE)。 设计模式

名字听起来很高大上,不过它的实现其实很简单: 浏览器

1
2
3
(function(){
    //do some work
})();

若是在此以前你从未接触过匿名函数,可能如今你会以为它很怪 — 怎么会有这么多括号!匿名函数是会当即执行的函数,你能够这么理解:一个函数被建立了后又马上被调用。它应该是一个表达而不是一个语句:一个函数语句是必定要有一个名字的,可是你们也看到了,匿名函数是没有名字的。在函数定义的外部还有一组括号,这一点也能很好地帮助咱们在代码中轻易找到匿名函数的身影。 安全

如今咱们知道要怎么写一个匿名函数了,那就来聊聊为何要使用它吧。在JS中咱们都是在和各类做用域之中的函数打交道,因此若是咱们想要建立一个做用域,就可使用函数。匿名函数中的变量和方法的做用域仅仅在匿名函数中,就不会污染全局的命名空间,那么如今还须要考虑的一个问题是,咱们要如何从外部取得那些在匿名函数做用域中的变量和方法呢?答案就是全局命名空间:将变量放入全局命名空间中,或者至少将做用变量与全局命名空间关联起来 闭包

想要在匿名函数外部调用方法,咱们能够将window对象传入匿名函数,再将函数或变量值赋值到这个对象上。为了保证这个window对象的引入不会形成什么混乱,咱们能够将widow对象做为一个变量传入咱们的匿名函数。当作函数传入参数的方法一样适用于第三方库,甚至undefined这样的值。如今咱们的匿名函数看起来是这样的: 框架

1
2
3
(function(window, $, undefined){
    //do some work
})(window, jQuery);

正如你所看到的,咱们将window和jQuery传入函数中(’$'符号表示的就是’jQuery’,把它用在这的缘由是防止其余库也定义了’$'),可是这个函数实际上是接收了3个参数。若是咱们没有传入第三个参数,那么在遇到undefined的时候就会结束, 为了不有其余的JS文件更改这一点,因此咱们将一个undefined的变量传入方法中来保证这个方法里是必定可使用undefined的。其实在函数内咱们也是能够直接使用这些值,能这么作的原理是,JS的闭包会覆盖他们所处的上下文。对于这个话题,我曾写过一篇关于C#的文章以解释这个概念,这二者是互通的。 函数

如今咱们有了一个会当即执行的方法,还有一个相对安全的执行上下文,其中还包含有window$undefined变量(这几个变量仍是有可能在这个脚本被执行前就被从新赋值,不过如今的可能性要小的多了)。如今咱们已经作得很好了:把咱们的代码从全局环境下的一团混乱的局面中拯救了出来;下降了与其余在同一应用中使用的脚本的冲突可能性。

任何咱们想要从模块中获取的东西均可以经过window对象拿到。可是一般我不会直接将模块中的内容直接复制到window对象上,而是会用更有组织性地将模块中的内容。在大部分语言中,咱们将这些容器称为“命名空间”,在JS中咱们能够用“对象”的方式来模拟。

 

命名空间

若是咱们想要声明一个命名空间,将一个函数放进这个空间中,代码能够写成这样:

1
2
3
4
window.myApp = window.myApp || {};
window.myApp.someFunction =function(){
    //so some work
};

咱们只是在全局环境中建立了一个用于查看某个对象是否已经存在,若是已经存在了,那么咱们就能够直接使用;否则就须要用’{}’来建立一个新的对象。接着,咱们能够开始添加这个命名空间的内容,将各类函数放入这个空间中,就像上面的代码片断所作的那样,可是咱们又不但愿这些函数就随便的放在那里,而是但愿将模块和命名空间联系在一块儿,就像下面这样:

1
2
3
(function(myApp, $, undefined){
    //do some work
}(window.myApp = window.myApp || {}, jQuery));

还能够这么写:

1
2
3
4
window.myApp = (function(myApp, $, undefined){
    //do some work
    returnmyApp;
})(window.myApp || {}, jQuery);

如今,咱们再也不是将window传入咱们的模块中,咱们将一个和window对象联系在一块儿的命名空间传入模块中。之因此使用’||’的缘由是咱们能够重复使用同一个命名空间,而不是每次须要使用命名空间的时候咱们又要从新建立一个。许多包含有命名空间方法的库会帮你建立好空间的,或者你可使用一些想namespace.js这样的工具来构建嵌套的命名空间。因为在JS中,每个在命名空间中的项你都不得不指定它的命名空间,因此一般我都尽可能不会去建立深度嵌套的命名空间。若是你在MyApp.MyModule.MySubModule中建立了一个doSomething方法,你须要这么引用它:

1
MyApp.MyModule.MySubModule.doSomething();

每次你要调用它,或者你能够在你的模块中给这个命名空间一个别名:

1
varMySubModule = MyApp.MyModule.MySubModule;

这样定义之后,若是你想用doSomething这个方法能够用MySubModule.doSomething()来调用。不过这个方式实际上是没必要要的,除非你有很是很是多的代码,否则这么作只会将问题复杂化。

 

揭秘模块模式

在建立模块时你也常会看到另外一种设计模式:揭秘模块模式(Revealing Module Pattern)。它和模块模式有一些不一样:全部定义在模块中的内容都是私有的,而后你能够把全部要暴露到模块外部的内容放在一个对象中,再返回这个对象。你能够这么作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
varmyModule = (function($, undefined){
    varmyVar1 ='',
    myVar2 ='';
 
    varsomeFunction =function(){
        returnmyVar1 +" "+ myVar2;
    };
 
    return{
        getMyVar1:function() {returnmyVar1; },//myVar1 public getter
         setMyVar1:function(val) { myVar1 = val; },//myVar1 public setter
         someFunction: someFunction//some function made public
     }
})(jQuery);

一次就创建一个模块,而后返回一个包含有须要公有化的模块片断的对象,同时模块中须要保持私有的变量也不会被暴露。myModule变量会包含有两个共有的项,不过其中Somefunction中的myVar2是从外部获取不到的。

 

建立构造器(类)

在JS中没有“类”这个概念,可是咱们能够经过建立构造器来建立“对象”。假设如今咱们要建立一系列Person对象,还须要传入姓、名和年龄,咱们能够将构造器定义成下面这样(这部分代码应该放在模块之中):

1
2
3
4
5
6
7
8
9
varPerson =function(firstName, lastName, age){
    this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
}
 
Person.prototype.fullName =function(){
   returnthis.firstName +" "+this.lastName;
};

如今先看第一个函数,你会看到咱们建立了一个Person构造器。咱们用它来构造新的person对象。这个构造器须要3个传入参数,而后将这3个参数赋值到执行上下文中。咱们也是经过这种方式获取到公有实例变量。这里也能够建立私有变量:将传入参数赋值到这个构造器中的局部变量。可是这么作之后,公有的方法就无法获取这些私有的变量了,因此你最好仍是把它们都变成公有的。也能够把方法放在构造器中同时还能从外部获取到它,这样方法就能拿到构造器里的私有变量了,不过这么作的话又会出现一系列新的问题。

第二个方法中咱们使用了Person构造器的”原型”(prototype)。一个函数的原型就是一个对象,当你须要在某个实例上解析它所调用到的字段或者函数时你须要遍历这个函数上全部的实例。因此这几行代码所作的就是建立一个fullName方法的实例,而后全部的Person的实例都能直接调用到这方法,而不是对每一个Person实例都添加一个fullName方法,形成方法的泛滥。咱们也能够在构造器中用

1
this.fullName =function() { …

的方式定义fullName,但这样每个Person实例都会有fullName方法的副本,这不是咱们但愿的。

若是咱们想要建立一个Person实例,咱们能够这么作:

1
2
varperson =newPerson("Justin","Etheredge");
alert(person.fullName());

咱们也能够建立一个继承自Person的构造器:Spy构造器,咱们会建立Spy的一个实例,不过只会声明一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
varSpy =function(firstName, lastName, age){
   this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
};
Spy.prototype =newPerson();
 
Spy.prototype.spy =function(){
    alert(this.fullName() +" is spying.");   
}
 
varmySpy =newSpy("Mr.","Spy", 50);
mySpy.spy();

正如你所看到的,咱们建立了一个和Person很类似的构造器,可是它的原型是Person的一个实例。如今咱们又添加上一些方法,使得Spy的实例又能够调用到Person的方法,同时还能直接取得Spy中的变量。这个方法比较复杂,不过一旦你明白怎么使用了,你的代码就会变得很优雅。

 

结语

看到这里,但愿你已经学到了一些东西。不过这篇文章里并无介绍多少关于“现代”JS的开发。这篇文章中涉及的仍是旧知识,在过去几年里它们的使用面至关广。但愿你看完这篇文章之后,找到了学习JS的正确的方向。如今可能你把代码放到了不一样的模块不一样的文件中(你应该作到这一点!),那么下一步你要开始着手研究如何将JS结合和压缩。若是你是使用Rails 3的开发者,能够在asset pipeline上免费获取这些信息或者工具。若是你是.NET开发者,你能够看看SquishIt框架,我就是从这里开始的。若是你是ASP.NET MVC 4的开发者,也有相关的资源。

但愿这篇文章对你有帮助。之后我也会介绍关于现代JS的开发,期待到时候能看到你的ID。

原文连接: codethinked   翻译: 伯乐在线 kmokidd
译文连接: http://blog.jobbole.com/66135/

相关文章
相关标签/搜索