原文:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#constructorpatternjavascript
在经典的基于对象的编程语言中,constructor 是一个特殊的方法,用于为新建对象在分配到内存后进行初始化工作。在 JavaScript 中,几乎所有东西都是对象,所以我们最感兴趣的是对象的构造器。
对象构造器用于创建特定类型的对象:它既提供对象以备使用,也可以通过参数的方式为在对象首次创建时初始化成员属性和方法提供的值来源。
创建对象
以下是 JavaScript 中常用的三种新建对象的方式:
// 下面每个操作都会新建一个空对象var newObject = {};// 或者var newObject = Object.create(Object.prototype);// 或者var newObject = new Object();
在最后一个例子中,对象构造器用于为传入的特定的值创建一个包装对象,或者在没有值传入时生成一个空对象并返回。
我们可以通过下面四种方式来为对象添加属性和值:
// 兼容 ECMAScript 3 方法// 1. 点语法// 设置属性newObject.somekey = "hello world";// 读取属性var value = newObject.someKey;// 2. 方括号语法// 设置属性newObject["someKey"] = "Hello World";// 读取属性var value = newObject["someKey"];// 兼容 ECMAScript 5 的方法// 详细信息可以通过 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: value,writable: true,enumerable: true,configurable: true};Object.defineProperty(ojb, key, config);}// 为了使用, 我们创建一个名为 “person” 的空对象var person = Object.create( Object.prototype );// 为这个对象填充属性defineProp( person, "car", "Delorean" );defineProp( person, "dateOfBirth", "1981" );defineProp( person, "hasBeard", false );console.log(person);// Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false}// 4. Object.defineProperties// 设置属性Object.defineProperties(newObject, {"somekey": {value: "hello world",writable: true},"anotherKey": {value: "Foo Bar",writable: false}});// 可以用1、2方式来读取3、4方式设置的属性
正如我们本书后文将看到,这些方法甚至可以用于继承,方式如下:
// 用法:// 创建一个赛车手继承自 person 对象var driver = Object.create(person);// 为赛车手设置一些属性defineProp(driver, "topSpeed", "100mph");// 获取继承来的属性 (1981)console.log(driver.dateOfBrith);// 或者我们设置的属性(100mph)console.log(driver.topSeed);
构造器基本用法
正如我们前面看到的,JavaScript 不支持 class 的概念,但是它支持处理对象的特殊构造函数。简单的在构造函数的调用前加上一个 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() );
以上就是构造器模式 的一个简单版本,但它还存在一些问题。一个就是它会让继承变得困难,另一个就是像 toString() 这样的函数会在 Car 构造器实例出的对象中重复定义。这不是最理想的状态,因为函数应该可以在所有的 Car 的实例间共享才对。
得益于一系列 ES3 和 ES5 提供的构造对象的方式,使得这个限制不在重要。
基于原型的构造器
函数,就像其他 JavaScript 中的对象一样,包含了一个 prototype 对象。当我们调用 JavaScript 构造器创建对象时,新对象能够访问到对应构造器原型对象所有的属性。按这种方式,就能创建出多个访问同一原型对象的 car 对象。我们可以把原来的实例改造成这样:
function Car(model, year, miles) {this.model = model;this.year = year;this.miles = miles;}// 注意这里我使用的是 Object.prototype.newMethod// 而不是Object.prototypeCar.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 对象间共享。
