Giter Club home page Giter Club logo

util's Introduction

util

Class

  • 实现创建Class,支持扩展;

    1. 实现extend方法,给类添加方法
    2. 实现include方法,给实例添加方法
  • 实现继承,子类无法修改父类的引用属性:

    1. 值引用很容易通过,属性copy实现;
    2. 引用类型则需要深copy实现;
  • 实现_super,方便子类重写父类的方法

  • 指定init为初始化方法;

    1. 方便初始化
  • 保证继承关系

api

var Person = Class.extend({
    init:function(){
        //init
    },
    dance:function(){
        //dance
    }
});
//增加实例的方法
Person.include({
    included:function(){
        console.log("included...");
    },
    play:function(){
        //play
    }
});
//增加类的方法
Person.implement({
    implemented:function(){
        //implemented
    },
    type:"person"
});


var Ninja  = Person.extend({
    init:function() {
        //init
        _super();
    },
    dance:function(){
        //dance
        _super();
    }
});

Ninja.include({
    included:function(){
        //included
    },
    kongfu:function(){
        //kongfu
    },
    play:function(){//play方法

    }
});

console.log(Person.type);

var p = new Person();
p.dance();
p.play();

var ninja = new Ninja();
ninja.dance();
ninja.play();
ninja.kongfu();

//引用属性,判断属性改变的时候是否影响prototype的独立性

ninja.__proto__.play = function(){
    //play1
};

ninja.play();
p.play();
var n1 = new Ninja();
n1.play();

//属性检测
console.log(p instanceof  Person);
console.log(ninja instanceof  Ninja);
console.log(ninja instanceof  Person);

console.log(ninja.constructor ==Ninja);
console.log(p.constructor == Person);

type

  • 判断类型
    type.isNaN(obj);//NaN
    type.isNull(obj);//null
    type.isUndefined(obj);//undefined
    type.isFunction(obj);//function
    type.isArray(obj);//array
    type.isArrayLike(obj);//array like
    type.isWindow(obj);//window
    type.isPlainObject(obj);//plainObject    
        
    

util's People

Contributors

peterfont avatar

Watchers

 avatar

util's Issues

javascript中的正则表达式

创建正则表达式

  1. 字面直接量
// /pattern/flags
/* flags(组合): 
    1. m(multiple)多行匹配
    2. i(ignore) 忽略大小写
    3. g(global) 全局匹配
*/
var regx = /ab+c/g;
  1. RegExp对象
// new RegExp('pattern','flags')
// flags 同上

var regx = new RegExp("ab+c");
var regex = new RegExp(/^[a-zA-Z]+[0-9]*\W?_$, "gi");

两种创建方式的区别:

  1. 字面直接量:在加载脚本后,正则表达式字面值提供正则表达式的编译。当正则表达式保持不变时,使用此方法可获得更好的性能。

  2. 构造函数: 使用构造函数提供正则表达式的运行时编译。使用构造函数,当你知道正则表达式模式将会改变,或者你不知道模式,并从另一个来源,如用户输入。

RegExp的三大方法

  1. test

检测指定字符串是否含有某个子串(或者匹配模式),返回true或者false。

var s = 'you love me and I love you';
var pattern = /you/;
var ans = pattern.test(s);
console.log(ans); // true

  1. exec

提取指定字符串中的符合要求的子串(或者匹配模式),返回一个数组存放匹配结果;如果没有,则返回null。(也可自己写方法循环提取所有或者指定index的数据)

exec可以说是test的升级版本,因为它不仅可以检测,而且检测到了可以直接提取结果。

var rex = new RegExp("d(b+)d", "g");
var arr = rex.exec("cdbbdbsbz");

rex和arr的说明:

对象 属性或索引 描述 在例子中对应的值
arr 匹配到的字符串和所有被记住的子字符串。 ["dbbd","bb"]
arr index 在输入的字符串中匹配到的以0开始的索引值。 1
arr input 初始字符串。 "cdbbdbsbz"
arr [0] 匹配到的所有字符串 "dbbd"
rex lastIndex 下一个匹配的索引值。 5
rex source 模式文本。在正则表达式创建时更新,不执行. "d(b+)d"

当正则表达式带有全局标志g时,exec则永远返回第一个匹配项。但是当连续调用exec时,则每次的返回值都是下一个匹配项。

  1. compile

改变当前匹配模式(pattern)

String对象正则相关的方法(4大护法)

  1. search 方法

搜索指定字符串中是否含有某子串(或者匹配模式),如有,返回子串在原串中的初始位置,如没有,返回-1。

是不是和test类似呢?test只能判断有木有,search还能返回位置!当然test()如果有需要能继续找下去,而search则会自动忽略g(如果有的话)。

var s = 'you love me and I love you';
var pattern = /you/;
var ans = s.search(pattern);
console.log(ans);  // 0

话说和String的indexOf方法有点相似,不同的是indexOf方法可以从指定位置开始查找,但是不支持正则。

  1. match 方法

和exec类似,从指定字符串中查找子串或者匹配模式,找到返回数组,没找到返回null

match是exec的轻量版,当不使用全局模式匹配时,match和exec返回结果一致;当使用全局模式匹配时,match直接返回一个字符串数组,获得的信息远没有exec多,但是使用方式简单。

var s = 'you love me and I love you';
console.log(s.match(/you/));    // ["you", index: 0, input: "you love me and I love you"]
console.log(s.match(/you/g));   // ["you", "you"]
  1. repleace 方法

用另一个子串替换指定字符串中的某子串(或者匹配模式),返回替换后的新的字符串 str.replace(‘搜索模式’,'替换的内容’) 如果用的是pattern并且带g,则全部替换;否则替换第一处。

var s = 'you love me and I love you';
console.log(s.replace('you', 'zichi')); // zichi love me and I love you
console.log(s.replace(/you/, 'zichi')); // zichi love me and I love you
console.log(s.replace(/you/g, 'zichi'));    // zichi love me and I love zichi

如果需要替代的内容不是指定的字符串,而是跟匹配模式或者原字符串有关,那么就要用到$了(记住这些和$符号有关的东东只和replace有关哦)。

字符 替换文本
$1、$2、...、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。
$& 与 regexp 相匹配的子串。
$` 位于匹配子串左侧的文本。
$' 位于匹配子串右侧的文本。
$$ 直接量符号。
var s = 'I love you';
var pattern = /love/;
var ans = s.replace(pattern, '$`' + '$&' + "$'");
console.log(ans); // I I love you you

没错,’$`’ + ‘$&’ + “$’”其实就相当于原串了!

//交换 Doe,John ->> John,Doe
var name = "Doe, John";
name.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");//John,Doe

//首字母大写
name = 'aaa bbb ccc';
uw=name.replace(/\b\w+\b/g, function(word){
  return word.substring(0,1).toUpperCase()+word.substring(1);}
  );

replace,替换字符串可以是function

"<%= name %><%- aaa%><%ccc%>".replace(/<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g,function(){
console.log(arguments);
});

/<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g.exec("<%= name %><%- aaa%><%ccc%>");


//每次function接收的参数和执行exec的方法的内容一样,但是有点区别
//执行exec返回的是一个数组,[匹配到的字符串,子表达式匹配到的字符串1,子表达式2匹配的到字符串,....,子表示n匹配的字符串],属性包括,input 元字符串,index 匹配到的字符串的起始索引

比较图片:
image
image

  1. split 方法

字符串分割成字符串数组的方法(另有数组变成字符串的join方法)。

直接看以下例子:

var s = 'you love me and I love you';
var pattern = 'and';
var ans = s.split(pattern);
console.log(ans);   // ["you love me ", " I love you"]

match 和 exec的区别

相同点

  1. 两个方法都是查找符合条件的匹配项,并以数组形式返回。
  2. 当没有找到匹配项时,都返回null。
  3. 当正则表达式包含子表达式且不包含全局标志g时,二者返回相同的数组。

示例:

var str = 'cat10,bat20,kat30';
var patten = /\w(at)\d+/;
var arr = str.match(patten);
arr[0] <=> ['cat10']
arr[1] <=> ['at']

var arr = patten.exec(str);
arr[0] <=> ['cat10']
arr[1] <=> ['at']

不同点:

  1. match是字符串的方法,exec是RegExp对象的方法
  2. 当正则表达式带有全局标志g时,二者表现不一致。

match会返回所有符合条件的匹配项,并以数组形式返回。数组第一项存放第一个匹配项,数组第二项存放第二个匹配项...依次类推。

exec则永远返回第一个匹配项。但是当连续调用exec时,则每次的返回值都是下一个匹配项。

//示例1:

var str = 'cat,bat,kat';
var patten = /at/g;
str.match(patten);  //['at', 'at', 'at']
patten.exec(str);  //['at']


//示例2:

var str = 'cat,bat,kat';
var patten = /\w+/g;
str.match(patten);  //['cat', 'bat', 'kat']

//第一次调用
patten.exec(str);  //['cat']
//第二次调用
patten.exec(str);  //['bat']
//第三次调用
patten.exec(str);  //['kat']

  1. 当正则表达式包含子表达式时且包含全局标志g时,二者表现不一致。

match会返回所有符合条件的匹配项,并以数组形式返回。这时,match不会再返回子表达式的匹配项了。数组第一项存放第一个匹配项,数组第二项存放第二个匹配项...依次类推。

exec会返回子表达式的匹配项。换句话说就是,数组第一项存放整个匹配项,数组第二项存放第一个子表达式匹配项,数组第三项存放第二个子表达式匹配项...依次类推。

示例:

var str = 'cat10,bat20,kat30';
var patten = /\w(at)\d+/g;
var arr = str.match(patten);  //['cat10', 'bat20', 'kat30']
var arr = patten.exec(str);
arr[0] <=> ['cat10']
arr[1] <=> ['at']

零宽断言

零宽断言(zero-length assertions)这概念直译过于术语化。零宽的意思是指该位置是不占宽度的,也就是只作断言判断,但不匹配实际的内容,比如\d(?=.)这个正向先行断言(这概念也归纳在后)就只匹配点号之前的数字,但是它并不会匹配到这个点号,这个\d(?=.)括号中的匹配内容也就是零宽了。

贪婪模式和非贪婪模式

 String str="abcaxc";
 Patter p="ab*c";
  1. 贪婪匹配:正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。如上面使用模式p匹配字符串str,结果就是匹配到:abcaxc(ab*c)。

  2. 非贪婪匹配:就是匹配到结果就好,就少的匹配字符。如上面使用模式p匹配字符串str,结果就是匹配到:abc(ab*c)。

贪婪量词,惰性量词,支配性量词

  1. 贪婪量词

所有简单量词。就像成语中说的巴蛇吞象那样,一口吞下整个字符串,发现吞不下(匹配不了),再从后面一点点吐出来(去掉最后一个字符,再看这时这个整个字符串是否匹配,不断这样重复直到长度为零)

  1. 惰性量词

隋性量词,在简单量词后加问号。由于太懒了,先吃了前面第一个字符,如果不饱再捏起多添加一个(发现不匹配,就读下第二个,与最初的组成一个有两个字符串的字符串再尝试匹配,如果再不匹配,再吃一个组成拥有三个字符的字符串……)。其工作方式与贪婪量词相反。

  1. 支配性量词

支配性量词,在简单量词后加加号。上面两种都有个不断尝试的过程,而支配性量词却只尝试一次,不合口味就算了。就像一个出身高贵居支配地位的公主。但你也可以说它是最懒量词。由于javascript不支持,所以它连出场的机会也没有了。

构建一个模板工具

 // underscore代码
  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate: /<%([\s\S]+?)%>/g,
    interpolate: /<%=([\s\S]+?)%>/g,
    escape: /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'": "'",
    '\\': '\\',//
    '\r': 'r',//回车
    '\n': 'n',//换行
    '\u2028': 'u2028',//行分隔符
    '\u2029': 'u2029'//段落分隔符
  };

  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

  var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  // NB: `oldSettings` only exists for backwards compatibility.
  _.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    // 匹配的组
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {

      //replace function args
      source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
      index = offset + match.length;

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }

      // Adobe VMs need the match returned to produce the correct offset.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + 'return __p;\n';

    var render;
    try {
      render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}';

    return template;
  };

正则表达式的思维导图

image

正则表达式在线工具

http://tool.oschina.net/regex

参考文章:

  1. http://www.cnblogs.com/rubylouvre/archive/2010/03/09/1681222.html
  2. http://www.cnblogs.com/tinkbell/p/4563688.html
  3. https://segmentfault.com/a/1190000004106110
  4. http://www.codeceo.com/article/javascript-reg-expression.html
  5. http://www.cnblogs.com/dojo-lzz/p/5518474.html

封装和继承

javascript最强大的特性是灵活性

作为javasScript程序员,只要你愿意,可以写的很简单,也可以写的很复杂。

这种语言也支持多种不同的编程风格,你可以采用函数式编程的风格,也可以采用
更复杂一点的面向对象的编程风格。

即使你根本函数式编程或者面向对象编程也能写出较为复杂的程序。

使用这语言,哪怕只采用编写了一个一个简单的函数方式,你也能高效的完成任务。

这可能是某些人把JavaScript视同玩具的原因之一,但我确认为这是一个优点。

程序员只要使用这种语言的很小的、易于学习的自己就能完成一些用用的任务。

这也意味着,当你成为一个更高级的程序员时,JavaScript在你的手中的威力也在增长!

-这就是写这些东西的原因,同意的不能再同意了。

闲言少叙,直奔主题!!

封装

封装目的为了隐藏内部实现细节并提供公共的接口,已达到解耦的目的;

var Book = (function(){
    //只在创建类的时候初始化一次
    //私有的静态属性
    var numberOfBook = 0;
    
    //私有的静态方法
    /*
     私用属性和静态方法,可以用"_"的方式标记;但是仍然无法避免被覆盖;
     通过闭包实现则不会被覆盖;
    */
    function checkISBN(){
    
    }
    //constuctor 返回构造方法
    return function(newIsbn,newTitle,newAuthor){
        //私有属性
        var isbn,title,author;
        //创建属性的访问器
        this.setIsbn = function(newIsbn){
            if(!checkISBN(newIsbn)){
                throw new Error("isbn format is error!!");
            }
            isbn = newIsbn;
        }
        this.getIsbn = function(){
            return isbn;
        }
        this.setTitle = function(newTitle){
            title = newTitle;
        }
        this.getTitle = function(){
            return title;
        }
        this.setAuthor = function(newAuthor){
            author = newAuthor;
        }
        this.getAuthor = function(){
            return author;
        }
        //初始化动作
        numberOfBook++;
        this.setIsbn(newIsbn);
        this.setTitle(newTitle);
        this.setAuthor(newAuthor);
        
    }
})();
//公开的静态方法
Book.covertToTitleCase = function(){
    
}
//公开的非私有方法
Book.prototype = {
    display:function(){
        
    }
}

封装的利弊:

  1. 内部方法很难被单元测试;这种弊端不是js独有的,只对共有方法进行测试时广为接受处理方式;
  2. 封装保护了内部数据的完整性;
  3. 通过公开方法弱化模块之间的耦合度;

继承

  1. 类式继承

利用构造器构造函数的实现继承


function extend(subClass,supClass){
    var F = function(){};
    F.prototype =  supClass.prototype;
    //subClass.prototype= new F().__proto__ = supClass.prototype
    subClass.prototype = new F();
    subClass.subClass =  supClass.prototype;
    
    if(supClass.prototype.constructor == Object.prototype.constructor){
        supClass.prototype.constructor = supClass;
    }
}
  1. 原型式继承

原型式继承和类式继承截然不同;谈到他的时候,最好忘掉类和实例的一切知识,只从对象的角度来思考;

原型式继承,不需要创建用类定义对象的结构;只需要直接创建一个对象即可;这个对象随后会被新的对象重用,这得益于原型链的属性查找机制;

原型式继承,继承而来的属性的读和写存在不对等性;

对于引用类型的属性,如果当前对象没有定义相同属性的话,直接读取属性则是父对象的原型公共属性;但是,当前对象定义了相同的引用属性的属性,则会覆盖父对象原型提供的公共的引用属性;

解决方式有3种:

  1. 深copy
  2. 通过hasOwnprototype区分对象的实际成员或者继承的成员;
  3. 任何复杂的子对象,都要通过工厂方式创建
function clone(object){
    function F(){};
    F.prototype = object;
    return new F;
}

原型式继承和类式继承相比更节约内存,更简洁;

原型式继承继承的所有属性都是来自同一个对象的clone,只有直接设置了某个克隆的方法和属性时,情况才会有所变化;而类式继承方式,创建的每一个对象在内存中都有自己的一套属性的副本;

  1. 掺元类

重用代码的一种方式,不需要用到严格的继承;
想要一个函数用到多个类中,可以通过扩充(augmentation)的方式让这些类共享该函数;其实际做法为:先创建一个包含各种通用方法的类,让后再把它扩充到其他类;这种包含通用方法的类,一般被称为掺元类;他通常不会实例化或者直接调用。其存在的目的只是向其他类提供自己的方法。

function Mixin(){};
Mixin.prototype={
    serialize:function(){
        
    }
}

实现:

  function augment(rc,gc){
        if(arguments[2]){//继承指定的方法
            for(var i=2,len=arguments.length;i<len;i++){
                rc.prototype[arguments[i]] = gc.prototype[arguments[i]];
            }
        }else{
            for(var pro in gc.prototype){
                if(!rc.prototype[pro]){
                    rc.prototype[pro] = gc.prototype[pro];
                }
            }
        }
    }

ps.最近写代码遇到了瓶颈了。加上这几年的前端技术蓬(fan)勃(lan)发展,出门不会几种时髦的框架都不好意思说自己是前端开发;尽管用了时髦框架,但是仍然你能阻止你写出烂代码,裹脚布一样又臭又长的代码。 我一直以来的困惑也在此;写出优质高效的代码甚至优雅的代码,才是我追求的;如果无法掌握设计模式,数据结构和算法,这根本就是缘木求鱼!最近的一些学习和反思,让我更加印证了我的想法;

异步编程

不连续的分段执行,叫做异步。

解决方案:

  1. 回调函数
  2. 事件监听
  3. 发布/订阅
  4. Promise
  5. ES6的异步解决方案,(Genrator+Promist+co,Generator+Thunk自动化流程管理)
  6. ES7方案,async+await
    解决方案对比:

1.回调函数

所谓回调函数,就是把第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数.

主要弊端层层嵌套的回调,callback hell(回调地狱);

影响代码的易读性,不易维护,还有在各种的问题。

代码是横向 发展,而不是线性的纵向发展。

var fs = require("fs");
fs.readFile("./a.json",function(err,data){
    fs.readFile("./b.json",function(err,data){
        ....
    });        
});

  1. Promise

解决代码的横向发展

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function(data){
  console.log(data.toString());
})
.then(function(){
  return readFile(fileB);
})
.then(function(data){
  console.log(data.toString());
})
.catch(function(err) {
  console.log(err);
});

补充:

Promise是一个代理对象

  1. 状态

    • pending: 初始状态,不是成功或失败状态。
    • fulfilled: 意味着操作成功完成。
    • rejected: 意味着操作失败。
  2. 方法

    • then(cb,faildCb)
    • catch

实现一个promise:


function Promise(fn) {
    var state = 'pending',
        value = null,
        callbacks = [];

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };
    //增加异常处理
    function handle(callback) {
        if (state === 'pending') {
            callbacks.push(callback);
            return;
        }

        var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
            ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? callback.resolve : callback.reject;
            cb(value);
            return;
        }
        try {
            ret = cb(value);
            callback.resolve(ret);
        } catch (e) {
            callback.reject(e);
        }
    }

    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        execute();
    }

    function reject(reason) {
        state = 'rejected';
        value = reason;
        execute();
    }

    function execute() {
        setTimeout(function () {
            callbacks.forEach(function (callback) {
                handle(callback);
            });
        }, 0);
    }

    fn(resolve, reject);
}

Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。

  1. 协程

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。

执行顺序:

  • 第一步,协程A开始执行。
  • 第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
  • 第三步,(一段时间后)协程B交还执行权。
  • 第四步,协程A恢复执行。
function asnycJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

  1. Generator函数

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

//定义
function* gen(x){
  var y = yield x + 2;
  return y;
}

//执行
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

Generator结合Promise

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

//执行
var g = gen();
var result = g.next();

//返回的是promise对象
result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

  1. javascript的Trunk函数

(Trunk n. 形实转换程序),Trunk是一种求值策略的方式。

fn(x+5)
//形参的两种求值策略
//1. 传值时的调用

f(x + 5)
// 传值调用时,等同于
f(6)

//2. 传名时调用

f(x + 5)
// 传名调用时,等同于
(x + 5) * 2

因此,有一些计算机学家倾向于"传名调用",即只在执行时求值。这就是 Thunk 函数的定义,它是"传名调用"的一种实现策略,用来替换某个表达式。

javascript的Trunk实现

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。

// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);

var Thunk = function (fileName){
 return function (callback){
   return fs.readFile(fileName, callback); 
 };
};

简单的Trunk转化器:

var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

Trunk转化器转化readFile

var readFileThunk = Thunk(fs.readFile);
//readFileTrunk(fileA) 并没有执行而是返回一个函数
readFileThunk(fileA)(callback);

Thunkify源代码:

function thunkify(fn){
  return function(){
    var args = new Array(arguments.length);
    var ctx = this;

    for(var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }

    return function(done){
      var called;

      args.push(function(){
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};

//它的源码主要多了一个检查机制,变量 called 确保回调函数只运行一次。这样的设计与下文的 Generator 函数相关.


  1. Generator函数的流程管理

Thunk 函数现在可以用于 Generator 函数的自动流程管理。


var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFile('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFile('/etc/shells');
  console.log(r2.toString());
};

//手动执行
var g = gen();

var r1 = g.next();
r1.value(function(err, data){
  if (err) throw err;
  var r2 = g.next(data);
  r2.value(function(err, data){
    if (err) throw err;
    g.next(data);
  });
});

//基于trunk函数的自动执行器
function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

run(gen);

/*
函数 gen 封装了 n 个异步的读取文件操作,只要执行 run 函数,这些操作就会自动完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行
*/

/*

Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点

*/
  1. co函数库
var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

//co 函数库可以让你不用编写 Generator 函数的执行器。

var co = require('co');
co(gen);

//co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。

co(gen).then(function (){
  console.log('Generator 函数执行完成');
})

co 函数库的原理

为什么 co 可以自动执行 Generator 函数?

Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点。\

  1. 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
  2. Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。

co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象.

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。
这时,要把并发的操作都放在数组或对象里面。

// 数组的写法
co(function* () {
  var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  console.log(res); 
}).catch(onerror);

// 对象的写法
co(function* () {
  var res = yield {
    1: Promise.resolve(1),
    2: Promise.resolve(2),
  };
  console.log(res); 
}).catch(onerror);
  1. 终极解决方案

异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。
从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。

async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案

一句话,async 函数就是 Generator 函数的语法糖。

之前方案:

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

async函数:
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

async 函数的优点

async 函数对 Generator 函数的改进,体现在以下三点。

(1)内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。

var result = asyncReadFile();

(2)更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

原型

构造对象的几种方式

  1. 字面直接量
 var obj = {
   a:1,
   b:2
 }
 obj.a
  1. 构造函是方式,new
Foo function(a,b){
    this.a = a;
    this.b = b;
}
Foo.prototype.say = function(){
    
}
var foo = new Foo(1,2);
foo.a;
foo.a;
  1. Object.create方式
var obj = Objct.create({
    a:1,
    b:1,
    say:function(){
        
    }
});

obj.a;
obj.say();

几个概念

  1. 对象:
    是new Object的结果,所以对象可以作为实例对象,其构造函数是Object,原型对象是Object.prototype

  2. 实例对象:
    由new创建的对象;对象直接量(只是隐式的调用new);本质上没有区别;

  3. 原型对象: object.prototype 的原型对象是null

  4. 构造函数: 所有函数都可以成为构造函数,构造函数只是函数调用的一种方式。这种说法,只是针对于,实例对象来说的。通常情况下需要加上定语,xx的构造函数。

  5. 函数:new Function的结果;所以函数也是实例对象;构造函数是Function;原型对象是Function.prototype

  6. __proto__和prototye的区别:

一张图

image

对象类型

image

原型链的理解

  1. proto(隐式原型 implicit prototype)

    JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf().

    Note: Object.prototype 这个对象是个例外,它的__proto__值为null

  2. prototype(显式原型 explicit prototype)

    每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。

    Note:通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。

  3. 二者的关系:
    隐式原型指向创建这个对象的函数(constructor)的prototype

  4. 作用是什么?

    显式原型的作用:用来实现基于原型的继承与属性的共享。

    ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. ----ECMAScript Language Specification

    隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着__proto__依次查找。

    Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” ----ECMAScript Language Specification

  5. __proto__的指向

    __proto__的指向到底如何判断呢?根据ECMA定义 'to the value of its constructor’s "prototype" ' ----指向创建这个对象的函数的显式原型。所以关键的点在于找到创建这个对象的构造函数,接下来就来看一下JS中对象被创建的方式,一眼看过去似乎有三种方式:

    (1)对象字面量的方式

    (2)new 的方式

    (3)ES5中的Object.create()

    但是我认为本质上只有一种方式,也就是通过new来创建。为什么这么说呢,首先字面量的方式是一种为了开发人员更方便创建对象的一个语法糖,本质就是 var o = new Object(); o.xx = xx;o.yy=yy; 再来看看Object.create(),这是ES5中新增的方法,在这之前这被称为原型式继承,

    道格拉斯在2006年写了一篇文章,题为 Prototypal Inheritance In JavaScript。在这篇文章中,他介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建新对象,同时还不比因此创建自定义类型,为了达到这个目的,他给出了如下函数:

    function object(o){
        function F(){}
        F.prototype = o;
        return new F()
    }
    

    《JavaScript高级程序设计》P169所以从实现代码

    return new F()中我们可以看到,这依然是通过new来创建的。不同之处在于由 Object.create() 创建出来的对象没有构造函数,看到这里你是不是要问,没有构造函数我怎么知道它的__proto__指向哪里呢,其实这里说它没有构造函数是指在 Object.create() 函数外部我们不能访问到它的构造函数,然而在函数内部实现中是有的,它短暂地存在了那么一会儿。假设我们现在就在函数内部,可以看到对象的构造函数是F, 现在

    //以下是用于验证的伪代码
    var f = new F(); 
    //于是有
    f.__proto__ === F.prototype //true
    //又因为
    F.prototype === o;//true
    //所以
    f.__proto__ === o;
    

    因此由Object.create(o)创建出来的对象它的隐式原型指向o。好了,对象的创建方式分析完了,现在你应该能够判断一个对象的__proto__指向谁了

    好吧,还是举一些一眼看过去比较疑惑的例子来巩固一下。 构造函数的显示原型的隐式原型:内建对象(built-in object):比如Array(),Array.prototype.__proto__指向什么?Array.prototype也是一个对象,对象就是由 Object() 这个构造函数创建的,因此Array.prototype.proto === Object.prototype //true,或者也可以这么理解,所有的内建对象都是由Object()创建而来。自定义对象

    1. 默认情况下:function Foo(){}
    var foo = new Foo()
    Foo.prototype.__proto__ === Object.prototype //true 理由同上
    
    
    1. 其他情况:
    //情况1
    function Bar(){}
    //这时我们想让Foo继承Bar
    Foo.prototype = new Bar()
    Foo.prototype.__proto__ === Bar.prototype //true
    
    //情况2
    //我们不想让Foo继承谁,但是我们要自己重新定义Foo.prototype
    Foo.prototype = {
      a:10,
      b:-10
    }
    //这种方式就是用了对象字面量的方式来创建一个对象,根据前文所述 
    Foo.prototype.__proto__ === Object.prototype 
    

    注:以上两种情况都等于完全重写了Foo.prototype,所以Foo.prototype.constructor也跟着改变了,于是乎constructor这个属性和原来的构造函数Foo()也就切断了联系。 构造函数的隐式原型 既然是构造函数那么它就是Function()的实例,因此也就指向Function.prototype,比如 Object.proto === Function.prototype。

new

 function new(func){
    var o = {};
    ret.__proto__= func.prototype;
    var ret = func.call(o,arguments);
    return typeof ret === 'object'? ret : obj;
 }
 

instanceof

instanceof 操作符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的:

//设 L instanceof R 
//通过判断
 L.__proto__.__proto__ ..... === R.prototype ?
//最终返回true or false

也就是沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止。知道了这个也就知道为什么以下这些奇怪的表达式为什么会得到相应的值了

Function instanceof Object // true 
Object instanceof Function // true 
Function instanceof Function //true
Object instanceof Object // true
Number instanceof Number //false

bind

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。

说明:

  1. 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。

  2. 绑定函数被调用时,bind() 也接受预设的参数提供给原函数。

  3. 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数说明:
thisArg

当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

arg1, arg2, ...

当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值

返回由指定的this值和初始化参数改造的原函数拷贝

bind的实现

bind 函数在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。

//实现Function.prototype.bind 的Polyfill

//扩展Function原型
if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5
            // internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () {},
            fBound = function () {//fBound继承自this
                return fToBind.apply(this instanceof fNOP
                        ? this
                        : oThis || this,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}
//非扩展方式实现
function bind(func,context,arg){
    if(typeof func !== "function") throw new TypeError("Bind must be called on a function");
    var args = Array.prototype.slice.call(arguments,2),
        Cort = function(){},
        bound = function (){
            //非构造函数方式
            if(!(this instanceof func)){
                return func.apply(context||this,args.concat(arguments));
            }
            //构造函数方式
            Cort.prototype = func.prototype;
            //this.__proto__ ==  bound.prototype.__proto__ = new FONP.__proto__ = FONP.prototype = func.prototype
            //this instaceof func  --> true;
            bound.protype = new Cort();
            func.apply(this instanceof func?this:context||this,args.concat(arguments));
        };
    return bound;
}

bind的使用:

  1. 创建绑定函数

bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。

JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象。(比如在回调中传入这个方法。)如果不做特殊处理的话,一般会丢失原来的对象。从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决这个问题

var n = "other";
var mod = {
    n:"mod",
    sayName:function(){
        console.log(this.n);
    }
}
var sayName = mod.sayName;
sayName();//this.n==>window.n  other

// 创建一个新函数,将"this"绑定到mod对象
var sayName1 = mod.sayName.bind(mod);
sayName1();//mod

  1. 偏函数(Partial Functions)

bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind()的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

  1. 配合 setTimeout

在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window (或全局)对象。当使用类的方法时,需要 this 引用类的实例,你可能需要显式地把 this 绑定到回调函数以便继续使用实例。

var a =1;
var obj={
    a:10,
    timer:function(){
    
        setTimeout(function(){
            console.log(this.a);
        },1000);
    }
};
obj.timer();//1 此时this指向window

//bing解决
function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后, 调用'declare'方法
  1. 作为构造函数使用的绑定函数

警告 :这部分演示了 JavaScript 的能力并且记录了 bind() 的超前用法。以下展示的方法并不是最佳的解决方案且可能不应该用在任何生产环境中。

自然而然地,绑定函数适用于用new操作符 new 去构造一个由目标函数创建的新的实例。当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。然而, 原先提供的那些参数仍然会被前置到构造函数调用的前面。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};

var p = new Point(1, 2);
p.toString(); // '1,2'

var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 以下这行代码在 polyfill 不支持,
// 在原生的bind方法运行没问题:
//(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true

// 仍然能作为一个普通函数来调用
// (即使通常来说这个不是被期望发生的)
YAxisPoint(13);

emptyObj.x + ',' + emptyObj.y;   //  '0,13'
  1. 快捷调用

在你想要为一个需要特定的 this 值的函数创建一个捷径(shortcut)的时候,bind() 方法也很好用。

var slice = Array.prototype.slice;
slice.apply(arguments);

//bind
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
slice(arguments);

  1. underscore的实现
  // Naked function reference for surrogate-prototype-swapping.
  var Ctor = function(){};
  //不定参转换
  var restArgs = function(func, startIndex) {
    //TODO:func.length 返回的是形参的数量
    //TODO:arguments返回的是实参的数量

    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {

      var length = Math.max(arguments.length - startIndex, 0),//返回参数的长度 - 开始的索引  默认是0
          rest = Array(length),//构造restArgs的容器
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };
  
  //实现继承
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);//Object.create
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };
  //executeBound
  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    //非构造函数方式
   //return executeBound(func, bound, context, this, args.concat(callArgs));

    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);

    //构造函数方式
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
    if (_.isObject(result)) return result;//obj
    return self;
  };
  
  //bind实现
  _.bind = restArgs(function(func, context, args) {
    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
    var bound = restArgs(function(callArgs) {
      return executeBound(func, bound, context, this, args.concat(callArgs));
    });
    return bound;
  });

//偏函数实现
  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context. _ acts
  // as a placeholder by default, allowing any combination of arguments to be
  // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
  _.partial = restArgs(function(func, boundArgs) {
    var placeholder = _.partial.placeholder;
    var bound = function() {
      var position = 0, length = boundArgs.length;
      var args = Array(length);
      for (var i = 0; i < length; i++) {
        args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
      }
      while (position < arguments.length) args.push(arguments[position++]);
      return executeBound(func, bound, this, this, args);
    };
    return bound;
  });

  _.partial.placeholder = _;

  // Bind a number of an object's methods to that object. Remaining arguments
  // are the method names to be bound. Useful for ensuring that all callbacks
  // defined on an object belong to it.
  _.bindAll = restArgs(function(obj, keys) {
    keys = flatten(keys, false, false);
    var index = keys.length;
    if (index < 1) throw new Error('bindAll must be passed function names');
    while (index--) {
      var key = keys[index];
      obj[key] = _.bind(obj[key], obj);
    }
  });

Promise/A+规范

Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算.。
一个Promise对象代表着一个还未完成,但预期将来会完成的操作。
Promise 对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。
它允许你为异步操作的成功或失败指定处理方法。
这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的 promise 对象来替代原返回值。

image

Backbone.js解读

Backbone.js是一个前端MVC库。

框架和库的区别:使用一个库,你有控制权。如果用一个框架,控制权就反转了,变成框架在控制你。库能够给予灵活和自由,但是框架强制使用某种方式,减少重复代码。

Backbone.js强依赖underscore.js
依赖Backbone.$,以及$.ajax用于向server端持久化数据;可以和jquery或者zepto结合使用;当然持久化如果是本地的话,也可以用Backbone.localStorage.js来测试。

其实Backbone.js是MVC是有点问题的,他后来的版本是没有Controller的。因为Controller的功能过于单薄,基本上就只有路由的功能。所以Controller被Router所取代了。

示意图如下:

image

解释:

  1. 用户可以向 View 发送指令(DOM 事件),再由 View 直接要求 Model 改变状态。View监听Model的改变,Model改变之后会通知View去渲染模板。

  2. 用户也可以直接向 Controller 发送指令(改变 URL 触发 hashChange 事件),再由 Controller 发送给 View。

  3. Controller 非常薄,只起到路由的作用,而 View 非常厚,业务逻辑都部署在 View。所以,Backbone 索性取消了 Controller,只保留一个 Router(路由器)

参考: MVC,MVP 和 MVVM 的图示

Backbone.js的源码

基本结构

 
 (function(factory){
    //UMD包装    
 })(function(root, Backbone, _, $){
    var previousBackbone = root.Backbone;
    Backbone.$ = $;

    Backbone.noConflict = function() {
        root.Backbone = previousBackbone;
        return this;
    };
    
    //Events
    var Events = {};
    _.extend(Backbone,Events);
    
    //Model
    var Model = Backbone.Model = function(){}
    _.extend(Model.prototype,Events,{});
    
    //Collection
    var Collection = Backbone.Collection = function(){}
    _.extend(Collection.prototype,Events,{});
    
    //View
    var View = Backbone.View = function(){}
    _.extend(View.prototype,Events,{});
    
    //sync
    Backbone.sync = function(method, model, options){};
    
    //Router
    var Router = Backbone.Router = function(){}
    _.extend(Router.prototype,Events,{});
    
    //History
    var History = Backbone.History = function() {}
    
    //Helper
    var extend = function(protoProps, staticProps){}
    Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
    
    var urlError = function() {};
    var wrapError = function(model, options) {};
    
    
    return Backbone;
 });
 

Events

image

  1. on(bind) : 注册事件
/*
 * 绑定事件
 * this.on("event"||"event1 event2",function(){}||{event1:fn1,event2:fn2});
 */
Events.on = function(name, callback, context) {
    return internalOn(this, name, callback, context);
}

Events.bind = Events.on;

  1. off(unbind): 解除事件
// 解除绑定
// Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
Events.off = function(name, callback, context) {
    if (!this._events) return this;
    this._events = eventsApi(offApi, this._events, name, callback, {
      context: context,
      listeners: this._listeners
    });
    return this;
  };
  1. listenTo:on的控制反转版本
  // Inversion-of-control versions of `on`. Tell *this* object to listen to
  // an event in another object... keeping track of what it's listening to
  // for easier unbinding later.
  Events.listenTo = function(obj, name, callback) {
}
  1. stopListening 让 object 停止监听事件。
// Tell this object to stop listening to either specific events ... or
  // to every object it's currently listening to.
  Events.stopListening = function(obj, name, callback) {
    var listeningTo = this._listeningTo;
    if (!listeningTo) return this;

    var ids = obj ? [obj._listenId] : _.keys(listeningTo);

    for (var i = 0; i < ids.length; i++) {
      var listening = listeningTo[ids[i]];

      // If listening doesn't exist, this object is not currently
      // listening to obj. Break out early.
      if (!listening) break;

      listening.obj.off(name, callback, this);
    }

    return this;
  };
  1. Backbone.js的事件列表

    • "add" (model, collection, options) — 当一个model(模型)被添加到一个collection(集合)时触发。
    • "remove" (model, collection, options) — 当一个model(模型)从一个collection(集合)中被删除时触发。
    • "reset" (collection, options) — 当该collection(集合)的全部内容已被替换时触发。
    • "sort" (collection, options) — 当该collection(集合)已被重新排序时触发。
    • "change" (model, options) — 当一个model(模型)的属性改变时触发。
    • "change:[attribute]" (model, value, options) — 当一个model(模型)的某个特定属性被更新时触发。
    • "destroy" (model, collection, options) —当一个model(模型)被destroyed(销毁)时触发。
    • "request" (model_or_collection, xhr, options) — 当一个model(模型)或collection(集合)开始发送请求到服务器时触发。
    • "sync" (model_or_collection, resp, options) — 当一个model(模型)或collection(集合)成功同步到服务器时触发。
    • "error" (model_or_collection, resp, options) — 当一个model(模型)或collection(集合)的请求远程服务器失败时触发。
    • "invalid" (model, error, options) — 当model(模型)在客户端 validation(验证)失败时触发。
    • "route:[name]" (params) — 当一个特定route(路由)相匹配时通过路由器触发。
    • "route" (route, params) — 当任何一个route(路由)相匹配时通过路由器触发。
    • "route" (router, route, params) — 当任何一个route(路由)相匹配时通过history(历史记录)触发。
    • "all" — 所有事件发生都能触发这个特别的事件,第一个参数是触发事件的名称。

Model:模型

image

核心方法:set

'The heart of the beast.'(有意思,野兽之心)

// Extract attributes and options.
// 扩展的属性操作
// unset 是否删除属性
// silent 是否触发chang事件
      
//递归调用做了处理
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
// 变化可以递归嵌套在“改变”的事件。
// 处理在set调用期间,再次调用set的这种递归调用

Collection:集合

image

View

视图支持DOM事件的批量绑定

events:{
    "click .name":function(){
        
    },
    "keypress .input":"show"
}
this.show = function(){}

源码:

  // Backbone Views are almost more convention than they are actual code. A View
  // is simply a JavaScript object that represents a logical chunk of UI in the
  // DOM. This might be a single item, an entire list, a sidebar or panel, or
  // even the surrounding frame which wraps your whole app. Defining a chunk of
  // UI as a **View** allows you to define your DOM events declaratively, without
  // having to worry about render order ... and makes it easy for the view to
  // react to specific changes in the state of your models.

  // Creating a Backbone.View creates its initial element outside of the DOM,
  // if an existing element is not provided...
  var View = Backbone.View = function(options) {
  
      
  };
  // Cached regex to split keys for `delegate`.
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  // List of view options to be set as properties.
  // 限定options指定的属性
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
  //注意,tagName className 和el的区别
 

sync:向服务器持久化模型

// Backbone.sync
  // -------------

  // Override this function to change the manner in which Backbone persists
  // models to the server. You will be passed the type of request, and the
  // model in question. By default, makes a RESTful Ajax request
  // to the model's `url()`. Some possible customizations could be:
  //
  // * Use `setTimeout` to batch rapid-fire updates into a single request.
  // * Send up the models as XML instead of JSON.
  // * Persist models via WebSockets instead of Ajax.
  //
  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  // as `POST`, with a `_method` parameter containing the true HTTP method,
  // as well as all requests with the body as `application/x-www-form-urlencoded`
  // instead of `application/json` with the model in a param named `model`.
  // Useful when interfacing with server-side languages like **PHP** that make
  // it difficult to read the body of `PUT` requests.
   Backbone.sync = function(method, model, options) {
    };
      // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  //同步方法,对应的mothod操作      
  var methodMap = {
    'create': 'POST',
    'update': 'PUT',
    'patch': 'PATCH',
    'delete': 'DELETE',
    'read': 'GET'
  };

  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
  // Override this if you'd like to use a different library.
  Backbone.ajax = function() {
    return Backbone.$.ajax.apply(Backbone.$, arguments);
  };

Router

/* 

初始化参数,routes 将带参数的 URLs 映射到路由实例的方法上(或只是直接的函数定义,如果你喜欢),这与 View(视图) 的 events hash(事件键值对) 非常类似。 路由可以包含参数, :param,它在斜线之间匹配 URL 组件。 路由也支持通配符, *splat,可以匹配多个 URL 组件。 路由的可选部分放在括号中(/:optional)。

举个例子,路由 "search/:query/p:page" 能匹配#search/obama/p2 , 这里传入了 "obama" 和 "2" 到路由对应的动作中去了。

路由 "file/*path"可以匹配 #file/nested/folder/file.txt,这时传入动作的参数为 "nested/folder/file.txt"。

路由 "docs/:section(/:subsection)"可以匹配#docs/faq 和 #docs/faq/installing,第一种情况,传入 "faq" 到路由对应的动作中去, 第二种情况,传入"faq" 和 "installing" 到路由对应的动作中去。

结尾的斜杠会被当作URL的一部分, 访问时会被(正确地)当作一个独立的路由。 docs 和 docs/将触发不同的回调。 如果你不能避免产生这两种类型的URLs时, 你可以定义一个"docs(/)"来匹配捕捉这两种情况。

当访问者点击浏览器后退按钮,或者输入 URL ,如果匹配一个路由,此时会触发一个基于动作名称的 event, 其它对象可以监听这个路由并接收到通知。 下面的示例中,用户访问 #help/uploading 将从路由中触发 route:help 事件。

routes: {
  "help/:page":         "help",
  "download/*path":     "download",
  "folder/:name":       "openFolder",
  "folder/:name-:mode": "openFolder"
}
router.on("route:help", function(page) {
  ...
});

*/

//核心方法

/*为路由对象手动创建路由,route 参数可以是 routing string(路由字符串) 或 正则表达式。 每个捕捉到的被传入的路由或正则表达式,都将作为参数传入回调函数(callback)。 一旦路由匹配, name 参数会触发 "route:name" 事件。如果callback参数省略 router[name]将被用来代替。 后来添加的路由可以覆盖先前声明的路由。
*/
route: function(route, name, callback) {
  return this;
},
/*
每当你达到你的应用的一个点时,你想保存为一个URL,  可以调用navigate以更新的URL。 如果您也想调用路由功能, 设置trigger选项设置为true。 无需在浏览器的历史记录创建条目来更新URL,  设置 replace选项设置为true。
*/
execute: function(callback, args, name) {
  if (callback) callback.apply(this, args);
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
/*
这种方法在路由内部被调用,  每当路由和其相应的callback匹配时被执行。 覆盖它来执行自定义解析或包装路由, 例如, 在传递他们给你的路由回调之前解析查询字符串,像这样:
var Router = Backbone.Router.extend({
  execute: function(callback, args) {
    args.push(parseQueryString(args.pop()));
    if (callback) callback.apply(this, args);
  }
});
*/
navigate: function(fragment, options) {
  Backbone.history.navigate(fragment, options);
  return this;
},

History

接下来会有一篇专门介绍History的文章(单页面应用中尤其的重要)。

// Backbone.History
  // ----------------

  // Handles cross-browser history management, based on either
  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
  // and URL fragments. If the browser supports neither (old IE, natch),
  // falls back to polling.
  var History = Backbone.History = function() {
  }

extend

  //实现了一个继承
  // Helper function to correctly set up the prototype chain for subclasses.
  // Similar to `goog.inherits`, but uses a hash of prototype properties and
  // class properties to be extended.
  var extend = function(protoProps, staticProps) {
    var parent = this;
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent constructor.
    if (protoProps && _.has(protoProps, 'constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){ return parent.apply(this, arguments); };
    }

    // Add static properties to the constructor function, if supplied.
    _.extend(child, parent, staticProps);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function and add the prototype properties.
    child.prototype = _.create(parent.prototype, protoProps);
    child.prototype.constructor = child;

    // Set a convenience property in case the parent's prototype is needed
    // later.
    child.__super__ = parent.prototype;

    return child;
  };

参考文章:

  1. http://www.css88.com/doc/backbone/
  2. http://www.css88.com/doc/underscore/
  3. http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
  4. http://www.open-open.com/lib/view/open1480580504201.html
  5. http://www.oschina.net/news/75789/front-end-mvc-develope?from=timeline&isappinstalled=1
  6. https://segmentfault.com/a/1190000002447556
  7. *http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html
  8. http://www.techug.com/post/mvc-deformation.html
  9. https://segmentfault.com/a/1190000003002851

动手写一个MVC框架(待...)

MVC实现TODOS,以方便理解MVC模式

功能:

  1. 添加待办事项
  2. 编辑待办事项
  3. 删除待办事项
  4. 批量删除已完成事项
  5. 批量完成待办事项
  6. 显示待完成事项数量
  7. 显示已完成事项数量

效果:

image

MVC示意图:

image

说明:

  1. 用户通过事件,向View发送指令,View直接要求Model改变状态
  2. Model状态改变,通知View去重新渲染DOM(View通过监听Model的事件指令,执行相关的操作)
  3. 用户也可以直接向 Controller 发送指令(改变 URL 触发 hashChange 事件),再由 Controller 发送给 View

接口

接口声明和接口检查

鸭式辨形结合注释实现接口

var Interface = function(name,methods){
    if(arguments.length!=2){
        throw new Error("Interface constructor called with "+
        arguments.length +" arguments,but expected exactly 2");
    }    
    this.name = name;
    this.methods = [];
    for(var i=0,len=methods.length;i<len;i++){
        if(type methods[i]!="string"){
            throw new Error("Interface constructor expects method names 
            to be passed in as a string");
        }
        this.methods.push(method[i]);
    }
}

//static class method

Interface.ensureImplements = function(obj){
    if(arguments.length<2){
        throw new Error("Function Interface.ensureImplements called witdh
        "+arguments.length+"arguments ,but expected at least 2");
    }
    for(var i = 1,len = arguments.length;i<len;i++){
        var interface = arguments[i];
        if(interface.constructor !== Interface){
            throw new Error("Function interface,ensureImplemnts expects 
            arguments two and above-to be instances of Interface");
        }
        for(var j=0,methodslen = interface.methods.length;j<methodsLen;j++){
            var method = interface.methods[j];
            if(!object[method] || typeof object[mthod] !=="function"){
                throw new Error("Function interface.ensureImplements:object does not implement the "+ interface.name +" interface.mthod "+method+"was not found.");
            }
        }
        
    }
}


Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.