`
2008winstar
  • 浏览: 57533 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
  • chenke: 写的很好,也可以看看那这个文章,我感觉学的还可以。http:/ ...
    HTML

23世纪的JavaScript

 
阅读更多

本文来自这里,翻译过程中加入了译者的理解并作了适当的筛减。

 

在过去的几年中,JavaScript应用程序在规模和复杂性方面都越来越大。市场上出现了越来越多的单页面应用,并且对这种体验的需求已经达到了促使Google最终决定在爬行页面的过程中考虑支持JavaScript。

 

对单页面应用的需求,也促使JavaScript架构变得越来越重要。而对于作为动态语言的JavaScript来说,为了保证代码的可维护性、避免代码混乱,你需要做很多额外的工作。

 

过去很长一段时间中,在一个单一的文件中使用jQuery选择器以及事件处理并没有什么不好。然而,这种方式并不是一种可持续的模式。在23世纪中面临的现代Web,我们需要更多的思考,并且需要通过架构的方式来解决这样的问题。

 

架构模式

 

架构模式不是坐下来就能写出来的。不可能坐在那里就能想出如何写出一个很好的模式。模式是在解决问题的过程中发现的。许多问题可以使用同样的方式解决,而模式是从这些解决方案中抽取出来的。

 

构造器

 

首先,让我们先看看已存在多年的JavaScript内置的功能——构造器模式

function Starship() {}

 

在JavaScript声明的所有函数都可以用作构造函数来创建一个实例。这样,就可以创建可重用的函数了。

var enterprise = new Starship(),
IKSBuruk = new Starship();

 

可以修改上面这个函数,给函数传一些参数从而实现更好地描述实例。可以将参数赋值到this的属性上,this将指向由这个构造函数创建的实例。

function Starship(owner, operator, type) {
   this.owner = owner;
   this.operator = operator;
   this.type = type;
}
var enterprise = new Starship('Federation', 'Star Fleet', 'Class 1 Heavy Cruiser'),
birdOfprey = new Starship('Klingon Empire', 'Klingon Imperial Fleet', 'Klingon Warship');

 

除此之外,还可以使用JavaScript构造函数的一个内置特性——prototype。简单地说,prototype是一个对象。在这个对象上,可以定义一些属性和方法,而这些属性和方法会添加到由该构造函数创建出来的每个实例上。

function Starship(owner, operator, type, weapons) {
  /* ... */
  this.weapons = weapons;
}

Starship.prototype.fire = function(weapon) {
  this.weapons[weapon].launch();
};

function PhotonTorpedoSystem() {}
  PhotonTorpedoSystem.prototype.launch = function() {
  console.log('launching torpedos');
};

var torpedos = new PhotonTorpedoSystem(),

var weaponSystem = {
  torpedos: torpedos;
};

var enterprise = new Starship(
  'Federation',
  'Star Fleet',
  'Class 1 Heavy Cruiser',
  weaponSystem
);
enterprise.fire('torpedos') // launching torpedos;

 

JavaScript的prototype体系真正重要的是它可以帮助你创建一个继承体系。JavaScript中的继承与其他语言有些许的不同,但通过实践其也不难理解。

function ConstitutionClass(captain, firstOfficer, missionDuration) {
  this.captain = captain;
  this.firstOfficer = firstOfficer;
  this.missionDuration = missionDuration;

  Starship.apply(this, ['Federation', 'Star Fleet', 'Class 1 Heavy Cruiser', weaponSystem]);
}

ConstitutionClass.prototype = Object.create(Starship.prototype);
ConstitutionClass.prototype.constructor = ConstitutionClass;

ConstitutionClass.prototype.warp = function(speed) {
console.log('warping at: ' + speed);
};

var enterprise = new ConstitutionClass('Kirk', 'Spock', 5);
enterprise.fire('torpedos'); // Launching Torpedos
enterprise.warp(14.1); // warping at: 14.1

 

这里,我们创建了ConstitutionClass构造函数。该构造函数接收三个参数captain,firstOfficer和missionDuration。在函数中调用了Starship.apply方法并且传了一个数组参数。这确保了父级构造函数将被调用。

 

JavaScript中每个函数都有一个call和apply方法,这两个方法允许你通过传入一个上下文来修改函数执行时的上下文,而使用apply方法的话,还需要同时传入一个数组,这个数组包含函数执行时所需的参数。

 

接下来,为了正确地让ConstitutionClass继承自Starship,我们简单地使用了object.create来赋值给ConstitutionClass的prototype。

 

这里所做的是告诉ConstitutionClass,将Starship的prototype中所有的属性和方法加到ConstitutionClass的prototype上。

 

这就是原型链继承。

 

IIFE避免全局泄漏

 

需要注意的一点是,上面的代码中存在几个全局泄漏的问题。当你在一个JavaScript文件中简单地声明函数或变量时,这些函数或变量会被添加到全局命名空间上(在浏览器中指的是window)。

 

下面的代码会创建好几个全局变量:

function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
  console.log('launching torpedos');
};

var torpedos = new PhotonTorpedoSystem(),

var weaponSystem = {
  torpedos: torpedos;
};

 

执行这段代码后,会创建window.PhotonTorpedoSystem,window.torpedos,以及window.weaponSystem。这是不好的情形。

 

为了避免这个问题,一个重要的模式是使用立即执行函数表达式(IIFE,Immediately Invoked Function Expression):

(function(global) {

function PhotonTorpedoSystem() {}
  PhotonTorpedoSystem.prototype.launch = function() {
  console.log('launching torpedos');
};

var torpedos = new PhotonTorpedoSystem(),

var weaponSystem = {
  torpedos: torpedos;
};

/* Create instance of enterprise etc */

global.PhotonTorpedoSystem = PhotonTorpedoSystem; // Export just this to the window
}(window));

 

这里可以看到前面的(function(global) {和后面的}(window));。这就创建了一个匿名的函数并且该函数将被立即执行,同时将global的值设为window。这些都是有效的,因为IIFE创建了一个闭包。在IIFE中定义的一切只包含在那个函数中,除非它被输出到我们定义的global上。

 

现在,可以显性地在window上设置内容以便在其他的JavaScript文件中也可以使用。

 

这种方式已经在模式的方向上迈出了正确的一步,但另外一个更好的模式是使用命名空间的模式:

// weaponSystems.js
(function(global, NS) {
NS.Weapons = NS.Weapons || {};

function PhotonTorpedoSystem() {}
PhotonTorpedoSystem.prototype.launch = function() {
  console.log('launching torpedos');
};

/* ... */

NS.Weapons.PhotonTorpedoSystem = PhotonTorpedoSystem; // Export just this to the NS.Weapons namespace
}(window, window.NS = NS || {});

 

注意到这里我们将前面IIFE的最后一行的代码改成了}(window, window.NS = NS || {});。这样就会给函数传入window和NS对象。这里稍微有点怪的写法是说,“如果NS存在则直接传进入,如果NS还不存在则为它创建一个新的对象”。

 

你也可以这样使用这种语法,如NS.Weapons = NS.Weapons || {};

 

它跟下面的if语句是一样的,只不过这种写法更加简洁:

if (!NS.Weapons) {
NS.Weapons = {};
}

 

你可以向命名空间上添加任何对象和函数。这种方式使你避免了添加太多的全局变量到window上。

(function(global, NS) {
NS.Ships = NS.Ships || {};

function Starship(owner, operator, type, weapons) { /* ... */ }
function ConstitutionClass(captain, firstOfficer, missionDuration) { /* ... */ }

/* ... */

NS.Ships.Starship = Starship;
NS.Ships.ConstitutionClass = ConstitutionClass;

}(window, window.NS = NS || {});

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics