JavaScript构造器模式

197 阅读4分钟

创作声明:如需转载,请注明出处

本文首发于知乎专栏

前言

最近在学习 JavaScript 的设计模式,在通过人民邮电出版社出版的《JavaScript设计模式》([美] Addy Osmani 著)学习的过程中,发现书中所译不仅没有体现原文的精华,而且还经常使我迷惑。

对此,我把视线投入到了网络,找到了 W3Cschool的译本,然而也发现其中有不少地方翻译的让人很迷惑,这迫使我不得不去看原著,只是没想到原著也如此难找,最终在某个犄角旮瘩找到了。

Learning JavaScript Design Patterns

下文是由本人逐字逐句研读原著过程中,根据自己的理解记录下的翻译。当然在研读的过程中也参考了 W3Cschool 的译本和人民邮电出版社出版的《JavaScript设计模式》。若有翻译不到之处,希望大家可以在评论中指正。

构造器模式

在面向对象编程中,构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。在 JavaScript 中一切皆对象。 对象构造器是被用来创建特殊类型的对象的,首先它要准备使用的对象,其次在对象初次被创建时,通过接收参数,构造器要用来对成员的属性和方法进行赋值。

对象创建

创建对象的三种基本方式:

var newObject = {};

// or
var newObject = Object.create( null );

// or
var newObject = new Object();

在 Object 构造器为特定的值创建对象封装,或者没有传递值时,它将创建一个空对象并返回它。

将一个键值对赋给一个对象的四种方式:

// ECMAScript 3 兼容形式

// 1. “点号”法

// 设置属性
newObject.someKey = "Hello World";

// 获取属性
var key = newObject.someKey;

// 2. “方括号”法

// 设置属性
newObject["someKey"] = "Hello World";

// 获取属性
var key = newObject["someKey"];

// ECMAScript 5 仅兼容性形式
// For more information see: http://kangax.github.com/es5-compat-table/

// 3. Object.defineProperty方式

// 设置属性
Object.defineProperty( newObject, "someKey", {
    value: "for more control of the property's behavior",
    writable: true,
    enumerable: true,
    configurable: true
});

// 如果上面的方式你感到难以阅读,可以简短的写成下面这样:

var defineProp = function ( obj, key, value ){
    var config = {
        value
    }
  Object.defineProperty( obj, key, config );
};

// 为了使用它,我们要创建一个“person”对象
var person = Object.create( null );

// 用属性构造对象
defineProp( person, "car",  "Delorean" );
defineProp( person, "dateOfBirth", "1981" );
defineProp( person, "hasBeard", false );

// 还可以创建一个继承于Person的赛车司机
var driver = Object.create( person );

// 设置司机的属性
defineProp(driver, "topSpeed", "100mph");

// 获取继承的属性 (1981)
console.log( driver.dateOfBirth );

// 获取我们设置的属性 (100mph)
console.log( driver.topSpeed );

// 4. Object.defineProperties方式

// 设置属性
Object.defineProperties( newObject, {

  "someKey": { 
    value: "Hello World", 
    writable: true 
  },

  "anotherKey": { 
    value: "Foo bar", 
    writable: false 
  } 

});
// 3和4中的读取属行可用1和2中的任意一种

基础构造器

正如我们先前所看到的,Javascript 不支持类的概念,但它有一种与对象一起工作的构造器函数。使用 new 关键字来调用该函数,我们可以告诉 Javascript 把这个函数当做一个构造器来用,它可以用自己所定义的成员来初始化一个对象。

在这个构造器内部,关键字 this 引用到刚被创建的对象。回到对象创建,一个基本的构造函数看起来像这样:

function Car( model, year, miles ) {

  this.model = model;
  this.year = year;
  this.miles = miles;

  this.toString = function () {
    return this.model + " has done " + this.miles + " miles";
  };
}

// 使用:
// 我们可以实例化一个Car
var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );

// 打开浏览器控制台查看这些对象toString()方法的输出值
console.log( civic.toString() );
console.log( mondeo.toString() );

上述是个简单版本的构造器模式,但它有两个问题:

  1. 难以继承
  2. 每个 Car 构造函数创建的对象中,toString() 之类的函数都被重新定义

这不是非常好,理想的情况是所有 Car 类型的对象都应该引用同一个函数

使用“原型( prototype )”的构造器

在 Javascript 中函数有一个 prototype 的属性。当我们调用 Javascript 的构造器创建一个对象时,构造函数 prototype 上的属性对于所创建的对象来说都能被访问和调用

function Car( model, year, miles ) {

  this.model = model;
  this.year = year;
  this.miles = miles;

}

// 注意这里我们使用 Object.prototype.newMethod 而不是 Object.prototype ,以避免我们重新定义原型对象
Car.prototype.toString = function () {
  return this.model + " has done " + this.miles + " miles";
};

// 使用:

var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );

console.log( civic.toString() );
console.log( mondeo.toString() );

通过上面代码,单个 toString() 实例被所有的 Car 对象所共享了。