前言
代码约定
两种常见的断言
1、判断一个值是否为真
var assert = function(value,msg){
if(!value){
throw(msg || (value + '不是真值!'));
}
}
2、判断值一是否等于值二
var assertEqual = function(val1,val2,msg){
if(val1 !== val2){
throw(msg || (val1 + '不等于' + val2 + ';'));
}
}
第一章MVC和类
1、什么是MVC
MVC 是一种设计模式,它将应用划分为3 个部分:数据(模型)、展现层(视图)和用户交互层(控制器)。换句话说,一个事件的发生是这样的过程:
1. 用户和应用产生交互。
2. 控制器的事件处理器被触发。
3. 控制器从模型中请求数据,并将其交给视图。
4. 视图将数据呈现给用户。
模型
模型用来存放应用的所有数据对象。比如,可能有一个User 模型,用以存放用户列表、他们的属性及所有与模型有关的逻辑。
模型不必知晓视图和控制器的细节,模型只需包含数据及直接和这些数据相关的逻辑。任何事件处理代码、视图模板,以及那些和模型无关的逻辑都应当隔离在模型之外。将模型和视图的代码混在一起,是违反MVC 架构原则的。模型是最应该从你的应用中解耦出来的部分。
视图
视图层是呈现给用户的,用户与之产生交互。在JavaScript 应用中,视图大都是由HTML、CSS 和JavaScript 模板组成的。除了模板中简单的条件语句之外,视图不应当包含任何其他逻辑。
控制器
控制器是模型和视图之间的纽带。控制器从视图获得事件和输入,对它们进行处理(很可能包含模型),并相应地更新视图。当页面加载时,控制器会给视图添加事件监听,比如监听表单提交或按钮点击。然后,当用户和你的应用产生交互时,控制器中的事件触发器就开始工作了。
例子如下:不用使用类库和框架也能实现控制器,下面这个例子就是使用简单的jQuery 代码来实现的:
var Controllers = {};
//使用匿名函数封装作用域,避免对全局作用域造成污染
(Controllers.users = function(){
var nameClick = function(){
alert(1);
};
//页面加载后执行
$(function(){
$('#view .name').click(nameClick);
})
})(jQuery)
创建类
JavaScript 中并没有真正的类,但JavaScript 中有构造函数和new 运算符。构造函数用来给实例对象初始化属性和值。任何JavaScript 函数都可以用做构造函数,构造函数必须使用new 运算符作为前缀来创建新的实例。
new 运算符改变了函数的执行上下文,同时改变了return 语句的行为。实际上,使用new和构造函数和传统的实现了类的语言中的使用方法是很类似的:
var Person = function(name){
this.name = name;
}
var xiaom = new Person('xiaoming');
assert(xiaom instanceof Person);
构造函数的命名通常使用驼峰命名法,首字母大写,以此和普通的函数区分开来,这是一种习惯用法。记住这一点非常重要,因为你不会希望用省略new 前缀的方式来调用构造函数。
第一步:创建自己的类模拟库
var Class = function(){
var Klass = function(){
this.init.apply(this,arguments);
}
Klass.prototype.init = function(){};
return Klass;
}
var Person = new Class;
Person.prototype.init = function(){
alert(1);
}
var person = new Person;
//js new关键字省略括号和不省略括号是一样的,浏览器解析时候会自动补全
下一步给类添加静态方法和实例方法
var Class = function(){
var Klass = function(){
this.init.apply(this,arguments);
}
Klass.prototype.init = function(){};
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.include = function(obj){//添加到原型里
var included = obj.included;//添加回调
for(var i in obj){
Klass.fn[i] = obj[i];
}
if(included){//如果有回调则执行
included(Klass);
}
}
return Klass;
}
var Person = new Class;
Person.include({
save: function(id) { /* ... */ },
destroy: functions(id) { /* ... */ }
});
var person = new Person;
person.save();
Person.extend({
extended: function(klass) {
console.log(klass, "执行了回调");//回调
}
});
基于原型的类继承
JavaScript 是基于原型的编程语言,原型用来区别类和实例,这里提到一个概念:原型对象(prototypical object)。原型是一个“模板”对象,它上面的属性被用做初始化一个新对象。任何对象都可以作为另一个对象的原型对象,以此来共享属性。实际上,可以将其理解为某种形式的继承。
当你读取一个对象的属性时,JavaScript 首先会在本地对象中查找这个属性,如果没有找到,JavaScript 开始在对象的原型中查找,若还未找到还会继续查找原型的原型,直到查找到Object.prototype。如果找到这个属性,则返回这个值,否则返回undefined。
给“类”库添加继承
代码如下:
var Class = function(parent){
var Klass = function(){
this.init.apply(this,arguments);//执行初始化
}
if(parent){
var subClass = function(){};
subClass.prototype = parent.prototype;
Klass.prototype = new subClass;//继承parent的方法
}
Klass.prototype.init = function(){};//默认初始化
Klass.fn = Klass.prototype;//定义别名
Klass.fn.parent = Klass;
/*
设置对象的__proto__ ;属性并不是所有浏览器都支持,类
似Super.js(http://github.com/maccman/super.js)的类库则通过属性复制的方式来解决这
个问题,而非通过固有的动态继承的方式来实现。
*/
Klass._super = Klass.__proto__;
Klass.extend = function(obj){
var extended = obj.extended;
for(var i in obj){
Klass[i] = obj[i];
}
if(extended){
extended(Klass);
}
}
Klass.include = function(obj){
var included = obj.included;
for(var i in obj){
Klass.fn[i] = obj[i];
}
if(included){
included(Klass);
}
}
return Klass;
}
用法
var Animal = new Class;
Animal.include({
breath: function(){
console.log('breath');
}
});
var Cat = new Class(Animal)
// 用法
var tommy = new Cat;
tommy.breath();
函数调用
我们可以使用apply来将代码变得更干净一些,通过将回调包装在另外一个匿名函数中,来保持原始的上下文。
var proxy = function(func,thisobject){
return (function(){
return func.apply(thisobject,arguments);
})
}
这和jQuery.proxy()的方法的是一样的。
使用apply() 和call() 还有其他很有用的原因,比如“委托”。我们可以将一个调用委托给另一个调用,甚至可以修改传入的参数:
var App = {
log:function(){
if(typeof console == 'undefined'){
return;
}
var args = jQuery.makeArray(arguments);//转换成合适的数组
args.unshift('(App)');
console.log.apply(console,args);
}
}
使用
App.log('1');//(App)1
控制“类”库的作用域
将上面的proxy()方法添加到我们的类库中,代码整理如下:
var Class = function(parent){
var Klass = function(){
this.init.apply(this,arguments);//执行初始化
}
if(parent){
var subClass = function(){};
subClass.prototype = parent.prototype;
Klass.prototype = new subClass;//继承parent的方法
}
Klass.prototype.init = function(){};//默认初始化
Klass.fn = Klass.prototype;//定义别名
Klass.proxy = function(func){//将代理方法添加到静态方法中
var self = this;
return (function(){
return func.apply(self,arguments);
});
}
Klass.fn.proxy = Klass.proxy;//在类的原型中也添加这个方法
Klass.fn.parent = Klass;
/*
设置对象的__proto__ ;属性并不是所有浏览器都支持,类
似Super.js(http://github.com/maccman/super.js)的类库则通过属性复制的方式来解决这
个问题,而非通过固有的动态继承的方式来实现。
*/
Klass._super = Klass.__proto__;
Klass.extend = function(obj){
var extended = obj.extended;
for(var i in obj){
Klass[i] = obj[i];
}
if(extended){
extended(Klass);
}
}
Klass.include = function(obj){
var included = obj.included;
for(var i in obj){
Klass.fn[i] = obj[i];
}
if(included){
included(Klass);
}
}
return Klass;
}
使用如下:
var c = new Button('body');
在新版本的JavaScript——ECMAScript5(ES5)——中同样加入了bind()函数用以控制调用的作用域。bind()是基于函数进行调用的,用来确保函数是在指定的this值所在的上下文中调用的。
但老版本的浏览器不支持bind(),幸运的是,如果需要的话可以手动实现它。对于老版本的浏览器来说,手动实现的bind() 兼容性也不错,直接扩展相关对象的原型,这样就可以像今天在ES5 中使用bind() 那样在任意浏览器中调用它。例如,下面就是一段实现了bind() 函数的代码:
if(!Function.prototype.bind){
Function.prototype.bind = function(obj){
var slice = [].slice;//将数组的slice方法赋值给一个变量
var args = slice.call(arguments,1);//将参数转换成一个args数组
var self = this;
var nop = function(){};
var 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;
};
}