本文整理自MVC的JavaScript Web富应用开发,欢迎购买原版书籍
一、MVC是一种设计模式
4、类的概念
JavaScript 中并没有真正的类,但JavaScript 中有构造函数和new 运算符。构造函数用来给实例对象初始化属性和值。任何JavaScript 函数都可以用做构造函数,构造函数必须使用new 运算符作为前缀来创建新的实例。
new 运算符改变了函数的执行上下文,同时改变了return 语句的行为。实际上,使用new和构造函数和传统的实现了类的语言中的使用方法是很类似的:
var Person = function(name) {
this.name = name;
};
// 实例化一个Person
var alice = new Person('alice');
// 检查这个实例
assert( alice instanceof Person );
要不然就返回任意非原始类型的值。比如,我们可以返回一个用以新建一个新类的函数,第一步要做的是创建自己的类模拟库:
var Class=function(){
var klass=function(){
this.init.apply(this,arguments);
}
klass.prototype.inti=function(){};
return klass;
}
var Person=new Class; //具体参见new的用法 此时的Person等于的是一个function
Peson.prototype.init=function(){
//基于Person的实例做初始化
}
5、在JavaScript中,在构造函数中给类添加函数和给对象添加属性是一模一样
Person.find=functon(){};
var person=Perons.find();
要想给构造函数添加实例函数,则需要用到构造函数的prototype
Person.prototype.breath=function(){};
var person=new Person();
person.breath();
一般常用的模式是给类的prototype起一个别名,fn
Person.fn=Person.proptype;
Person.fn.run=function(){ };
6、给类库添加方法
直接给类设置属性和设置其静态成员是等价的:
var Person = new Class;
// 直接给类添加静态方法
Person.find = function(id){ /* ... / };
// 这样我们可以直接调用它们
var person = Person.find(1);
给类的原型设置的属性在类的实例中也是可用的:
var Person = new Class;
// 在原型中定义函数
Person.prototype.save = function(){ / ... */ };
// 这样就可以在实例中调用它们
var person = new Person;
person.save();
我们采用另外一种不同的方法来给类添加属性,这里用到了两个函数extend和include
var class=function(){
var klass=function(){
this.init.apply(this,arguments);
};
klass.prototype.init=function(){};
//定义prototype别名
klass.fn=klass.prototype;
//定义类的别名
klass.fn.parent=klass;
//给类添加属性
klass.extend=function(obj){
var extended=obj.extended;
for(var i in obj){
klass[i]=obj[i];
}
if(extended) extended(klass);
};
//给类添加属性
klass.inclue=function(obj){
var inclued=obj.inclued;
for(var i in obj){
klass.fn[i]=obj[i];
}
if(inclued) inclued(klass);
};
}
var Person=new Class;
Person.extend({
find:function(id){}
exists:functions(id){}
});
var person=Person.find(1);
include() 函数的工作原理也是一样的,只不过不是将属性复制至类中,而是复制至类的原型中。换句话说,这里的属性是类实例的属性,而不是类的静态属性。
var Person=new Class;
Person.include({
save:function(){},
save:functions(id){}
})
var person=new Person;
person.save();
7、基于原型的类继承
如果你给Array.prototype 添加了属性,那么所有的JavaScript 数组都具有了这些属性。代码如下:
var Animal=function(){};
Animation.prototype.breath=function(){
console.log("breath");
}
var Dog=function(){};
//通过原型基础
Dog.prototype=new Animal;
Dog.prototype.wag=function(){
console.log("wag')
}
实例
var dog=new Dog();
dog.breath();
dog.wag();
给“类”库添加继承
现在来给我们自定义的“类”库添加继承,我们通过传入一个可选的父类来创建新类:
var Class=function(){
var klass=funtion(){
this.init.apply(this,arguments);
};
//改变class原型
if(parent){
var subclass=function(){};
subclass.prototype=parent.prototype;
klass.prototype=new subclass();
};
kclass.prototype.inti=function(){};
//定义别名
klass.fn=klass.prototype;
kclass.fn.parent=klass;
kclass._super=kclass.__prototype__;
/* include/extend 相关的代码…… */
return klass;
}
var Class=function(){
var klass=funtion(){
this.init.apply(this,arguments);
};
//改变class原型
if(parent){
var subclass=function(){};
subclass.prototype=parent.prototype;
klass.prototype=new subclass();
};
kclass.prototype.inti=function(){};
//定义别名
klass.fn=klass.prototype;
kclass.fn.parent=kclass;
kclass._super=kclass.__prototype__;
/* include/extend 相关的代码…… */
klass.extend=function(obj){
var extended=obj.extended;
for(var i in obj){
klass[i]=obj[i];
}
if(extended) extended(klass);
};
//给类添加属性
klass.inclue=function(obj){
var inclued=obj.inclued;
for(var i in obj){
klass.fn[i]=obj[i];
}
if(inclued) inclued(klass);
};
return klass;
}
8、函数调用
在javascript中,函数和其他东西都是对象,然而函数可调用。
jQuery 在其API 的实现中就利用了apply() 和call() 来更改上下文,比如在事件处理程序中或者使用each() 来做迭代时。起初这很让人费解,一旦你理解了就会发现它非常有用:
$('.clicky’).click(function(){
// ‘this’指向当前节点
$(this).hide();
});
$('p’).each(function(){
// ‘this’指向本次迭代
$(this).remove();
});
为了访问原始上下文,可以将this的值存入一个局部变量中,这是一个常见的模式;
var clicky=function(){
wasClicked:function(){};
addListeners:function(){
var self=this;
$('.cicky').click(
fuction(){
sele.wasCliked();
};
)
}
}
clicky.addListeners();
然而,我们可以使用apply 来将这段代码变得更干净一些,通过将回调包装在另外一个匿名函数中,来保持原始的上下文:
var proxy=function(func,thisObject){
return (
function (){
return func.apply(thisObject,arguments);//改变func的执行对象
}
)
}
var clicky={
wasClicked:function{};
addListener:function(){
var self=this;
$('.clicky').click(proxy(this.wasClicked,this));
}
}
var App {
log: function(){
if (typeof console == "undefined") return;
// 将参数转换为合适的数组
var args = jQuery.makeArray(arguments);
// 插入一个新的参数
args.unshift("(App)");
// 委托给console
console.log.apply(console, args);
}
};
9、控制类库的作用域
我们在类和实例中都添加proxy 函数,这样就可以在事件处理程序之外处理函数的时候和下面这段代码所示的场景中保持类的作用域:
var Class=function(parent){
var klass=function(){
this.init.apply(this,argument)
};
klass.prototype.init=function(){};
klass.fn=klass.prototype;
klass.fn.parent=ckass;
//添加一个poxy函数
klass.proxy=function(func){
var self=this;
return (function(){
return func.apply(self,arguments)
})
}
//在实例中冶添加
klass.prototype=klass.proxy;
return klass;
}
现在使用proxy函数来包装函数,以包装在作用域中正确调用
var Button=new Class();
Button.include({
init:function(){
this.element=jQuery(element);
//代理click函数
this.element.click(this.proxy(this.click));
//this.element.click(this.click.apply(this.argument))
},
click:function(){}
});
9、添加私有函数
通过创建匿名函数的方式来创建私有作用域;
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");