原文:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#constructorpatternjavascript

在经典的基于对象的编程语言中,constructor 是一个特殊的方法,用于为新建对象在分配到内存后进行初始化工作。在 JavaScript 中,几乎所有东西都是对象,所以我们最感兴趣的是对象的构造器。

对象构造器用于创建特定类型的对象:它既提供对象以备使用,也可以通过参数的方式为在对象首次创建时初始化成员属性和方法提供的值来源。

创建对象

以下是 JavaScript 中常用的三种新建对象的方式:

  1. // 下面每个操作都会新建一个空对象
  2. var newObject = {};
  3. // 或者
  4. var newObject = Object.create(Object.prototype);
  5. // 或者
  6. var newObject = new Object();

在最后一个例子中,对象构造器用于为传入的特定的值创建一个包装对象,或者在没有值传入时生成一个空对象并返回。

我们可以通过下面四种方式来为对象添加属性和值:

  1. // 兼容 ECMAScript 3 方法
  2. // 1. 点语法
  3. // 设置属性
  4. newObject.somekey = "hello world";
  5. // 读取属性
  6. var value = newObject.someKey;
  7. // 2. 方括号语法
  8. // 设置属性
  9. newObject["someKey"] = "Hello World";
  10. // 读取属性
  11. var value = newObject["someKey"];
  12. // 兼容 ECMAScript 5 的方法
  13. // 详细信息可以通过 http://kangax.github.com/es5-compat-table/ 了解
  14. // 3. Object.defineProperty
  15. // 设置属性
  16. Object.defineProperty(newObject, "someKey", {
  17. value: "for more control of the property's behavior",
  18. writable: true,
  19. enumerable: true,
  20. configurable: true
  21. });
  22. // 如果上面的形式阅读比较困难,可以使用以下简写形式
  23. var defineProp = function(obj, key, value) {
  24. var config = {
  25. value: value,
  26. writable: true,
  27. enumerable: true,
  28. configurable: true
  29. };
  30. Object.defineProperty(ojb, key, config);
  31. }
  32. // 为了使用, 我们创建一个名为 “person” 的空对象
  33. var person = Object.create( Object.prototype );
  34. // 为这个对象填充属性
  35. defineProp( person, "car", "Delorean" );
  36. defineProp( person, "dateOfBirth", "1981" );
  37. defineProp( person, "hasBeard", false );
  38. console.log(person);
  39. // Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false}
  40. // 4. Object.defineProperties
  41. // 设置属性
  42. Object.defineProperties(newObject, {
  43. "somekey": {
  44. value: "hello world",
  45. writable: true
  46. },
  47. "anotherKey": {
  48. value: "Foo Bar",
  49. writable: false
  50. }
  51. });
  52. // 可以用1、2方式来读取3、4方式设置的属性

正如我们本书后文将看到,这些方法甚至可以用于继承,方式如下:

  1. // 用法:
  2. // 创建一个赛车手继承自 person 对象
  3. var driver = Object.create(person);
  4. // 为赛车手设置一些属性
  5. defineProp(driver, "topSpeed", "100mph");
  6. // 获取继承来的属性 (1981)
  7. console.log(driver.dateOfBrith);
  8. // 或者我们设置的属性(100mph)
  9. console.log(driver.topSeed);

构造器基本用法

正如我们前面看到的,JavaScript 不支持 class 的概念,但是它支持处理对象的特殊构造函数。简单的在构造函数的调用前加上一个 new 关键词,我们就可以告诉 JavaScript 我们希望这个函数作为构造器来使用,并用该函数成员实例化一个新对象。

在构造器内,关键词 this 指向的是刚被创建的新对象。回顾一下对象的创建方式,一个基本的构造器大致是这样的:

  1. function Car(model, year, miles) {
  2. this.model = model;
  3. this.year = year;
  4. this.miles = miles;
  5. this.toString = function() {
  6. return this.model + "has done " + this.miles + " miles";
  7. };
  8. }
  9. // 用法
  10. // 我们创建一个 car 的实例
  11. var civic = new Car( "Honda Civic", 2009, 20000 );
  12. var mondeo = new Car( "Ford Mondeo", 2010, 5000 );
  13. // 打开我们的浏览器控制台来查看这些对象的 toString() 方法调用结果
  14. console.log( civic.toString() );
  15. console.log( mondeo.toString() );

以上就是构造器模式 的一个简单版本,但它还存在一些问题。一个就是它会让继承变得困难,另一个就是像 toString() 这样的函数会在 Car 构造器实例出的对象中重复定义。这不是最理想的状态,因为函数应该可以在所有的 Car 的实例间共享才对。

得益于一系列 ES3 和 ES5 提供的构造对象的方式,使得这个限制不在重要。

基于原型的构造器

函数,就像其他 JavaScript 中的对象一样,包含了一个 prototype 对象。当我们调用 JavaScript 构造器创建对象时,新对象能够访问到对应构造器原型对象所有的属性。按这种方式,就能创建出多个访问同一原型对象的 car 对象。我们可以把原来的实例改造成这样:

  1. function Car(model, year, miles) {
  2. this.model = model;
  3. this.year = year;
  4. this.miles = miles;
  5. }
  6. // 注意这里我使用的是 Object.prototype.newMethod
  7. // 而不是Object.prototype
  8. Car.prototype.toString = function() {
  9. return this.model + " has done " + this.miles + " miles";
  10. };
  11. // 使用
  12. var civic = new Car( "Honda Civic", 2009, 20000 );
  13. var mondeo = new Car( "Ford Mondeo", 2010, 5000 );
  14. console.log( civic.toString() );
  15. console.log( mondeo.toString() );

上面的例子中,toString() 的单个实例将在所有 car 对象间共享。