图解学习JavaScript第二部分翻译

作者:kun10 发布时间:January 28, 2011 分类:JavaScript

图解学习JavaScript第二部分翻译



原文分三篇,第一篇是说一些JavaScript的基础特性,第二篇是说JavaScript向对象的一些思路,第三篇是把JavaScript和Ruby做了对比。
我比较在意的第二部分。翻译一下试试。
原文地址:
http://howtonode.org/object-graphs-2


图解学习之二



第一篇幅用图描述了Javascript的语法,这篇我会讲解三种JavaScript用于对象创建的技巧,分别是

  • 构造器+原型
  • 纯原型链
  • 对象工厂


我希望本文能够帮助人们理解每一个技巧的优点和缺点。


一、传统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();
};  


现在,我们再来创建一个新的类叫做Square,他继承于Rectangles。为此,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;
}; 


最后的调用也很简单,只要用new来创建一个实例。

var rect = new Rectangle(6, 4);
var sqr = new Square(5);
console.log(rect.toString())
console.log(sqr.toString()) 


传统构造器方式注意点:






对于上面的例子,注意rect实例和Square.prototype并不一样,虽然他们都是继承于Rectangle原型的简单对象,JavaScript的对象是一串链式的调用。
唯一特殊的对象是function,它们获取参数,并且掌握着内部执行的代码和作用域。


二、纯原型链式



我们依然来实现上面的例子,但我们这次不使用构造函数,这次我们只用原型链式继承。



我们先来定义一个Ractangle的原型作为我们所有对象的基础。

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


继续看图:






它并不如构造器+原型的方式来的强大,但是它直接易懂,如果你熟悉一个原型链式继承的语言,你会觉得很熟悉。


三、对象工厂



这是我很喜欢的方式————用一个工厂函数来创建对象的方式。它的不同在于,我只用简单的调用一个函数,它每次返回一个新的对象,而不是用一个原型对象供所有构造函数共享。



下面是一个很简单的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();
    }
  };
}


使用时只要简单的代入参数运行一下这个function。注意,现在我们不用先把工厂函数绑定给某个对象就可以直接传递函数给事件handlers(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


上图:






此图对应了上面的调用过程,注意,我们可以通过工厂函数的隐藏作用域获取到两个匿名对象,也就是说,我们可以从工厂函数的闭包里面获取到model和view。

批量添加和管理事件处理

作者:kun10 发布时间:January 22, 2011 分类:JavaScript

引入behavior对象


在做日常的过程中,释然为我们大家制定了一个behavior对象。
他的作用就是用来统一的管理页面里面需要的事件绑定。
我们先看一下这个behavior对象的调用:

behavior.add(JSON);
K.ready(function() {
    behavior.apply();
});
以上用于添加事件

JSON的格式如下:
{
    'selector': func,
    'selector': {
        'event1':func1,
        'event2':func2
    }
}


这样的一个调用使得我们可以专注于对函数功能的编写,而不用再过于关注对dom对象的事件绑定的逻辑。


在没有这个对象的之前,我们写事件处理很容易随自己的想法写一大片的功能代码。
就像这样。

E.on(ele, 'click', function(){
    //里面写满了各种各样的处理逻辑,长的不得了。    
});    


这样在写的时候会让我们觉得很爽,但是我们却不愿意回头再整理这样的代码。


原因:
里面的事件处理特别长,要是里面再调用点别处(甚至别的文件的代码),有时候读代码都费劲。
事件处理函数编写零散,尤其多人合作的时候代码更是可能写到不一样的地方,难以归宗管理。

behavior的实现



behavior的实现主要依据它要达到的功能。

首先它要对事件进行集体的管理、其次它要对dom元素进行批量的事件绑定。



所以大体可以这样,这里我们把behavior对象通过对象来实现。

behavior = {
    add: func(JSON){},
    _events: {},
    apply: func(){}
}


我们用_events来做事件函数列表管理,再用add来对dom元素进行统一的事件添加管理。


最后使用apply方法把事件和对应的元素对应绑上。


如上所说add方法的作用是给dom元素和事件函数做一个映射。它接受的参数是一个json对象。


我们先做一些约定:

  • 如果用户直接对元素绑定了函数,就表示dom元素载入完成时执行函数
  • 如果用户指定了元素触发函数的事件,就对该元素绑定事件。


约定behavior使用的JSON的格式如下:

{
    'selector': func,
    'selector': {
        'event1':func1,
        'event2':func2
    }
}    


下面我们来分析add方法,add方法就是处理json对象,并把找到的dom元素和事件函数做关联

add: function(obj){
    //that用来保存behavior的活动对象
    var that = this;
    //K表示KISSY对象
    //K.each是用来遍历一个对象的属性的方法
    //当然这个对象我们上面做了约定!
    //所以在解析它的时候我们可以写的简单一些
    //如果我们要接受更加随意的格式,就要在解析对象的容错
    //上面下更多的功夫
    K.each(obj, function(one, name) {
        var el = D.query(name);//找到符合的元素,这里的name就是传入的selector

        if (K.isFunction(one)) {
            events = {
                'load': one//直接写函数的话就是用
            }
        } else {
            //如果是对象,就直接赋给events
            events = one;
        }

        that._events = events;//关联的结果保存在_events里面
    });

    return that;
}


之后就是使用apply对象来集中的绑定元素事件,apply准备放在K.ready事件里面触发。

apply: function() {
    //KISSY.ready 不能传送作用域,所以不能使用 this
    var that = behavior;

    K.each(that._events, function(one, name) {
        var el = D.query(name);
        if (el.length > 0) {
            //真正要做绑定了,先找到要绑定的元素
            //把每一个函数绑定到元素上面
            //之前的one就保存了元素的事件和对应的函数
            K.each(one, function(fn, eventType) {
                 
                if('load' === eventType) {
                    //因为已经是domready了,直接运行函数
                    fn(el);
                } else {
                    //使用KISSY的Event来绑定事件函数    
                    E.on(el, eventType ,fn);
                }
            });
        }
    });
}    


这样我们可以实现对事件函数的统一管理和统一添加。
也可以专注于对事件处理的函数编写。
另外在事件函数内部,我们也应该对用得较多的功能块使用函数封装来复用。



本例里面使用了KISSY API的each方法,ready方法,KISSY的Event对象和Dom对象。了解更多KISSY点

  1. 1