一些优秀的代码分析与学习【一】

所有的代码实例都不是直接copy的源码,都结合了本人的理解添加了原理描述与实例(可以直接运行),通俗易懂。

 

1.jQuery初始化代码段

  技术亮点:jQuery无new化构建、每次jQuery构建的作用域隔离、jQuery拓展插件。

  实现源码:

var jQuery = function(args){
  return new jQuery.fn.init(args);
}
jQuery.fn = jQuery.prototype = {
  init: function(args){
    return this;
  },
  otherFn: function(){}
}
jQuery.fn.init.prototype = jQuery.fn;

  分析:

  每次jQuery函数内部new的作用是隔离作用域。每次构建的都是一个新对象,新对象obj如果重写obj.__proto__下的属性不影响其他jQuery对象__proto__下的属性。

var m = jQuery(),n= jQuery();//m = {__proto__:{init:fn(),otherFn:fn()}};n = {__proto__:{init:fn(),otherFn:fn()}}
m.otherFn;//fn(){}
m.otherFn = "test";//m =  {otherFn:"test",__proto__:{init:fn(),otherFn:fn()}};
n.otherFn;//fn(){}; n的结构n = {__proto__:{init:fn(),otherFn:fn()}}

  然后jQuery.fn提供jQuery对象的拓展,只要使用jQuery.fn.xx定义的属性在通过var m = jQuery();方式获取的对象m都可以通过m.xx获取到。如果直接使用jQuery.prototype来提供拓展给人感觉不太友好。

  上面的代码换成下面这种写法也是可以的

var jQuery = function(args){
  return new jq(args);
}
function jq(args){
    return this;
}
jQuery.fn = jq.prototype = {
  otherFn: function(){};
}

  多出了一个函数变量jq,jQuery的这种写法只是减少了变量而已。

  关于new的详细点击这里

 

2.Sizzle引擎词法缓存机制

  技术亮点:一个函数实现存、取、缓存数据。

  实现源码:

function createCache() {
  var cache, keys = [];
  return (cache = function( key, value ) {
    // 使用 (key + " ")避免命名冲突,最大缓存createCache.cacheLength:50
    if ( keys.push( key += " " ) > createCache.cacheLength ) {
      // 去掉最老的记录
      delete cache[ keys.shift() ];
    }
    return (cache[ key ] = value);
  });
}
createCache.cacheLength  = 50;


//例子
var tokenCache = createCache(); //设置缓存 tokenCache("name", "chua"); //tokenize函数中获取缓存 cached = tokenCache[ "name" + " " ];//"chua"

  分析:

  局部变量cache使用函数表达式方式赋值,cache函数中给cache这个函数对象添加要缓存的属性。可以说是把函数对象运用到了极致。

 

3.jQuery判断js数据的具体类型

  技术亮点:call的妙用

  实现源码:

var class2type = {} ,
core_toString = class2type.toString; 
jQuery.type = function( obj ) {
  if ( obj == null ) {
    return String( obj );
  }
  return typeof obj === "object" || typeof obj === "function" ?
  class2type[ core_toString.call(obj) ] || "object" :
  typeof obj;
}
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
  class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

   分析:

  首先我们知道toString()函数用于将当前对象以字符串的形式返回。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法。而Object调用toString方法返回"[object ObjectName]",其中ObjectName是对象类型的名称。然后jQuery利用这一特性将准备检测的数据代入object的toString方法替换掉了其上下文环境为要检测的数据。

 

4.Dean Edwards的base2格式化函数

  技术亮点:arguments的灵活运用

  实现源码:

function format(string) {
  var args = arguments;
  var pattern = new RegExp('%([1-' + arguments.length + '])', 'g');
  return String(string).replace(pattern, function(match, index,position,all) {  
    console.log(match + ' ' + index + ' ' + position + ' ' + all);
    return args[index];  
  });  
};

//实例
var name = "chua",
age = 20,
expr = "%1的年龄是%2";
format(expr,name,age);//"chua的年龄是20"
//控制台打印
//%1 1 0 %1的年龄是%2
//%2 2 6 %1的年龄是%2

  分析:这个比较简单就不分析了。主要就是arguments.length的运用。

   

5.jQuery节点排序

  技术亮点:docElem.compareDocumentPosition的灵活运用

  实现源码:

function sortby( a, b ) {
    var compare;

    if ( a === b ) {return 0;}

    if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
    //如果b在a后面,那么compare的比特位至少是0?0100,?表示可能是0,也可能是1
    return compare & 4 ? -1 : 1;
    }
  //最后的容错处理,如果节点a不包含compareDocumentPosition方法,我们认为是非法节点,直接放在数组最后。
    return a.compareDocumentPosition ? -1 : 1;
}

  分析:

  这里的sorby函数是arrayObject.sort(sortby)的比较函数sortby。具体的arrayObject.sort与docElem.compareDocumentPosition的详解点击这里查找

  里面主要就是将compareDocumentPosition方法用到恰到好处,可能有很多童鞋不了解这个方法的可以学习一下,这个函数特别强大。而且该函数返回比特位的方式在实际运用中我们也可以借鉴。

 

6.jQuery延时对象的promise方法

  技术亮点:提供对外方法同时屏蔽私有接口

  实现源码:

function Deferred(){
  var state = true,
  promise = {
    state: function() {
      return state;
    },
    promise: function( obj ) {
      return obj != null ? jQuery.extend( obj, promise ) : promise;
    }
  },
  deferred = {};    

  deferred.resolve = "resolve function";
  promise.promise( deferred );
  
  return deferred;
}

//实例
var Deff = Deferred();
//Deff = {resolve:"resolve function", state:function(), promise:function()}
var test = {tt:"test"};
Deff.promise(test);
//test = {tt:"test", state:function(), promise:function()}
//test不能访问resolve方法了

  分析:

  通过promise给相应的对象组装Deferred对外提供的属性,屏蔽私有属性。见实例可知,Deff.promise(test);执行后test可以访问Deferred对外提供的方法state,但是不能访问Deferred的私有方法resolve了。

  另外延时对象的实现思想还是挺有意思,也很有学习意义,可以按序学习

  jQuery-1.9.1源码分析系列(五) 回调对象

  jQuery-1.9.1源码分析系列(六) 延时对象

  jQuery-1.9.1源码分析系列(六) 延时对象续——辅助函数jQuery.when

  

7.jQuery处理兼容的钩子(hook)机制

  技术亮点:所有可能出现的分支情况全部使用对象属性来存取。

  实现源码:

//属性钩子对象(所有的属性钩子都放在里面)
var attrHooks = {
  //属性为type的钩子
  type: {
    //操作为set的钩子
    set: function( elem, value ) {
      if ( value === "radio" && elem.nodeName.toLowerCase() === "input" ) {
        var val = elem.value;
        elem.setAttribute( "type", value );
        if ( val ) { elem.value = val; }
        return value;
      }
    }
  },
  other:{}
}

//使用例子
var attr = "type";
var hooks =  attrHooks[attr];
var value = 1;
var elem = document.querySelector("input[type='radio']");
if(hooks && "set" in hooks && (ret = hooks.set( elem, value)) !== undefined ){
    alert(ret);
}

  例子中添加上相应html代码即可用。

  分析:

  以往我们判断分支的时候使用一大堆if/else或Switch/case语句,使得执行代码很长。更省力的方法是将这些分支添加到对象obj上,直接通过obj[branch]方式直接调用对应的分支处理即可。

 

8.Dean Edwards的跨浏览器AddEvent()设计

  技术亮点:实现同一事件绑定多个处理函数

  代码实现:

//事件添加方法
function addEvent(element, type, handler) {
  //保证每个不同的事件响应函数只有唯一一个id
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
 
  // 给element维护一个events属性,初始化为一个空对象。  
    // element.events的结构类似于 { "click": {...}, "dbclick": {...}, "change": {...} }  
    if (!element.events) element.events = {};
 
  // 试图取出element.events中当前事件类型type对应的对象(这个对象更像数组),赋值给handlers
  //如果element.events中没有当前事件类型type对应的对象则初始化
    var handlers = element.events[type];
  if (!handlers) {
     handlers = element.events[type] = {};
 
     // 如果这个element已经有了一个对应的事件的响应方法,例如已经有了onclick方法
        // 就把element的onclick方法赋值给handlers的0元素,此时handlers的结构就是:
        // { 0: function(e){...} },这也是为什么addEvent.guid初始化为1的原因,预留看为0的空间;
        // 此时element.events的结构就是: { "click": { 0: function(e){...} },  /*省略其他事件类型*/ } 
        if (element["on" + type]) {
           handlers[0] = element["on" + type];
        }
    }
    
  // 把当前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是从1开始累加的    
  //因此,这是handlers的结构可能就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
    handlers[handler.$$guid] = handler;
 
  //下文定义了一个handleEvent(event)函数,将这个函数,绑定到element的type事件上作为事件入口。
  //说明:在element进行click时,将会触发handleEvent函数,handleEvent函数将会查找element.events,并调用相应的函数。可以把handleEvent称为“主监听函数”
    element["on" + type] = handleEvent;
};
 
//计数器
addEvent.guid = 1;
 
function removeEvent(element, type, handler) {
    // delete the event handler from the hash table
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};
 
function handleEvent(event) {
  //兼容ie
  event = event || window.event;
  //this是响应事件的节点,这个接点上有events属性(在addEvent中添加的)
  //获取节点对应事件响应函数列表
    var handlers = this.events[event.type];
    // 循环响应函数列表执行
  for (var i in handlers) {
         //保持正确的作用域,即this关键字
     this.$$handleEvent = handlers[i];
        this.$$handleEvent(event);
    }
};
View Code

  分析:

  详细的分析可以点击jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构查看。我们这里分析的是他的实现思路:将添加的事件放在一个单独的属性

events中,而直接绑定到事件上的处理函数为我们定义的函数handleEvent,handleEvent中遍历events中对应的处理函数并执行。中间多了一个处理handleEvent来实现多个函数的绑定这种思想影响了后来的jQuery,这种添加中间控制层的思想影响了一大批js框架开发者,比如说react的虚拟DOM。

 

  待续。。。

 

  如果觉得本文不错,请点击右下方【推荐】!

posted @ 2016-03-23 11:57  chua1989  阅读(2671)  评论(2编辑  收藏  举报