用JavaScript创建对象的现代方法

在停滞了几年之后,我们最近看到JavaScript语言开发取得了相当大的进展。ECMAScript5标准现在已经有3年的历史了,所有主要的浏览器供应商都在其当前版本中支持它。因此,现在也许正是真正接受新的语言特征并采用现代发展风格的时候。

如果JS不仅仅用于jQuery一行程序,那么您将需要一种结构化的方法。有很多方法可以用JS来构造代码。一个伟大的也是非常常见的方法是使用对象和原型继承。

老办法

假设你建立了一个商店,你需要快递产品。在以前的日子里,在ECMAScript5(ES5)之前,您可能会编写如下内容:

var Product = function(name) {
   this.name = name;
};
Product.prototype.showName = function() {alert(this.name);};

var myProduct = new Product("Super Fancy TV");
myProduct.showName();

实际上,这还不错。您有一个构造函数来创建对象并将方法附加到原型。所有单个产品都有这些方法,但有几个弱点需要考虑:

在JavaScript语言中没有私有属性。name的值可以随时从外部更改。

  • 方法是分散的。即使你把它们放在一个地方,也没有一个单一的结构来定义你的对象概念——就像许多其他面向对象语言中的类一样。

  • 你很容易忘记使用“new”关键字。这不会引发错误,它只会导致完全不同的行为,可能会导致一些很难找到的真正讨厌的bug。

  • 继承将带来更多问题。在JS社区中,没有一个一致同意的方法来正确地执行它。

由于这些原因,许多开发人员已经创建了提供所有类型的对象创建和实例化逻辑的库、框架和工具。其中许多引入了类(例如Prototype或Coffescript)。

如果你愿意,你可以走这条路,但我不推荐。可能很难相信,但原型继承实际上比基于类的继承容易得多,甚至提供了额外的好处。看看其他的原型语言,比如IO或Self。正是旧的JavaScript语法使得原型难以使用

道格拉斯·克罗克福德开创了一种更好的方法。他写了一篇短文对象.创建

方法已进入ES5标准-以稍微扩展的形式

对象.创建

好消息是,您不需要使用带有ES5实现的现代浏览器。Mozilla开发者网络有一个polyfill。它允许您使用创建对象的新方法,即使在IE8及以下的旧浏览器中也是如此。这一点很重要,因为IE7/IE8的市场份额总和仍约为9%。如果只针对Firefox、Chrome、Safari或Opera用户,这不是问题。

if (!Object.create) {
   Object.create = function (o) {
       if (arguments.length > 1) {
           throw new Error('Object.create implementation only accepts the first parameter.');
       }
       function F() {}
       F.prototype = o;
       return new F();
   };
}

只需包括从上面的polyfill你将有一个对象.创建功能。代码段的第一行检查对象.创建已经有了。这样可以确保在当前浏览器提供本机实现的情况下,polyfill不会覆盖本机实现。如果您需要其他ES5功能的额外多填充,可以使用Kris Kowal的ES5填充。

单独对象

让我们再看一看商店项目。如果您只想创建一个产品,则不需要对象.创建. 直接创建对象即可。

var myProduct = {
     price: 399.50,
     name: 'Super Fancy TV'
};

然而,这并不是最好的选择。您可以从外部轻松地操纵对象的属性,并且无法检查或转换指定的值。考虑像这样的任务我的产品价格=-20。

像这样的一个错误将确保你的公司销售很多产品很快,并有真正高兴的客户-客户收到20美元每购买。你的公司做不了多久!

多年的面向对象编程和设计经验告诉我们,要将对象的内部状态与其外部接口分离开来。通常需要将属性私有化,并提供一些getter和setter方法。

接受者和接受者

ES5为使用getter和setter访问属性提供了一个很好的概念。不幸的是,没有办法让它们在旧浏览器中运行——你不能只使用polyfill。所以在接下来的两年左右,我们大多数人都没有奢侈的使用它。同时,您可以使用许多方法(参见Stoyan Stefanov的JavaScript模式)。它们都有自己独特的优点和缺点。没有单一的解决方案,所以归根结底是口味的问题。以下是我通常做的:

带有下划线的前缀属性,例如价格。这只是将属性标记为private的约定。永远不要从对象外部调用它们;违反此规则通常很容易发现。还有更复杂的方法使用基于闭包的模式来存档真实的隐私(例如Stefanov的书)。我的观点是,大多数情况下,它们不值得付出努力。

  • 提供一个以“set”开头的setter方法,例如setPrice(value)。在旧浏览器中,无法覆盖赋值运算符=。所以这是下一个最好的选择。使用java和C++程序员。

  • 有时属性可能只供内部使用—或者您希望它只准备就绪。在这种情况下,只需省略适当的方法。因此更好的实现可能是这样的。

var myProduct={u price:99.50,\u name:'Grindle 3',

price:    function()  {return this._price;},
name:     function()  {return this._name;},
setPrice: function(p) {this._price = p;},

};

如果您想在设置价格之前检查价格,现在可以轻松地执行此操作。

setPrice: function(p) {
   if (p <= 0) {
       throw new Error("Price must be positive");
   }
   this._price = p;
}

真正的ECMAScript5实现甚至更好。它还有一个额外的好处,就是隐式地调用getter和setter。我的产品价格= 85.99. 最后,JavaScript支持统一访问原则!

var myProduct = {
...
       get price() {return this._price;},
       set price(p) {
           if (p <= 0) { throw new Error("Price must be positive"); }
           this._price = p;
       },

...

原型

一家真正的商店当然会有很多产品。你不想从头开始创造它们。你需要一个地方,在那里你可以放置共同的结构和行为。JS中通常的模式是为此创建一个父对象,一个产品的原型,所有其他产品都是从这个原型派生的。与对象.创建你用它来制造新产品。

var Product = {
   _price: 0,
   _name: '',

   price:    function()  {return this._price;},
   name:     function()  {return this._name;},
   setPrice: function(p) {this._price = p;},
   setName:  function(n) {this._name  = n;}
};

var product1 = Object.create(Product);
product1.setName('Grindle 3');
product1.setPrice(99.50);

初始化

为产品的所有新实例调用所有必要的设置程序可能有点不方便。为了避免这种情况,提供一个initialize方法,类似于其他语言的构造函数。

var Product = {
   ...
   init: function(name, price) {
       this._name = name;
       this._price = price;
   },
   ...
};
var product1 = Object.create(Product).init('Grindle 3', 99.50);

继承

原型方法的一大优点是实例化和继承的统一。你不需要任何特别的东西-只要使用对象.创建为了继承。

var Product = {
...
};

var Book = Object.create(Product);
Book._author = null;
Book._numPages = null;
Book.setAuthor   = function(author) {this._author = author;};
Book.setNumPages = function(num_pages) {this._numPages = num_pages;};
Book.author      = function() {return this.author();};
Book.numPages    = function() {return this.numPages();};

书是从产品中衍生出来的新事物。它没有设置特定的值,比如名称和价格,而是获得额外的结构和行为。新的属性author和numPages组成了这个结构,而它们的简单getter和setter充当了一些实际行为的占位符示例。

缺点是多次调用Book是多余的,而且整个语法与定义基本对象有很大的不同。因此,通常构建一个小的extend函数,使继承更加方便。

Object.prototype.extend = function(newProperties) {
   for (var propertyName in newProperties) {
       this[propertyName] = newProperties[propertyName];
   }
   return this;
};

再说一次,在真正的ES5中你不需要这个。真实的对象.创建允许包含扩展名的第二个参数。

现在可以使用新的extend方法来分解代码。

var Product = {
...
};

var Book = Object.create(Product).extend({
   _author: null,
   _numPages: null,
   setAuthor:   function(author) {this._author = author;},
   setNumPages: function(num_pages) {this._numPages = num_pages;},
   author:      function() {return this.author();},
   numPages:    function() {return this.numPages();}
});

内原型遗传

与基于静态类的方法相比,对象和原型概念有许多优点,例如:

继承和实例化的统一。你可以用同样的机制(对象.创建)从原型继承或从原型生成实例(类似于类的用法)。实际上,这是一回事。

  • 价值观的继承。您可以从原型继承值;无需在构造函数中设置默认值。

  • 原型的运行时修改。在JS中,运行时和编译时没有区别。您可以在程序执行期间修改原型,而更多的静态语言只允许在编译器运行之前更改类。这不是原型继承的直接优势,而是JS的动态特性。甚至还有基于类的语言,允许对类进行运行时修改——Ruby或Smalltalk就可以了。但是JavaScript的纯对象方法使这变得简单多了——如果您喜欢进行元编程,这将是一个真正的好处。

结论

对象.创建提供了一种更具吸引力的方法来使用原型,但仍然有一些怪癖。您必须实现自己的getter/setter、init和extend方法,才能在较旧的浏览器上工作。这些缺点最终会消失,当你放弃垫片和蓬勃发展的真正ECMAScript5-你已经可以这样做,如果IE8和以下是不关心你。在此之前,您可以使用对象.创建. 它可能比你想象的更快成为标准方法。

Marco Emrich于1993年开始专业软件开发,并使用了许多不同的语言和技术。他拥有计算机科学(德语)学位。并为弗劳恩霍夫IESE研究所做了一些关于生成编程的研究。目前,他受雇于德国IT培训中心“网站管理员akademie”。在那里,他担任作家、培训师、软件开发人员和项目经理。他也喜欢为了好玩而编写代码,比如业余时间的项目幻想-卡片.net是的。

作者介绍

用微信扫一扫

收藏