原文地址 http://howtonode.org/object-graphs-2
第一篇文章用对象图描述JavaScript的语义,很受欢迎。我将继续用对象图描述一些高级的主题。这篇文章主要解释三种构造对象的方法,即原型,纯原型和对象工厂。
我的目标是帮助大家理解每个方法的优劣和真正的机制。
经典JavaScript构造器
首先我们创建一个简单的原型构造器。这个构造器和类比较相似。它是很强大和高效的,但是它和其他由类构造对象的语言完全不同。
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.getArea = function getArea() {
return this.width * this.height;
};
Rectangle.prototype.getPerimeter = function getPerimeter() {
return 2 * (this.width + this.height);
};
Rectangle.prototype.toString = function toString() {
return this.constructor.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
};
现在我们定义一个新的Squares对象继承Rectangles,为了实现继承,构造器prototype必须从父构造器的prototype继承。这里我们覆盖了getPerimeter函数。
function Square(side) {
this.width = side;
this.height = side;
}
// Make Square inherit from Rectangle
Square.prototype = Object.create(Rectangle.prototype, { constructor: { value: Square } });
// Override a method
Square.prototype.getPerimeter = function getPerimeter() {
return this.width * 4;
};
使用很简单。常见对象实例并调用每个函数。
var rect = new Rectangle(6, 4);
var sqr = new Square(5);
console.log(rect.toString())
console.log(sqr.toString())
输出Rectangle a=24 p=20Square a=25 p=20
下图是数据结构图。虚线表示对象继承。
请注意rect实例和Square.prototype有些许不同。他们都是从Rectangle.prototype派生的简单对象。JavaScript仅仅是一系列链接的对象。唯一特殊的对象是带参数并拥有执行代码的函数对象。
纯原型对象
下面我们给出一些不带构造函数的例子。我们将使用纯原型继承。
定义一个Rectangle原型。
var Rectangle = {
name: "Rectangle",
getArea: function getArea() {
return this.width * this.height;
},
getPerimeter: function getPerimeter() {
return 2 * (this.width + this.height);
},
toString: function toString() {
return this.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
}
};
接下来定义一个Square子对象覆盖一些属性来改变行为。
var Square = Object.create(Rectangle);
Square.name = "Square";
Square.getArea = function getArea() {
return this.width * this.width;
};
Square.getPerimeter = function getPerimeter() {
return this.width * 4;
};
创建其对象很简单,只需要手动设置一些局部状态。
var rect = Object.create(Rectangle);
rect.width = 6;
rect.height = 4;
var square = Object.create(Square);
square.width = 5;
console.log(rect.toString());
console.log(square.toString());
输出Rectangle a=24 p=20Square a=25 p=20
这是对象图。
这种方法通常不如第一个构造器+原型的方法强大,但是它因为指向比较少而很容易理解。而且如果你刚从原型继承的语言转换过来,你会发现JavaScript也可以实现。
对象工厂
创建对象最受欢迎的方法之一是使用工厂函数。区别在于以前使用共享函数产生原型对象再创建这些对象的实例,现在是调用一个函数来返回对象实例。
下面的例子是一个最简单的MVC系统,控制器函数接受model和view对象为参数并产生一个新的控制器对象。所有状态都通过作用域保存在闭包中。
function Controller(model, view) {
view.update(model.value);
return {
up: function onUp(evt) {
model.value++;
view.update(model.value);
},
down: function onDown(evt) {
model.value--;
view.update(model.value);
},
save: function onSave(evt) {
model.save();
view.close();
}
};
}
使用这个控制器,只需要简单传递参数并调用。注意我们可以直接使用它们作为事件处理器(setTimeout)而不需要绑定函数到对象上。
var on = Controller(
// Inline a mock model
{
value: 5,
save: function save() {
console.log("Saving value " + this.value + " somewhere");
}
},
// Inline a mock view
{
update: function update(newValue) {
console.log("View now has " + newValue);
},
close: function close() {
console.log("Now hiding view");
}
}
);
setTimeout(on.up, 100);
setTimeout(on.down, 200);
setTimeout(on.save, 300);
输出为
// Output
View now has 5
View now has 6
View now has 5
Saving value 5 somewhere
Now hiding view
下面是上面代码的对象图。注意我们从工厂函数产生的闭包中访问到model和view。
结论
我想写很多东西,但是我更想保持这系列文章的短小,如果有需要,我想写写ruby风格的模块和其他高级主题。