网上看到一篇文章,虽然它讲的是一个叫javascriptMVC的框架内部的方法,但对我理解js的MVC模式还是很有帮助的,为防止以后访问不了,先抄下来:
$.Class为javascript模拟了继承的实现,他将jquery面向函数的编程和面向对象的编程衔接在一起。$.Class基于 John Resig 的Simple Class类库,除了原型继承外,他还有其他一些特性:静态继承、自描述(Introspection)、命名空间的支持、创建和初始化方法、更容易创建的回调函数。 构造函数 我们通过下面的方式生命一个类型: $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class Name{可选:字符串}:如果设置了,将会把它作为fullName和shortName的值,并添加相应的命名空间。 Static{可选:Object对象}:如果设置了,会为Class添加静态的属性和方法。 Prototype:{Object对象}:为Class添加原型的属性和方法。 当Class被创建的时候,静态的setup和init方法将会被执行。实例化一个类型的方式如下,并且原型的setup和init方法会在这一过程中被执行: new Class([args ... ]) -> instance 静态属性vs原型属性 在开始学习Class之前,弄清楚静态属性和原型属性的区别是很重要的,我们来看例子: //静态 MyClass.staticProperty //共享属性 //原型 myclass = new MyClass() myclass.prototypeMethod() //实例方法 这里的静态和原型属性和c#,java中的静态和原型属性在使用上比较相似,只是实现的机制不一样。静态属性是类对象(而不是他的实例)本身具有的属性,在$.Class中我们可以使用this.constructor.XXX的方式访问它,但是在传统的javascript编程中只能通过Class.Property的方式访问,就像上例;原型属性是从原型链上继承下来的,或者是实例对象本身具有的。 一个基本的类 下面的例子创建了一个名叫Monster的类。 $.Class('Monster', /* 这里定义的是静态属性 */ { count: 0 }, /* 这里定义的是原型属性 */ { init: function( name ) { // 将name变量保存到实例的name属性中 this.name = name; // 设置health为10 this.health = 10; // 使用 this.constructor.XXX 的方式可以访问到静态属性,这里自增count属性的值 this.constructor.count++; }, eat: function( smallChildren ){ this.health += smallChildren; }, fight: function() { this.health -= 2; } }); hydra = new Monster('hydra'); dragon = new Monster('dragon'); hydra.name // -> hydra Monster.count // -> 2 Monster.shortName // shortName是$.Class自带的一个静态属性 -> 'Monster' hydra.eat(2); // health = 12 dragon.fight(); // health = 8 原型属性中的init方法相当于构造函数,本次实例化对象的时候,都会执行。 继承 当一个类被别的类扩展之后,他的所有静态和原型属性都会被继承。当你重写一个函数的时候,你可以通过使用this._super()访问他在父类中的实现。我们来创建一个新类SeaMonster : Monster("SeaMonster",{ eat: function( smallChildren ) { this._super(smallChildren / 2); }, fight: function() { this.health -= 1; } }); lockNess = new SeaMonster('Lock Ness'); lockNess.eat(4); //health = 12 lockNess.fight(); //health = 11 静态属性的继承 你可以像这样实现静态属性的继承: $.Class("First", { staticMethod: function() { return 1;} },{}) First("Second",{ staticMethod: function() { return this._super()+1;} },{}) Second.staticMethod() // -> 2 命名空间 命名空间是个好东西,使用它可以把你的代码按功能分割,程序架构上更加合理;而且便于把代码移植到别的地方,而不用担心异常的产生。javascript本身不支持命名空间,所以只有采取一种变通的方式,例如我们可以使用单例模式。下面是$.Class中使用命名空间的例子: $.Class("MyNamespace.MyClass",{},{}); new MyNamespace.MyClass() 自描述(Introspection) $.Class为类提供了字符串的名称实现自我表述的功能,看下例: $.Class("MyOrg.MyClass",{},{}) MyOrg.MyClass.shortName //-> 'MyClass' MyOrg.MyClass.fullName //-> 'MyOrg.MyClass' fullName包括命名空间,shortName不包括命名空间,他们都是静态属性。 创建和初始化方法 $.Class提供了静态的和原型的初始化方法,以方便你完成类的创建,他们是setup和init。setup将会在init之前运行,一般情况下你不需要使用到他,但当你需要为init做一些复杂的工作,比如初始化init需要使用的参数时可以使用它。 $.Class("MyClass", { setup: function() {} //static setup init: function() {} //static constructor }, { setup: function() {} //prototype setup init: function() {} //prototype constructor }) 1.Setup: setup在init之前被调用,静态setup函数把基类的参数传递给扩展类,原型函数传递构造函数参数。如果setup返回数组,数组会作为init方法的参数,这样可以吧一些默认参数传递给init方法。你也可以再setup中放置经常要运行的代码。你不需要重写setup方法。 $.Class("jQuery.Controller",{ ... },{ setup: function( el, options ) { ... return [$(el), $.extend(true, this.Class.defaults, options || {} ) ] } }) 2.Init: init方法在setup之后运行,他们接收setup的结果作为参数。 $.Class("Foo", { init: function( arg1, arg2, arg3 ) { this.sum = arg1+arg2+arg3; } }) var foo = new Foo(1,2,3); foo.sum //-> 6 代理 和jquery提供的代理方法类似,他返回一个回调函数,this总是指向Class或者Class的实例。下面例子中使用this.proxy以确保this.name是指向Todo实例的,这样show是就可以得到正确的值。 $.Class("Todo",{ init: function( name ) { this.name = name }, get: function() { $.get("/stuff",this.proxy('show')) }, show: function( txt ) { alert(this.name+txt) } }) new Todo("Trash").get()
入门级js都是一些零散的function,然后搞一个start();方法,其内部执行所有页面loading后初始化要执行的方法。
然后一些牛人喜欢把自己的代码封装好了变成像插件一样可以重复利用,甚至还自定义标签。
但是,有一个我认为是误区的地方: 一个网站不可能处处都是封装好的插件,而你封装了自己的插件,并不代表其他部分的零散代码就可以没有思路清晰的结构。
就像《基于MVC的JavaScript Web富应用开发》一书中写的:
我在读一个非常喜欢封装的牛人的代码的时候常常会发现他封装得太多,导致后期维护修改的问题,连他自己也常常忘记自己写的是什么意思了。。。
于是有后台的强人跟我说,你不能学他这样,你应该去看看《设计模式》,学学MVC。
虽然还没有好好去看过这本书,不过已经开始依样画葫芦学人家重构我的JS了。
学习上篇 P. R. May 写的global.js的结构:
<head> <script type="text/javascript"> $(function(){Suli.init();}); </script> </head> var Suli = Suli || {};
Suli.init = function () {
new Suli.Functioname();
}
Suli.Functioname = function () {
api :{
init = function (){
} }, _fun = function ( ) {
}, api = { anything : function () { }, dosomething : function () { }, ... return api.init(); }
把相关功能都放到一个具有标志性的函数中,该函数具有一个api对象,在api的init方法中执行子方法,return含有init方法的对象,在这个函数被实例化的的同时它就成为了一个构造函数。因为JavaScript中,在构造函数中给类添加函数和给对象添加属性是一模一样的。
前面的书中还提到一个技巧:
Person.prototype.breath = function(){ /*...*/ };
var person = new Person;
Person.fn = Person.prototype;
Person.fn.run = function(){ /*...*/ };
这样比较方便给构造函数添加实例函数,并且给实例化以后的类添加属性和给构造函数添加属性是一模一样的,采用prototype。
事件的优化
$("ul li").click(function(){ /* ... */ });//给每个li添加监听,很浪费的做法
⇓
$("ul").delegate("li", "click", /* ... */);//通过委托,只会添加一个事件监听
自定义事件
通过bind绑定自定义事件
$(".class").bind("refresh.widget",function(){});
通过trigger触发自定义事件
$(".class").trigger("refresh.widget");
好的for循环模式是将已经遍历过的容器的长度缓存起来
for(var i=0, max = myarray.length; i <max; i++){ //对myarray[i]进行处理 }
对长度的值只提取一次,但应用到整个循环中。
for-in循环应该用来遍历非数组对象。使用for-in循环也被称为枚举(enumeration)。
从技术上来说,也可以做到遍历数组,但不推荐。
因为当数组对象已经被自定义函数扩大后,这样可能会导致逻辑错误。
因此推荐用for循环处理数组,for-in处理对象。
使用hasOwnProperty()过滤遇到原型链的属性。
不要增加js内置的原型
避免使用隐式类型转换 ==
比较时会执行隐式类型转换,应该永远采用===和!==来对数值和类型进行比较!
eval()是evil
该函数可以将任意字符串当作一个JavaScript代码来执行。
不是动态运行时产生的代码,没有理由需要eval(),动态运行时也有更好的替代方法。
//反模式 var property = "name"; alert(eval("obj." + property));
⇓
//推荐的方法 var property = "name"; alert(obj[property]);
eval()还存在Ajax请求时的安全隐患。
通过setInterval(),setTimeout(),function()等构造函数来传递参数的花,
大部分情况下,会导致累死eval()的隐患,应该避免。
setTimeout("myFunc()",1000); setTimeout("myFunc(1,2,3)",1000);
⇓
//推荐的模式 setTimeout(myFunc,1000); setTimeout(function(){ myFunc(1,2,3); },1000);
O'reilly 几本相关的书
《编写可维护的JavaScript》龟书
《JavaScript语言精粹(修订版)》蝶书
《JavaScript高效图形编程》羊书
《JavaScript设计模式》鹊书
《用AngularJS开发下一代Web应用》鱼书