从8月10多号开始工作到现在已差不多有2个多月了,这期间接触了许多新的东西,比如前端MVC,JS模块化,前端模板,游戏引擎,等,也学习了很多出色的js库的源码,有的也在具体的项目中应用了,但一直以来忙于工作没有时间总结,以前在学校的时候每看一本js的书籍,我都会把其中的精华记录在我的笔记本中,到现在差不多笔记满满得也有3,4本了,每天光是看着这些心里都很充实,呵呵,学校里面的学习总是那么惬意,工作了,没那么多时间和精力用来写笔记,但是在电脑上敲敲键盘写写博也是一件很fashion的事情,而且公司这边招人也都比较看重博客写的好的童鞋,其实博客写的好的一般都很善于总结,总结的重要性大家应该都比较清楚,每当学习的时候把它记录下来,用到的时候查看一下以前的笔记很快就能回忆起来,避免无谓的反复的学习,而且每当做完一个项目,我的导师和leader都会提醒我做总结(其实挺感谢他们的)。好吧,从今天开始,我也开始用心打造属于属于我的blog,记录我的成长的脚步,以技术会友,如果看到我哪篇博客的朋友觉得对他有帮助,那么我也会感到很欣慰。

  其实在我看到backbone+underscore+jquery+icanhaz(mustache)这一经典的mvc组合之前,我对mvc的理解主要还是集中在后端,在学校里面做的j2ee的项目中用到的ssh的整合开发,接触不多的extjs的mvc模式,以及用的比较熟的ASP.NET的三层架构,ASP.NET MVC,现在貌似都3.0了,如果让我畅想一下mvc在js中的应用应该也是模仿后台这些机制,并把它迁移到js中去(PS:其实js经常被这么应用,单从游戏开发中就可以看出),当我看了backbone的文档后发现正是如此。记得我在入职后第一个mini项目中成功的应用了它,过程很是坎坷,当时也是才接触它一周不到,没有中文文档,遇到问题只有查英文文档和源码,连续加班甚至通宵终于克服了一个又一个技术障碍完成了开发,当时很是激动,我阅读英文文档的能力也在这个过程大幅度提升,(*^__^*) 嘻嘻,大家其实也不用对mvc感到很恐慌,我也只是一个才工作几个月的小菜鸟都可以做到,相信你们也可以的,今天,我就和大家一起一步一步揭开backbone的面纱,总结下自己的学习经历也为backbone的中文社区做些贡献,如果笔记中有哪些我理解上的偏差(这个肯定会有的),也希望大家给我留言来指出,也很感谢大家多多指出我的错误,这样才能共同进步。

  学习backbone前我们先来看下Underscore,因为backbone内部其实是使用Underscore提供的很强大的工具函数,现在离我第一次(也是最后一次)学习backbone有2个多月了,有很多机制,细节也记不太清楚了,不过不要紧,我们一步一步来分析它。首先我们看下Underscore的概况。

  Underscore其实是一个JavaScript工具函数集,它提供了很多有助于提高你编程效率的使用工具函数,这些函数在prototype以及后台的Ruby中都有类似的实现,但是Underscore和prototype相比,它没有改变原生的JavaScript对象,这就使得它也可以和jQuery搭配使用。Underscore提供的60多个函数中包括常见的map,select,invoke以及一些特殊的帮助函数诸如binding,javascript templating,deep equality testing等等,如果在现代的浏览器中有这些函数的相应的实现比如foreach,map,reduce,filter,every,some,indexOf的话就用浏览器提供的实现,如果没的话就用其他函数来完成。Underscore这个开源项目目前是在GitHub上。

  好,我们一点一点来学习它的源码:

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Establish the object that gets returned to break out of a loop iteration.
  var breaker = {};

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var slice = ArrayProto.slice,
      unshift = ArrayProto.unshift,
      toString = ObjProto.toString,
      hasOwnProperty = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeForEach = ArrayProto.forEach,
    nativeMap = ArrayProto.map,
    nativeReduce = ArrayProto.reduce,
    nativeReduceRight = ArrayProto.reduceRight,
    nativeFilter = ArrayProto.filter,
    nativeEvery = ArrayProto.every,
    nativeSome = ArrayProto.some,
    nativeIndexOf = ArrayProto.indexOf,
    nativeLastIndexOf = ArrayProto.lastIndexOf,
    nativeIsArray = Array.isArray,
    nativeKeys = Object.keys,
    nativeBind = FuncProto.bind;

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) { return new wrapper(obj); };

  // Export the Underscore object for **Node.js** and **"CommonJS"**, with
  // backwards-compatibility for the old `require()` API. If we're not in
  // CommonJS, add `_` to the global object.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    // Exported as a string, for Closure Compiler "advanced" mode.
    root['_'] = _;
  }

  // Current version.
  _.VERSION = '1.2.0';

 在闭包中声明了一个root变量,和大多数库差不多,这里就是为了把顶层的window对象的引用放在闭包中,这样做的目的就是缩短查找的范围来节省时间,提高效率,比如jQuery也是把window对象的引用存在闭包中的一个变量。previousUnderscore指向window._,就是如果window._不是underfined,就把它暂时放在 previousUnderscore中以防止覆盖以前的变量。然后又声明了一个breaker对象,暂时先不管它,从第16到38行声明了一些列的变量,他们都是js1.6中的新方法的一个快捷方式,作用其实和那个root变量一样,也是缩短超找时间,用高性能JavaScript中的话说就是对于js中的变量访问,要缓存一切可以缓存的。41行正是Underscore的构造函数,内部是返回一个wrapper对象,46行的作用是为了兼容commonJS,关于commonJS以及seajs这里就不再多提了,大家可以从网上查下相关的资料,我们来看下一段。

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles objects with the built-in `forEach`, arrays, and raw objects.
  // Delegates to **ECMAScript 5**'s native `forEach` if available.
  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

  // Return the results of applying the iterator to each element.
  // Delegates to **ECMAScript 5**'s native `map` if available.
  _.map = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
    each(obj, function(value, index, list) {
      results[results.length] = iterator.call(context, value, index, list);
    });
    return results;
  };

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
    var initial = memo !== void 0;
    if (obj == null) obj = [];
    if (nativeReduce && obj.reduce === nativeReduce) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
    }
    each(obj, function(value, index, list) {
      if (!initial) {
        memo = value;
        initial = true;
      } else {
        memo = iterator.call(context, memo, value, index, list);
      }
    });
    if (!initial) throw new TypeError("Reduce of empty array with no initial value");
    return memo;
  };

  // The right-associative version of reduce, also known as `foldr`.
  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    if (obj == null) obj = [];
    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
      if (context) iterator = _.bind(iterator, context);
      return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
    }
    var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
    return _.reduce(reversed, iterator, memo, context);
  };

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, iterator, context) {
    var result;
    any(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) {
        result = value;
        return true;
      }
    });
    return result;
  };

  // Return all the elements that pass a truth test.
  // Delegates to **ECMAScript 5**'s native `filter` if available.
  // Aliased as `select`.
  _.filter = _.select = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
    each(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) results[results.length] = value;
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    each(obj, function(value, index, list) {
      if (!iterator.call(context, value, index, list)) results[results.length] = value;
    });
    return results;
  };

  // Determine whether all of the elements match a truth test.
  // Delegates to **ECMAScript 5**'s native `every` if available.
  // Aliased as `all`.
  _.every = _.all = function(obj, iterator, context) {
    var result = true;
    if (obj == null) return result;
    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
    each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
    });
    return result;
  };

  // Determine if at least one element in the object matches a truth test.
  // Delegates to **ECMAScript 5**'s native `some` if available.
  // Aliased as `any`.
  var any = _.some = _.any = function(obj, iterator, context) {
    iterator = iterator || _.identity;
    var result = false;
    if (obj == null) return result;
    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
    each(obj, function(value, index, list) {
      if (result |= iterator.call(context, value, index, list)) return breaker;
    });
    return !!result;
  };

  // Determine if a given value is included in the array or object using `===`.
  // Aliased as `contains`.
  _.include = _.contains = function(obj, target) {
    var found = false;
    if (obj == null) return found;
    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
    any(obj, function(value) {
      if (found = value === target) return true;
    });
    return found;
  };

  我们来看each函数的实现,如果浏览器支持JavaScript1.6就用1.6原生的foreach来代理实现,如果不支持就遍历,注意这里有个if(obj.length===+obj.length),可能会有同学对这句不理解,那个“+”号其实就是把后面的东西转换为number类型,然后再去和自己比较,这就确保了当obj有length属性,并且length属性值为number类型时表达式才为真(length为“1”不行)然后进入这个分支(数组和类数组的集合),在对obj的每一项遍历时context为传入的context,第一个参数为当前迭代的值,第二个参数为每次迭代递增的变量i,最后一个参数为obj本身,并且当迭代某一项返回值和我们之前声明的breaker全等的时候退出本次迭代。第二个分支是针对遍历对象的属性的,它遍历对象的每个属性但不访问对象的原型中的属性。有了each后,对于构建后面的函数来说就比较方便了。

  map(obj,iterator,context):对obj的每一项都调用传入的迭代函数iterator,函数的执行结果是返回了一个新数组,新数组中的每一项都为该项在迭代函数中的返回值。

  reduce(obj,iterator,memo,context):reduce其实可以理解为一个有记忆的迭代,通常的forEach只是迭代每一项,但是每一项迭代后对下一项的迭代没影响,而reduce是每次迭代后的结果都会作为第一个参数传入下一次迭代的函数中,这个参数就是memo,Ecmascript5中的reduce为:obj.reduce(iterator,memo),其中memo可选,如果没传memo就是迭代的第一个元素,每次迭代的结果都要return出去。iterator执行的上下文为context,参数为(memo, value, index, list)例如:

1 var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
2 => 6

  reduceRight(obj,iterator,memo,context):和reduce差不多,只不过它是从obj的最右边开始迭代,内部实现其实是把原数组(或类数组集合)先进行一个reverse操作,再调用reduce,当然,如果浏览器原生支持reduceRight就用原生的。

  any|some(obj,iterator,context):迭代过程中只要obj中有一项在iterator中返回true,则any返回true,es5原生支持。

  all|every(obj,iterator,context):迭代过程中必须obj中每一项在iterator中返回true,all,返回true,es5原生支持。

  find(list,iterator,[context]):遍历list的每一项并传入iterator,如果这一项在iterator(value, index, list)中返回true,则返回这一项的值。

  filter|select(obj,iterator,context):遍历obj的每一项,如果这一项在iterator(value,index,list)中返回true,则将该项push进最终返回的数组,函数的执行结果是返回了一个新数组而不是修改原数组。

  include|contains(obj,target):如果obj中包含target则返回true,否则返回false。

  reject(obj,iterator,context):遍历obj的每一项,如果iterator中返回false,则将该项push进最终返回的数组。

  

  

  

posted on 2011-10-19 19:18  冰王子(等待只为与你相遇)  阅读(4138)  评论(0编辑  收藏  举报