前言

代码约定

两种常见的断言

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;
    };
}