控制“类”库的作用域
在类和实例中都添加proxy
函数,可以在事件处理程序之外处理函数的时候保持类的作用域。下面是不用proxy
的办法:
var Class = function(parent){
var klass = function(){
this.init.apply(this, arguments);
};
klass.prototype.init = function(){};
klass.fn = klass.prototype;
// 添加一个proxy 函数
klass.proxy = function(func){
var self = this;
return(function(){
return func.apply(self, arguments);
});
}
// 在实例中也添加这个函数
klass.fn.proxy = klass.proxy;
return klass;
};
下面是用proxy()
函数来包装函数,以确保它们在正确的作用域中被调用:
var Button = new Class;
Button.include({
init: function(element){
this.element = jQuery(element);
// 代理了这个click 函数
this.element.click(this.proxy(this.click));
},
click: function(){ /* ... */ }
});
如果没有使用proxy
将click()
的回调包装起来,它就会基于上下文this.element
来调用,而不是Button
,这会造成各种问题。在新版本的JavaScript——ECMAScript 5(ES5)——中同样加入了bind()
函数用以控制调用的作用域。bind()
基于函数进行调用的,用来确保函数是在指定的this
值所在的上下文中调用的。例如:
Button.include({
init: function(element){
this.element = jQuery(element);
// 绑定这个click 函数
this.element.click(this.click.bind(this));
},
click: function(){ /* ... */ }
});
这个例子和proxy()
函数是等价的,它能确保click()
函数基于正确的上下文进行调用。老版本的浏览器不支持bind()
,但可以手动实现它,兼容性也不错,直接扩展相关对象的原型,这样就可以像在ES5 中使用bind()
,在任意浏览器中调用它。下面是一段实现了bind()
函数的代码:
if (!Function.prototype.bind) {
Function.prototype.bind = function (obj) {
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () {},
bound = function () {
return self.apply( this instanceof nop ? this : (obj || {}),
args.concat(slice.call(arguments)));
};
nop.prototype = self.prototype;
bound.prototype = new nop();
return bound;
};
}
如果浏览器原生不支持bind(),则仅仅重写了Function 的原型。现代浏览器则可以继续使用内置的实现。
对于数组来说这种“打补丁”式的做法非常有用,因为在新版本的JavaScript 中,数组增加了很多新的特性。可以使用es5-shi项目,它涵盖了ES5 中新增的尽可能多的特性。
添加私有函数
目前上面为“类”库添加的属性都是“公开的”,可以被随时修改。关于给“类”添加私有属性,JavaScript支持不可变属性,但在主流浏览器中并未实现,没办法直接利用这个特性。可以利用JavaScript 匿名函数来创建私有作用域,这些私有作用域只能在内部访问:
var Person = function(){};
(function(){
var findById = function(){ /* ... */ };
Person.find = function(id){
if (typeof id == "integer")
return findById(id);
};
})();
上面的代码将类的属性都包装进一个匿名函数中,然后创建了局部变量(findById),这些局部变量只能在当前作用域中被访问到。Person
变量是在全局作用域中定义的,可以在任何地方都能访问到。定义变量的时候不要丢掉var
运算符,如果丢掉var
就会创建全局变量。如果需要定义全局变量,可以在全局作用域中定义,或者定义为window
的属性:
(function(exports){
var foo = "bar";
// 将变量暴露出去
exports.foo = foo;
})(window);
assertEqual(foo, "bar");
“类”库
jQuery 本身并不支持类,但通过插件的方式可以轻易引入类的支持,比如HJS。HJS 允许通过给$.Class.create
传入一组属性来定义类:
var Person = $.Class.create({
// 构造函数
initialize: function(name) {
this.name = name;
}
});
可以在创建类的时候传入父类作为参数,这样就实现了类的继承:
var Student = $.Class.create(Person, {
price: function() { /* ... */ }
});
var alex = new Student("Alex");
alex.pay();
可以直接给类挂载属性:
Person.find = function(id){ /* ... */ };
HJS 的API 中同样包含一些工具函数,比如clone() 和equal() :
var alex = new Student("Alex");
var bill = alex.clone();
assert( alex.equal(bill) );
还有 Spine 同样实现了类,通过直接在页面中引入spine.js(http://maccman.github.com/spine/spine.js)来使用它:
<script src="http://maccman.github.com/spine/spine.js"> </script>
<script>
var Person = Spine.Class.create();
Person.extend({
find: function() { /* ... */ }
});
Person.include({
init: function(atts){
this.attributes = atts || {};
}
});
var person = Person.init();
</script>
jQuery 的作者John Resig 在他的博客中写过一篇文章:专门讲解如何实现经典的类继承。
公开记录学习JS MVC,不知道能坚持多久= =。以《基于MVC的JavaScript web富应用开发》为主要学习资料。类的部分终于看完了,再慢慢消化。JS真是博大精深!