[翻译]看图学Javascript Part II

第一篇使用图像描述Javascript语法的文章很受欢迎,所以我决定使用这个方法进行一些更加高级的想法。在这片文章里,我将会解释创建对象的三个常用技术。它们是使用原型的构造函数,纯原型以及对象工厂。

我的目标是想帮助大家明白每种技术的优点和弱点,并且明白它们具体是怎样运作的。

经典的Javascript构造函数(Classical JavaScript Constructors)

首先,我们用原型创建一个简单的构造函数。这是在原生的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();
};
现在我们定义一个继承自Rectangle叫Square的类。要进行继承,构造函数的原型必须继承自父构造函数的原型。这里我们重写getPerimeter让它稍微高效点,同时也看看怎样重写一个函数。
function Square(side) {
  this.width = side;
  this.height = side;
}
Square.prototype.__proto__ = Rectangle.prototype;
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=20
Square a=25 p=20
下面是结果的数据结构,虚线表示对象的继承。



注意到rect实例跟Square.prototype没啥不同。它们都是从Rectangle.prototype继承而来的简单对象。当你深入了解Javascript你会发现它只是一系列对象的连接。唯一比较特别的对象就是函数,它们可以接受参数和含有可执行的代码,并把它指向作用域。

纯原型对象(Pure Prototypal Objects)

让我们举些不使用构造函数的例子。这次我们只使用纯原型实现。

我们定义一个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 = {
  name: "Square",
  getArea: function getArea() {
    return this.width * this.width;
  },
  getPerimeter: function getPerimeter() {
    return this.width * 4;
  },
};
Square.__proto__ = Rectangle;
要创建这些原型的实例,我们只需要简单的创建继承自这些原型对象的新对象,然后设置它们的自己的属性。
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=20
Square a=25 p=20
下面是结果的对象图。



这个方法没有构造函数+原型的方法强大,但是它比较容易理解,因为它使用的继承比较少。而且如果你之前的语言也是使用这种纯原型的方式实现的话,那么你应该会觉得高兴Javascript也能这么干。

对象工厂(Object Factories)

我最喜欢的其中一个创建对象的方法就是使用工厂方法。它的不同之处在于它不用创建一个包含共享函数的原型对象然后创建实例,我只需要每次简单的调用一个函数然后返回一个新对象就行了。 这个例子是一个超级简单的MVC架构。Controller函数把Model跟View作为参数传入然后输出一个新的Controller对象。所有的状态都通过作用域保存在一个闭包里。
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)而不必先把函数绑定到对象上。而且它(函数)并没有在内部使用this,所以没有必要烦恼this的值这个问题了。
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);
输出
View now has 5
View now has 6
View now has 5
Saving value 5 somewhere
Now hiding view
下面是这段代码的结果的对象图。注意到我们通过函数的隐藏[scope]属性访问了两个传入的匿名对象,换句话说,我们访问了用工厂方法创建的闭包里的Model和View。


总结

我没有更多想要说的了,但是我想保持这篇文章短小精悍。如果有需要的话,我会写第三篇讲解如何写ruby式的Mixin以及其他进阶的话题。

原文:http://howtonode.org/object-graphs-2

同时发表在:http://www.icyfire.me/2011/10/object-graphs-2/

Translated by icyfire @ 2011-10-31
posted @ 2011-11-02 15:30  icyfire  阅读(329)  评论(0编辑  收藏  举报