[转载]Android ImageView如何加载网络图片资源 - 尝尽世间绚烂 - ITeye技术网站

mikel阅读(967)

[转载]Android ImageView如何加载网络图片资源 – 尝尽世间绚烂 – ITeye技术网站.

package com.android.antking.imageview;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {
	//定义一个图片显示控件
	private ImageView imageView;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //图片资源
        String url = "http://s16.sinaimg.cn/orignal/89429f6dhb99b4903ebcf&690";
        //得到可用的图片
        Bitmap bitmap = getHttpBitmap(url);
        imageView = (ImageView)this.findViewById(R.id.imageViewId);
        //显示
        imageView.setImageBitmap(bitmap);
       
    }
    /**
     * 获取网落图片资源 
     * @param url
     * @return
     */
    public static Bitmap getHttpBitmap(String url){
    	URL myFileURL;
    	Bitmap bitmap=null;
    	try{
    		myFileURL = new URL(url);
    		//获得连接
    		HttpURLConnection conn=(HttpURLConnection)myFileURL.openConnection();
    		//设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制
    		conn.setConnectTimeout(6000);
    		//连接设置获得数据流
    		conn.setDoInput(true);
    		//不使用缓存
    		conn.setUseCaches(false);
    		//这句可有可无,没有影响
    		//conn.connect();
    		//得到数据流
    		InputStream is = conn.getInputStream();
    		//解析得到图片
    		bitmap = BitmapFactory.decodeStream(is);
    		//关闭数据流
    		is.close();
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    	
		return bitmap;
    	
    }
}

[转载]TortoiseSVN 合并操作简明教程 - Roy Cheng - 博客园

mikel阅读(1002)

[转载]TortoiseSVN 合并操作简明教程 – Roy Cheng – 博客园.

下列步骤展示了如何将分支A中的修改合并到分支B

1.分支B的本地副本目录中选择合并(Merge

2.选择“合并一个版本范围(Merge a range of revisions)”,点击下一步。

3.输入分支Asvn服务器端路径(注意:此路径应与步骤1中的分支B本地路径相对应)。

4.选择需要合并的修改内容,并点击下一步。

5.合并(Merge

6.解决冲突(如果有的话),提交代码。完成!

38分钟,100亿,什么概念!!!!

mikel阅读(1173)

双十一还没结束,交易额已经让人大呼见鬼了!!

38分钟,100亿,什么概念!!一分钟平均2.63亿!!一秒钟438.33333万!!!

阿里你们用的是啥服务器?! 这是我第一反应,这么大的交易量,还能正常运转,可见技术实力多强,10年积累的技术团队来支持这么一个庞大的平台运转正常,实属不易!!

除了这些贡献交易额的女人们,马云背后的男人们更让人感动!!

但是最悲催的是今天的光棍儿们,活生生的狂欢节被阿里一下子弄成了购物节!买啥啥特价,线上线下都是人,这让光棍儿们情何以堪啊!!!

双十一还在继续,我们拭目以待最终的成交量,就算是拧拧水份,怎么也得上百亿不成问题了,考验全国物流的时刻随着双十一的落幕即将开启,然后是各家的客服能力!!!

电商从网络到线下的任督二脉还有多少症结没有打开,就让双十一来试试,电商到底哪家强吧!?

每年双十一貌似都像是互联网给传统企业的上的一次大课,用成交量来证明,互联网时代来了,赶紧触网要不然就分不到一杯羹了!!!

也是再教育消费者,现在人消费都理性了,不再那么刚需驱动,没有啥必须要买的,今天我逛了逛发觉自己真不知道买啥,看着热火朝天的天猫界面,当个看客,总觉得这巨大交易量的背后是不是缺点儿什么?

至于缺啥,一时半会儿说不清楚,总感觉看着那些商品页面,没啥人情味在里面,就是买卖!

那些改变生活的技术

mikel阅读(940)

智能手机真的改变了人们的生活方式,如果手机突然没电了,在中国真得是彻底消失了,因为没有固定电话亭了,那么没有手机支付,是不是就没有团购了呢?

还真的的是那样,阿里创造了个支付帝国,最近又推出5000元透支阿里金融计划,真得是拼了。

未来技术会更加平民化,人人都能编程做网站已经基本实现了,下一步就是人人都能做应用了,最近起来很多第三方开发平台,盯着移动应用开发这块蛋糕,不知道下一个蓝海市场是不是就在此。

传统行业都在被颠覆,可传统依然是传统不是一时半会儿能颠覆完的,怎么也得有个过渡期,拿餐饮行业来说,雕爷牛腩算是颠覆吗?不完全是,只是一种新的玩儿法而已。

下一步估计要颠覆餐饮行业得是移动应用和大数据,利用微信这个人最多的平台,订餐、团购、在线支付,甚至订包间,然后对接餐厅的管理系统,直接走小米的抢购模式,按需采购蔬菜、酒水等等,然后大厨后台提前操作备料,最后坐等顾客到店品尝了。

那这套系统怎么收费?收费已经没有必要了,中间各个环节节省下来的开支,提高得效率,免费就ok,然后建立一个线上线下的推荐平台,羊毛出在猪身上才赚钱,想吃饭少不了酒水,那就在线订餐推荐酒水,想被推荐那就竞价推荐位吧。

总而言之,技术只是个辅助,重要的还是运营和模式。

[转载]Ecshop的transport.js/run() error:undefined_hello_新浪博客

mikel阅读(801)

在使用ECshop的AJAX(即:transport.js)

IE有时候会出现:ReferenceError: process_request is not defined,FF则出现:transport.js/run() error:undefined,其实这完全和transport.js无关。那么问题出在哪里呢?

(1)首先找到445-447行发现了这两句:

/* 定义两个别名 */
var Ajax = Transport;
Ajax.call = Transport.run;

(2)然后再找到735发现了这句:

Ajax.onRunning = showLoader;

哈哈….其实问题就是出在这句的showLoader函数里面。因为在该函数里753行有这段语句

if ( ! obj && process_request),而出现上述问题就是说变量process_request没有定义!!

(3)为什么说该变量没有定义呢?

原因很简单,因为EC很多时候都是将这句话:

<script type=”text/JavaScript”>
var process_request = “{$lang.process_request}”;

…..

</script>

放在最后面的….在中途有其他什么处理、JS载入、JS运行而还没运行到最下面的时候当然就出现在上述的错误!!!

process_request这个变量到底有什么用呢?其实就是为了创建一个DIV层显示“正在处理您的请求…”这个信息!!为什么要这样实现呢?因为这样可以支持多语言。

解决办法:

(1)在showLoader函数体里面加入这句var process_request = ‘正在处理您的请求…’;英文的话就改成英文了。。。

(2)将var process_request = “{$lang.process_request}”;这句话放在<header>下面的<script>里面

(3)重新定义Ajax.onRunning这一事件例如可以自己新建一个函数run,Ajax.onRunning = run

第二种方法:对于这个问题,官方管理员的回复是”有时ajax抓取订单信息不全,出现这样的错误信息,但这个不会影响您的正常使用,请您放心。如果要关 闭,可以在后台里的商店设置 基本设置中去除。”,实际上,按照官方这个说法,问题还是照样存在,解决的办法是“眼不见,心不烦”,将这条弹出语句注释掉: 找到js/transport.js,第227行: alert(this.filename + “/run() error:” + ex.description); 复制代码将之改为: /* alert(this.filename + “/run() error:” + ex.description); */

[转载]Prototype之详细解说[转] - Sam Lin - 博客园

mikel阅读(1041)

[转载]Prototype之详细解说[转] – Sam Lin – 博客园.

/*  Prototype JavaScript framework, version 1.4.0

 *  (c) 2005 Sam Stephenson <sam@conio.net>

 *

 *  Prototype is freely distributable under the terms of an MIT-style license.

 *  For details, see the Prototype web site: http://prototype.conio.net/

 

 *  这是一个JavaScript的框架,致力于简化动态的Web开发,完全按照面对对象的思想

 *  进行Javascript开发,添加了迭代器的概念增强Javascript的设计能力。

 *  Prototype框架构思独特充满了技巧性的代码,方便易用的工具类!

 *

/*--------------------------------------------------------------------------*/

 

  

/* 【Prototype】定义一个全局对象,提供一个全局的基本信息和工具 */

var Prototype = {

  Version: '1.4.0', //可以作为版本检测用

//用于脚本检测的正则表达式,经常使用所以放在这里起到全局常量的作用

  ScriptFragment: '(?:<script.*?>)(( | |.)*?)(?:</script>)',



  emptyFunction: function() {},//空函数

  K: function(x) {return x}//K方法返回参数本身,在后面经常用到

}



/*========================================================================================*/



/*

 *【Class】对象的作用只有一个就是提供了一个定义类的模式

 * 仅含有create 一个方法,返回一个构造函数。 

 * 一般使用如下  

 *     var X = Class.create();  返回一个类型  

 * 要使用 X 类型,需继续用 new X()来获取一个实例 这与C# JAVA类似

 * 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。 

 * 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法,可以看作是一个抽象方法。 

 * 从C#角度讲可以理解为用Class.create()创建一个继承Object基类的类。 

 * 

 */ 



var Class = {

  create: function() {

    return function() {  //下面使用Apply方法传递参数是一个常用的技巧

      this.initialize.apply(this, arguments);

    }

  }

}



/*========================================================================================*/





//Abstract是一个空对象,它的作用仅仅是作为一个抽象的命名空间,所有在该对象下定义的类都是抽象类

//从而从形式上与实体类分开

var Abstract = new Object();



/*

 *Object是所有对象的基类,这个基类有两个静态方法

 */ 

//把Source的所有属性和方法传递给Destination,实现了继承的效果

Object.extend = function(destination, source) {

  for (property in source) {

    destination[property] = source[property];

  }

  return destination;

}

//观察Object的组成,需要参数中的object实现自己的inspect方法

Object.inspect = function(object) {

  try {

    if (object == undefined) return 'undefined';

    if (object == null) return 'null';

    return object.inspect ? object.inspect() : object.toString();

  } catch (e) {

    if (e instanceof RangeError) return '';

    throw e;

  }

}



/*========================================================================================*/





//【Function】是所有函数对象的基类,可以通过对它的扩展实现对所有函数对象添加功能

/* 这里在绑定的时候旧可以传递参数而不是调用的时候才指定参数,bind方法接收多个参数

  将函数绑定到第一个参数指定的对象上,并返回该方法的调用句柄

*/

Function.prototype.bind = function() {

  // $A()方法的作用是把参数专为数组 

  //数组的shift方法作用是删除数组的第一个元素

  var __method = this, args = $A(arguments), object = args.shift();

  return function() {

    return __method.apply(object, args.concat($A(arguments)));

    //这里的$A(arguments)是条用返回函数句柄时传递的参数,不是bind方法的参数

  }

}

/** 

 * 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象 

 * 好像是重载了_method方法

 */ 



Function.prototype.bindAsEventListener = function(object) {

  var __method = this;

  return function(event) {

    return __method.call(object, event || window.event);

  }

}



/*========================================================================================*/



//【Function】是所有数值类型的基类



Object.extend(Number.prototype, {

  toColorPart: function() {           //RGB-》16进制颜色

    var digits = this.toString(16);

    if (this < 16) return '0' + digits;

    return digits;

  },



  succ: function() { //数值加一 var a=1; var b=a.succ; 那么b=2

    return this + 1;

  },



  times: function(iterator) { 

  //这里的参数iterator是一个迭代器作用就是循环调用指定次数的iterator函数

  //$R(start,end,exclusive)是创建ObjectRnge对象的快捷方式

    $R(0, this, true).each(iterator);

    return this;

  }

});



/*========================================================================================*/



//提供了一个方法these,要求参数是函数的句柄,可以有多个。作用就是返回第一个成功执行的函数的返回值

var Try = {

  these: function() {

    var returnValue;



    for (var i = 0; i < arguments.length; i++) {

      var lambda = arguments[i];

      try {

        returnValue = lambda();

        break;

      } catch (e) {}

    }



    return returnValue;

  }

}



/*========================================================================================*/



//定时器类用来实现Window.setInterval的效果,在给定时间间隔执行某一个函数,增加了对重复执行的控制S

var PeriodicalExecuter = Class.create();

PeriodicalExecuter.prototype = {

  initialize: function(callback, frequency) {

  //构造函数指定回调函数和执行频率,单位是秒

    this.callback = callback;

    this.frequency = frequency;

    this.currentlyExecuting = false;



    this.registerCallback();

  },

  

/* 开始调用定时器,无需显示调用,在构造函数中就实现了自动调用,这一下面的

this.onTimerEvent.bind(this)如果写成this.onTimerEvent则this指针就会指向widows对象即setInterval的默认对象

从而不能正确的引用到下面两个函数上,也就失去了对正在执行函数的控制

*/



  registerCallback: function() {

    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);

  },

//下面的函数相当于是回调函数的一个代理, setInterval是到了指定的时间就会强制执行而这里

//加入了一个判断如果callback函数执行的时间超过了一个时间片,则阻止其被重复执行

  onTimerEvent: function() {

    if (!this.currentlyExecuting) {

      try {

        this.currentlyExecuting = true;

        this.callback();

      } finally {

        this.currentlyExecuting = false;

      }

    }

  }

}

/*使用举例:

function GetOnlineCount()

{//获得在线人数

}

new PeriodicalExecuter(GetOnlineCount,10)每10秒获取一次

*/



/*========================================================================================*/

/* 框架核心内容--------【基础工具类】

/*========================================================================================*/



 /*document.getElementById(id)获取一个指定ID的结点,是这个方法的快捷方式和扩展

 可以指定多个参数返回一个对象数组。参数也不一定是ID也可以是对象本身的引用,例如

 $('id')等价于$($('id'))

  */

function $() {

  var elements = new Array();



  for (var i = 0; i < arguments.length; i++) {

    var element = arguments[i];

    if (typeof element == 'string')

      element = document.getElementById(element);



    if (arguments.length == 1)

      return element;



    elements.push(element);

  }



  return elements;

}



/*========================================================================================*/

//【String】类的扩展



//删除字符串中的HTML标记

Object.extend(String.prototype, {

  stripTags: function() {

    return this.replace(/</?[^>]+>/gi, '');

  },

//删除字符串中的脚本块

  stripScripts: function() {

    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');

  },

//提取字符串中的所有脚本块,作为数组返回,每一个脚本作为一个数组元素

  extractScripts: function() {

    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');

    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');

    return (this.match(matchAll) || []).map(function(scriptTag) {

      return (scriptTag.match(matchOne) || ['', ''])[1];

    });

  },

//提取字符串中的脚本并执行

  evalScripts: function() {

    return this.extractScripts().map(eval);

  },

//将字符串进行html编码例如"<" --》"&lt"

  escapeHTML: function() {

    var div = document.createElement('div');

    var text = document.createTextNode(this);

    div.appendChild(text);

    return div.innerHTML;

  },

//对字符串进行html解码例如"&lt"--》"<"

  unescapeHTML: function() {

    var div = document.createElement('div');

    div.innerHTML = this.stripTags();

    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';

  },

//将查询字符串格式的字符串转换为键值对数组

//例如var s="a=1&b=2&c=3"; var s2=s.toQueryParams(); s2为{a:1,b:2,c:3}

  toQueryParams: function() {

    var pairs = this.match(/^??(.*)$/)[1].split('&');

    return pairs.inject({}, function(params, pairString) {

      var pair = pairString.split('=');

      params[pair[0]] = pair[1];

      return params;

    });

  },

//字符串转换为字符数组 "abc"-->['a','b','c']

  toArray: function() {

    return this.split('');

  },

//连字符--》骆驼样式 'good-man'-->'goodMan'

  camelize: function() {

    var oStringList = this.split('-');

    if (oStringList.length == 1) return oStringList[0];



    var camelizedString = this.indexOf('-') == 0

      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)

      : oStringList[0];



    for (var i = 1, len = oStringList.length; i < len; i++) {

      var s = oStringList[i];

      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);

    }



    return camelizedString;

  },

//得到字符串的组成结构

  inspect: function() {

    return "'" + this.replace('\', '\\').replace("'", '\'') + "'";

  }

});

//定义了一个等价的函数

String.prototype.parseQuery = String.prototype.toQueryParams;



/*========================================================================================*/



//【Enumerable】可枚举接口 是整个1.4.0框架的核心工具,所有实现此接口的类必须要实现_each(iterator)方法





var $break    = new Object(); //首先定义了两个异常对象,用于进行迭代计算的控制

var $continue = new Object();



var Enumerable = {

//用于对对象的每一个元素遍历执行iterator迭代器函数

  each: function(iterator) {

    var index = 0;                //可选参数表示元素在枚举对象的次序

    try {

      this._each(function(value) {//value是枚举元素的值

        try {

          iterator(value, index++);

        } catch (e) {

          if (e != $continue) throw e;

        }

      });

    } catch (e) {

      if (e != $break) throw e;

    }

  },

//判断是否所有的枚举元素都能使iterator返回true

  all: function(iterator) {

    var result = true;

    this.each(function(value, index) {

      result = result && !!(iterator || Prototype.K)(value, index);

      if (!result) throw $break;

    });

    return result;

  },

//判断是否有枚举元素能使iterator返回true,有一个就是True

  any: function(iterator) {

    var result = true;

    this.each(function(value, index) {

      if (result = !!(iterator || Prototype.K)(value, index))

        throw $break;

    });

    return result;

  },

//对所有的枚举元素执行iterator迭代器函数 结果作为一个数组返回

  collect: function(iterator) {

    var results = [];

    this.each(function(value, index) {

      results.push(iterator(value, index));

    });

    return results;

  },

//第一个素能使iterator返回true的枚举元素的值,没有返回undefined

  detect: function (iterator) {

    var result;

    this.each(function(value, index) {

      if (iterator(value, index)) {

        result = value;

        throw $break;

      }

    });

    return result;

  },

//找到所有的能使iterator迭代器函数返回true的枚举元素 作为一个数组返回

  findAll: function(iterator) {

    var results = [];

    this.each(function(value, index) {

      if (iterator(value, index))

        results.push(value);

    });

    return results;

  },

//找到素有匹配pattern的枚举元素,结果作为数组返回,iterator可选,如果不指定旧返回素有匹配pattern的枚举元素

  grep: function(pattern, iterator) {//正则模式 迭代器

    var results = [];

    this.each(function(value, index) {

      var stringValue = value.toString();

      if (stringValue.match(pattern))

        results.push((iterator || Prototype.K)(value, index));

    })

    return results;

  },

//判断枚举对象中是否含有参数Object指定的值

  include: function(object) {

    var found = false;

    this.each(function(value) {

      if (value == object) {

        found = true;

        throw $break;

      }

    });

    return found;

  },

//将memo作为iterator的第一个参数,枚举元素作为iterator的第二个参数,枚举元素的次序作为第三个

//参数每次迭代器的返回值将作为下一个iterator的memo参数,从而所有的迭代执行都通过memo联系起来了



  inject: function(memo, iterator) {

    this.each(function(value, index) {

      memo = iterator(memo, value, index);

    });

    return memo;

  },

//对所有的枚举元素执行method方法 后面是要传递的参数

  invoke: function(method) {

    var args = $A(arguments).slice(1);

    return this.collect(function(value) {

      return value[method].apply(value, args);

    });

  },

//返回的最大的迭代器执行结果

  max: function(iterator) {

    var result;

    this.each(function(value, index) {

      value = (iterator || Prototype.K)(value, index);

      if (value >= (result || value))

        result = value;

    });

    return result;

  },

//反之

  min: function(iterator) {

    var result;

    this.each(function(value, index) {

      value = (iterator || Prototype.K)(value, index);

      if (value <= (result || value))

        result = value;

    });

    return result;

  },

//返回两个数组一组能使iterator返回true 另一组返回false

  partition: function(iterator) {

    var trues = [], falses = [];

    this.each(function(value, index) {

      ((iterator || Prototype.K)(value, index) ?

        trues : falses).push(value);

    });

    return [trues, falses];

  },

//获取所有枚举元素的property属性值作为数组的返回

  pluck: function(property) {

    var results = [];

    this.each(function(value, index) {

      results.push(value[property]);

    });

    return results;

  },

//与findall相反 

  reject: function(iterator) {

    var results = [];

    this.each(function(value, index) {

      if (!iterator(value, index))

        results.push(value);

    });

    return results;

  },

//根据iterator的结果排序 最小的在前面 作为数组返回

  sortBy: function(iterator) {

    return this.collect(function(value, index) {

      return {value: value, criteria: iterator(value, index)};

    }).sort(function(left, right) {

      var a = left.criteria, b = right.criteria;

      return a < b ? -1 : a > b ? 1 : 0;

    }).pluck('value');

  },

//枚举对象--》数组

  toArray: function() {

    return this.collect(Prototype.K);

  },

//接收多个枚举对象参数,最后一个可以是迭代器用于阵列转换

  zip: function() {

    var iterator = Prototype.K, args = $A(arguments);

    if (typeof args.last() == 'function')

      iterator = args.pop();



    var collections = [this].concat(args).map($A);

    return this.map(function(value, index) {

      iterator(value = collections.pluck(index));

      return value;

    });

  },

//返回枚举对象的字符串描述

  inspect: function() {

    return '#<Enumerable:' + this.toArray().inspect() + '>';

  }

}

//等价函数定义

Object.extend(Enumerable, {

  map:     Enumerable.collect,

  find:    Enumerable.detect,

  select:  Enumerable.findAll,

  member:  Enumerable.include,

  entries: Enumerable.toArray

});



/*========================================================================================*/

//【Array】数组对象



//参数转换为数组,如果参数定义了toarray则直接调用,否则枚举获得数组

var $A = Array.from = function(iterable) {

  if (!iterable) return [];

  if (iterable.toArray) {

    return iterable.toArray();

  } else {

    var results = [];

    for (var i = 0; i < iterable.length; i++)

      results.push(iterable[i]);

    return results;

  }

}

//数组对象继承了Enumerable接口

Object.extend(Array.prototype, Enumerable);



Array.prototype._reverse = Array.prototype.reverse;



Object.extend(Array.prototype, {

//实现了枚举接口方法,用于对数组内的元素执行迭代器

  _each: function(iterator) {

    for (var i = 0; i < this.length; i++)

      iterator(this[i]);

  },

//清空数组

  clear: function() {

    this.length = 0;

    return this;

  },

//取得第一个元素

  first: function() {

    return this[0];

  },

//取得最后一个元素

  last: function() {

    return this[this.length - 1];

  },

//删除数组元素中所有的null undefined值 作为新数组返回,原数组不受影响

  compact: function() {

    return this.select(function(value) {

      return value != undefined || value != null;

    });

  },

//展开所有数组元素不再嵌套

  flatten: function() {

    return this.inject([], function(array, value) {

      return array.concat(value.constructor == Array ?

        value.flatten() : [value]);

    });

  },

//数组中删除指定的元素,返回删除后的结果,原数组不受影响

  without: function() {

    var values = $A(arguments);

    return this.select(function(value) {

      return !values.include(value);

    });

  },

//Value元素在数组中的索引值

  indexOf: function(object) {

    for (var i = 0; i < this.length; i++)

      if (this[i] == object) return i;

    return -1;

  },

//反转数组

  reverse: function(inline) {

    return (inline !== false ? this : this.toArray())._reverse();

  },

//删除数组中第一个元素并返回这个元素的值

  shift: function() {

    var result = this[0];

    for (var i = 0; i < this.length - 1; i++)

      this[i] = this[i + 1];

    this.length--;

    return result;

  },

//得到数组的字符串描述例如arr=[1,2,3]-----> "[1,2,3]"

  inspect: function() {

    return '[' + this.map(Object.inspect).join(', ') + ']';

  }

});



/*========================================================================================*/

//【Hash】哈希对象





var Hash = {

//实现枚举接口方法从而使Hash对象也是枚举对象

  _each: function(iterator) {

    for (key in this) {

      var value = this[key];

      if (typeof value == 'function') continue;



      var pair = [key, value];

      pair.key = key;

      pair.value = value;

      iterator(pair);

    }

  },

//所有键数组

  keys: function() {

    return this.pluck('key');

  },

//所有值数组

  values: function() {

    return this.pluck('value');

  },

//合并哈希表,键相同就覆盖调用者

  merge: function(hash) {

    return $H(hash).inject($H(this), function(mergedHash, pair) {

      mergedHash[pair.key] = pair.value;

      return mergedHash;

    });

  },

//键值对组成查询字符串的形式

  toQueryString: function() {

    return this.map(function(pair) {

      return pair.map(encodeURIComponent).join('=');

    }).join('&');

  },

//获取Hash对象的字符串描述

  inspect: function() {

    return '#<Hash:{' + this.map(function(pair) {

      return pair.map(Object.inspect).join(': ');

    }).join(', ') + '}>';

  }

}

/*哈希表不是类而是对象所以不能用new的方法创建,而是用$H()函数

  这个方法吧一个对象转换为哈希对象,对象的属性名为key 值为value

  可枚举!!

*/

function $H(object) {

  var hash = Object.extend({}, object || {});

  Object.extend(hash, Enumerable);

  Object.extend(hash, Hash);

  return hash;

}



/*========================================================================================*/

//【ObjectRange】用于进行指定次数的循环运算,同样继承于枚举接口并实现_each方法

//有了这个方法基本上Javascript里面就不用for循环了





ObjectRange = Class.create();

Object.extend(ObjectRange.prototype, Enumerable);

Object.extend(ObjectRange.prototype, {

//构造函数同事指定了3个参数的值

  initialize: function(start, end, exclusive) {

    this.start = start; //其实索引

    this.end = end;//结束索引

    this.exclusive = exclusive;//表示是否包含结束索引

  },



  _each: function(iterator) {

    var value = this.start;

    do {

      iterator(value);

      value = value.succ();

    } while (this.include(value));

  },

//重写枚举接口的include方法,判断改循环的索引是否包含参数value的指定的值

  include: function(value) {

    if (value < this.start)

      return false;

    if (this.exclusive)

      return value < this.end;

    return value <= this.end;

  }

});

//使用$R()方法快速创建objectRange对象

var $R = function(start, end, exclusive) {

  return new ObjectRange(start, end, exclusive);

}



/*========================================================================================*/

/* 【Ajax模块】

/*========================================================================================*/

//【ajax对象】



var Ajax = {//创建浏览器兼容的XMLHttpRequest对象

  getTransport: function() {

    return Try.these(

      function() {return new ActiveXObject('Msxml2.XMLHTTP')},

      function() {return new ActiveXObject('Microsoft.XMLHTTP')},

      function() {return new XMLHttpRequest()}

    ) || false;

  },

//当前激活的请求数目

  activeRequestCount: 0

}

//【Ajax.Responders对象】

Ajax.Responders = {

  responders: [],//表示所有响应处理代理



  _each: function(iterator) {

    this.responders._each(iterator);

  },

//注册一个响应代理

  register: function(responderToAdd) {

    if (!this.include(responderToAdd))

      this.responders.push(responderToAdd);

  },

//删除一个响应代理

  unregister: function(responderToRemove) {

    this.responders = this.responders.without(responderToRemove);

  },

//分发一个回调函数,让所有半酣callback回调函数事件标识符的响应代理都被调用

//传递三个参数最后一个可选表示Json对象

  dispatch: function(callback, request, transport, json) {

    this.each(function(responder) {

      if (responder[callback] && typeof responder[callback] == 'function') {

        try {

          responder[callback].apply(responder, [request, transport, json]);

        } catch (e) {}

      }

    });

  }

};



Object.extend(Ajax.Responders, Enumerable);

//统计当前活动请求的数目

Ajax.Responders.register({

//开始创建了一个请求

  onCreate: function() {

    Ajax.activeRequestCount++;

  },

//请求结束

  onComplete: function() {

    Ajax.activeRequestCount--;

  }

});



//【Ajax.Base类】进行服务器通信的基类

Ajax.Base = function() {};

Ajax.Base.prototype = {

  setOptions: function(options) {

    this.options = {

      method:       'post',

      asynchronous: true,

      parameters:   ''

    }

    Object.extend(this.options, options || {});

  },



  responseIsSuccess: function() {

    return this.transport.status == undefined

        || this.transport.status == 0

        || (this.transport.status >= 200 && this.transport.status < 300);

  },



  responseIsFailure: function() {

    return !this.responseIsSuccess();

  }

}

//【Ajax.Request类】用于向服务器端发送请求,封装了XmlHttpRequest

Ajax.Request = Class.create();

Ajax.Request.Events =

  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];



Ajax.Request.prototype = Object.extend(new Ajax.Base(), {

  initialize: function(url, options) {

    this.transport = Ajax.getTransport();

    this.setOptions(options);//使用基类方法设置

    this.request(url);

  },



//向指定url发送请求 一般不会显示调用

  request: function(url) {

    var parameters = this.options.parameters || '';

    if (parameters.length > 0) parameters += '&_=';



    try {

      this.url = url;

      if (this.options.method == 'get' && parameters.length > 0)

        this.url += (this.url.match(/?/) ? '&' : '?') + parameters;



      Ajax.Responders.dispatch('onCreate', this, this.transport);



      this.transport.open(this.options.method, this.url,

        this.options.asynchronous);



      if (this.options.asynchronous) {

        this.transport.onreadystatechange = this.onStateChange.bind(this);

        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);

      }



      this.setRequestHeaders();



      var body = this.options.postBody ? this.options.postBody : parameters;

      this.transport.send(this.options.method == 'post' ? body : null);



    } catch (e) {

      this.dispatchException(e);

    }

  },

//设置请求的Http头 类内部使用 一般无需显示调用

  setRequestHeaders: function() {

    var requestHeaders =

      ['X-Requested-With', 'XMLHttpRequest',

       'X-Prototype-Version', Prototype.Version];



    if (this.options.method == 'post') {

      requestHeaders.push('Content-type',

        'application/x-www-form-urlencoded');



      /* Force "Connection: close" for Mozilla browsers to work around

       * a bug where XMLHttpReqeuest sends an incorrect Content-length

       * header. See Mozilla Bugzilla #246651.

       */

      if (this.transport.overrideMimeType)

        requestHeaders.push('Connection', 'close');

    }



    if (this.options.requestHeaders)

      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);



    for (var i = 0; i < requestHeaders.length; i += 2)

      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);

  },

//检测XMLHttpRquest对象的onstatechange事件类内部使用 一般无需显示调用

  onStateChange: function() {

    var readyState = this.transport.readyState;

    if (readyState != 1)

      this.respondToReadyState(this.transport.readyState);

  },

//获取指定的Http头的内容

  header: function(name) {

    try {

      return this.transport.getResponseHeader(name);

    } catch (e) {}

  },

//如果服务器返回了Http头"X-JOSN"则将其作为js执行并返回执行结果

  evalJSON: function() {

    try {

      return eval(this.header('X-JSON'));

    } catch (e) {}

  },

//将XMLHttpRequest返回的responseText作为JS语句执行并返回执行结果

  evalResponse: function() {

    try {

      return eval(this.transport.responseText);

    } catch (e) {

      this.dispatchException(e);

    }

  },

//处理XMLHttpRequest的readystate属性根据成功或失败调用Options对象中相应的回调函数

//同时通知Ajax.Responders中注册的响应处理句柄

  respondToReadyState: function(readyState) {

    var event = Ajax.Request.Events[readyState];

    var transport = this.transport, json = this.evalJSON();



    if (event == 'Complete') {

      try {

        (this.options['on' + this.transport.status]

         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]

         || Prototype.emptyFunction)(transport, json);

      } catch (e) {

        this.dispatchException(e);

      }



      if ((this.header('Content-type') || '').match(/^text/javascript/i))

        this.evalResponse();

    }



    try {

      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);

      Ajax.Responders.dispatch('on' + event, this, transport, json);

    } catch (e) {

      this.dispatchException(e);

    }



    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */

    if (event == 'Complete')

      this.transport.onreadystatechange = Prototype.emptyFunction;

  },



  dispatchException: function(exception) {

    (this.options.onException || Prototype.emptyFunction)(this, exception);

    Ajax.Responders.dispatch('onException', this, exception);

  }

});





/*========================================================================================*/

//【Ajax.Updater类】用于将获取的内容填充到指定的容器中去

Ajax.Updater = Class.create();



Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {

//重写了构造函数

  initialize: function(container, url, options) {

    this.containers = {

      success: container.success ? $(container.success) : $(container),

      failure: container.failure ? $(container.failure) :

        (container.success ? null : $(container))

    }



    this.transport = Ajax.getTransport();

    this.setOptions(options);//可以指定evalscripts属性和insertion属性



    var onComplete = this.options.onComplete || Prototype.emptyFunction;

    this.options.onComplete = (function(transport, object) {

      this.updateContent();

      onComplete(transport, object);

    }).bind(this);



    this.request(url);

  },



  updateContent: function() {

    var receiver = this.responseIsSuccess() ?

      this.containers.success : this.containers.failure;

    var response = this.transport.responseText;



    if (!this.options.evalScripts)

      response = response.stripScripts();



    if (receiver) {

      if (this.options.insertion) {

        new this.options.insertion(receiver, response);

      } else {

        Element.update(receiver, response);

      }

    }



    if (this.responseIsSuccess()) {

      if (this.onComplete)

        setTimeout(this.onComplete.bind(this), 10);

    }

  }

});



/*========================================================================================*/

//【Ajax.PeriodicalUpdater类】用于定时执行服务器异步调用,功能与Updater相同只是有了定时功能



Ajax.PeriodicalUpdater = Class.create();

Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {

  initialize: function(container, url, options) {

    this.setOptions(options);

    this.onComplete = this.options.onComplete;

//区别就在下面两个属性 频率 衰减参数 指数增长!只要有一次不同旧回复原有频率

    this.frequency = (this.options.frequency || 2);

    this.decay = (this.options.decay || 1);



    this.updater = {};

    this.container = container;

    this.url = url;



    this.start();

  },



  start: function() {

    this.options.onComplete = this.updateComplete.bind(this);

    this.onTimerEvent();

  },



  stop: function() {

    this.updater.onComplete = undefined;

    clearTimeout(this.timer);

    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);

  },



  updateComplete: function(request) {

    if (this.options.decay) {

      this.decay = (request.responseText == this.lastText ?

        this.decay * this.options.decay : 1);



      this.lastText = request.responseText;

    }

    this.timer = setTimeout(this.onTimerEvent.bind(this),

      this.decay * this.frequency * 1000);

  },



  onTimerEvent: function() {

    this.updater = new Ajax.Updater(this.container, this.url, this.options);

  }

});





/*========================================================================================*/

/*【文档操作的封装】

/*========================================================================================*/



//返回了一个数组包含了所有符合条件的结点的引用

document.getElementsByClassName = function(className, parentElement) { //parentElement不指定就在全局查找

  var children = ($(parentElement) || document.body).getElementsByTagName('*');

  return $A(children).inject([], function(elements, child) {

    if (child.className.match(new RegExp("(^|\s)" + className + "(\s|$)")))

      elements.push(child);

    return elements;

  });

}



/*【Element对象】用于对文档结点做一些统一的操作



*/



if (!window.Element) {

  var Element = new Object();

}



Object.extend(Element, {

  visible: function(element) {

    return $(element).style.display != 'none';

  },

//指定结点可见性的切换

  toggle: function() {

    for (var i = 0; i < arguments.length; i++) {

      var element = $(arguments[i]);

      Element[Element.visible(element) ? 'hide' : 'show'](element);

    }

  },



  hide: function() {

    for (var i = 0; i < arguments.length; i++) {

      var element = $(arguments[i]);

      element.style.display = 'none';

    }

  },



  show: function() {

    for (var i = 0; i < arguments.length; i++) {

      var element = $(arguments[i]);

      element.style.display = '';

    }

  },



  remove: function(element) {

    element = $(element);

    element.parentNode.removeChild(element);

  },

//将html片段填充到element指定的结点中

  update: function(element, html) {

    $(element).innerHTML = html.stripScripts();

    setTimeout(function() {html.evalScripts()}, 10);

  },

//获得element的绝对高度

  getHeight: function(element) {

    element = $(element);

    return element.offsetHeight;

  },



  classNames: function(element) {

    return new Element.ClassNames(element);

  },



  hasClassName: function(element, className) {

    if (!(element = $(element))) return;

    return Element.classNames(element).include(className);

  },



  addClassName: function(element, className) {

    if (!(element = $(element))) return;

    return Element.classNames(element).add(className);

  },



  removeClassName: function(element, className) {

    if (!(element = $(element))) return;

    return Element.classNames(element).remove(className);

  },



  // removes whitespace-only text node children

  cleanWhitespace: function(element) {

    element = $(element);

    for (var i = 0; i < element.childNodes.length; i++) {

      var node = element.childNodes[i];

      if (node.nodeType == 3 && !/S/.test(node.nodeValue))

        Element.remove(node);

    }

  },



  empty: function(element) {

    return $(element).innerHTML.match(/^s*$/);

  },

//将浏览器的滚动条滚动到指定的结点的位置

  scrollTo: function(element) {

    element = $(element);

    var x = element.x ? element.x : element.offsetLeft,

        y = element.y ? element.y : element.offsetTop;

    window.scrollTo(x, y);

  },

//得到element的结点的绝对样式

  getStyle: function(element, style) {

    element = $(element);

    var value = element.style[style.camelize()];

    if (!value) {

      if (document.defaultView && document.defaultView.getComputedStyle) {

        var css = document.defaultView.getComputedStyle(element, null);

        value = css ? css.getPropertyValue(style) : null;

      } else if (element.currentStyle) {

        value = element.currentStyle[style.camelize()];

      }

    }



    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))

      if (Element.getStyle(element, 'position') == 'static') value = 'auto';



    return value == 'auto' ? null : value;

  },

//设置结点样式 var style={background-color:'black',color:'red'} Element.setStyle($('div1'),style)

  setStyle: function(element, style) {

    element = $(element);

    for (name in style)

      element.style[name.camelize()] = style[name];

  },

  

  //获得结点的宽度高度 该方法无论结点是否可见 都能获得其大小

  getDimensions: function(element) {

    element = $(element);

    if (Element.getStyle(element, 'display') != 'none')

      return {width: element.offsetWidth, height: element.offsetHeight};



    // All *Width and *Height properties give 0 on elements with display none,

    // so enable the element temporarily

    var els = element.style;

    var originalVisibility = els.visibility;

    var originalPosition = els.position;

    els.visibility = 'hidden';

    els.position = 'absolute';

    els.display = '';

    var originalWidth = element.clientWidth;

    var originalHeight = element.clientHeight;

    els.display = 'none';

    els.position = originalPosition;

    els.visibility = originalVisibility;

    return {width: originalWidth, height: originalHeight};

  },

//相对定位

  makePositioned: function(element) {

    element = $(element);

    var pos = Element.getStyle(element, 'position');

    if (pos == 'static' || !pos) {

      element._madePositioned = true;

      element.style.position = 'relative';

      // Opera returns the offset relative to the positioning context, when an

      // element is position relative but top and left have not been defined

      if (window.opera) {

        element.style.top = 0;

        element.style.left = 0;

      }

    }

  },

//取消相对定位

  undoPositioned: function(element) {

    element = $(element);

    if (element._madePositioned) {

      element._madePositioned = undefined;

      element.style.position =

        element.style.top =

        element.style.left =

        element.style.bottom =

        element.style.right = '';

    }

  },

//使结点隐藏超出的部分 overflow=relative

  makeClipping: function(element) {

    element = $(element);

    if (element._overflow) return;

    element._overflow = element.style.overflow;

    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')

      element.style.overflow = 'hidden';

  },



  undoClipping: function(element) {

    element = $(element);

    if (element._overflow) return;

    element.style.overflow = element._overflow;

    element._overflow = undefined;

  }

});





/*========================================================================================*/

//Toggle对象是Element.toggle方法的一个快捷用法

var Toggle = new Object();

Toggle.display = Element.toggle;



/*========================================================================================*/

//【Insertion命名空间】包含了4个类把指定的html片段插入到指定的结点的相应位置上

Abstract.Insertion = function(adjacency) {

  this.adjacency = adjacency;

}



Abstract.Insertion.prototype = {

  initialize: function(element, content) {

    this.element = $(element);

    this.content = content.stripScripts();



    if (this.adjacency && this.element.insertAdjacentHTML) {

      try {

        this.element.insertAdjacentHTML(this.adjacency, this.content);

      } catch (e) {

        if (this.element.tagName.toLowerCase() == 'tbody') {

          this.insertContent(this.contentFromAnonymousTable());

        } else {

          throw e;

        }

      }

    } else {

      this.range = this.element.ownerDocument.createRange();

      if (this.initializeRange) this.initializeRange();

      this.insertContent([this.range.createContextualFragment(this.content)]);

    }



    setTimeout(function() {content.evalScripts()}, 10);

  },



  contentFromAnonymousTable: function() {

    var div = document.createElement('div');

    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';

    return $A(div.childNodes[0].childNodes[0].childNodes);

  }

}



var Insertion = new Object();



Insertion.Before = Class.create();

Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {

  initializeRange: function() {

    this.range.setStartBefore(this.element);

  },



  insertContent: function(fragments) {

    fragments.each((function(fragment) {

      this.element.parentNode.insertBefore(fragment, this.element);

    }).bind(this));

  }

});



Insertion.Top = Class.create();

Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {

  initializeRange: function() {

    this.range.selectNodeContents(this.element);

    this.range.collapse(true);

  },



  insertContent: function(fragments) {

    fragments.reverse(false).each((function(fragment) {

      this.element.insertBefore(fragment, this.element.firstChild);

    }).bind(this));

  }

});



Insertion.Bottom = Class.create();

Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {

  initializeRange: function() {

    this.range.selectNodeContents(this.element);

    this.range.collapse(this.element);

  },



  insertContent: function(fragments) {

    fragments.each((function(fragment) {

      this.element.appendChild(fragment);

    }).bind(this));

  }

});



Insertion.After = Class.create();

Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {

  initializeRange: function() {

    this.range.setStartAfter(this.element);

  },



  insertContent: function(fragments) {

    fragments.each((function(fragment) {

      this.element.parentNode.insertBefore(fragment,

        this.element.nextSibling);

    }).bind(this));

  }

});



/*========================================================================================*/

//【Element.ClassNames】获得一个结点的所有的class名称组成的对象,实现了枚举接口



Element.ClassNames = Class.create();

Element.ClassNames.prototype = {

  initialize: function(element) {

    this.element = $(element);

  },



  _each: function(iterator) {

    this.element.className.split(/s+/).select(function(name) {

      return name.length > 0;

    })._each(iterator);

  },



  set: function(className) {

    this.element.className = className;

  },



  add: function(classNameToAdd) {

    if (this.include(classNameToAdd)) return;

    this.set(this.toArray().concat(classNameToAdd).join(' '));

  },



  remove: function(classNameToRemove) {

    if (!this.include(classNameToRemove)) return;

    this.set(this.select(function(className) {

      return className != classNameToRemove;

    }).join(' '));

  },



  toString: function() {//获得所有class的字符串表示空格隔开

    return this.toArray().join(' ');

  }

}

//表单域对象工具类

Object.extend(Element.ClassNames.prototype, Enumerable);

var Field = {

  clear: function() {

    for (var i = 0; i < arguments.length; i++)

      $(arguments[i]).value = '';

  },



  focus: function(element) {

    $(element).focus();

  },



  present: function() {

    for (var i = 0; i < arguments.length; i++)

      if ($(arguments[i]).value == '') return false;

    return true;

  },

//选中表单域

  select: function(element) {

    $(element).select();

  },

//focus + select

  activate: function(element) {

    element = $(element);

    element.focus();

    if (element.select)

      element.select();

  }

}



/*【Form】表单工具对象*/



var Form = {

//表单数据序列化

  serialize: function(form) {

    var elements = Form.getElements($(form));

    var queryComponents = new Array();



    for (var i = 0; i < elements.length; i++) {

      var queryComponent = Form.Element.serialize(elements[i]);

      if (queryComponent)

        queryComponents.push(queryComponent);

    }



    return queryComponents.join('&');

  },

//获取所有表单域按照标记名排列

  getElements: function(form) {

    form = $(form);

    var elements = new Array();



    for (tagName in Form.Element.Serializers) {

      var tagElements = form.getElementsByTagName(tagName);

      for (var j = 0; j < tagElements.length; j++)

        elements.push(tagElements[j]);

    }

    return elements;

  },

//有一个满足就返回

  getInputs: function(form, typeName, name) {

    form = $(form);

    var inputs = form.getElementsByTagName('input');



    if (!typeName && !name)

      return inputs;



    var matchingInputs = new Array();

    for (var i = 0; i < inputs.length; i++) {

      var input = inputs[i];

      if ((typeName && input.type != typeName) ||

          (name && input.name != name))

        continue;

      matchingInputs.push(input);

    }



    return matchingInputs;

  },

//整个表单域禁用

  disable: function(form) {

    var elements = Form.getElements(form);

    for (var i = 0; i < elements.length; i++) {

      var element = elements[i];

      element.blur();

      element.disabled = 'true';

    }

  },

//启用

  enable: function(form) {

    var elements = Form.getElements(form);

    for (var i = 0; i < elements.length; i++) {

      var element = elements[i];

      element.disabled = '';

    }

  },

//获取第一个可用的表单域

  findFirstElement: function(form) {

    return Form.getElements(form).find(function(element) {

      return element.type != 'hidden' && !element.disabled &&

        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());

    });

  },

//激活第一个表单域

  focusFirstElement: function(form) {

    Field.activate(Form.findFirstElement(form));

  },



  reset: function(form) {

    $(form).reset();

  }

}



//【Form.Element】 对特定的表单域进行操作

Form.Element = {

//序列化形成"&name=value"

  serialize: function(element) {

    element = $(element);

    var method = element.tagName.toLowerCase();

    var parameter = Form.Element.Serializers[method](element);



    if (parameter) {

      var key = encodeURIComponent(parameter[0]);

      if (key.length == 0) return;



      if (parameter[1].constructor != Array)

        parameter[1] = [parameter[1]];



      return parameter[1].map(function(value) {

        return key + '=' + encodeURIComponent(value);

      }).join('&');

    }

  },

//获得指定表单域的值

  getValue: function(element) {

    element = $(element);

    var method = element.tagName.toLowerCase();

    var parameter = Form.Element.Serializers[method](element);



    if (parameter)

      return parameter[1];

  }

}

//【Form.Element.Serializers】针对不同的表单域进行序列化

Form.Element.Serializers = {

//一般用所有的<input>标记的序列化都可以

  input: function(element) {

    switch (element.type.toLowerCase()) {

      case 'submit':

      case 'hidden':

      case 'password':

      case 'text':

        return Form.Element.Serializers.textarea(element);

      case 'checkbox':

      case 'radio':

        return Form.Element.Serializers.inputSelector(element);

    }

    return false;

  },



  inputSelector: function(element) {

    if (element.checked)

      return [element.name, element.value];

  },



  textarea: function(element) {

    return [element.name, element.value];

  },



  select: function(element) {

    return Form.Element.Serializers[element.type == 'select-one' ?

      'selectOne' : 'selectMany'](element);

  },



  selectOne: function(element) {

    var value = '', opt, index = element.selectedIndex;

    if (index >= 0) {

      opt = element.options[index];

      value = opt.value;

      if (!value && !('value' in opt))

        value = opt.text;

    }

    return [element.name, value];

  },



  selectMany: function(element) {

    var value = new Array();

    for (var i = 0; i < element.length; i++) {

      var opt = element.options[i];

      if (opt.selected) {

        var optValue = opt.value;

        if (!optValue && !('value' in opt))

          optValue = opt.text;

        value.push(optValue);

      }

    }

    return [element.name, value];

  }

}



//【获得表单域的值】

var $F = Form.Element.getValue;



/*========================================================================================*/

/*【Observer模式框架】 观察对象仅仅针对表单和表单域*/

/*========================================================================================*/



Abstract.TimedObserver = function() {}//定义了基于定时器的观察模式基类后面的类都继承了这个类

Abstract.TimedObserver.prototype = {

  initialize: function(element, frequency, callback) {

    this.frequency = frequency;//观察频率

    this.element   = $(element);//被观察的对象

    this.callback  = callback;//回调函数接受参数 被观察对象自身和触发事件时的值



    this.lastValue = this.getValue();//这是观察的值!!

    this.registerCallback();

  },

//注册回调函数

  registerCallback: function() {

    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);

  },

//每一个时间片触发一次该事件

  onTimerEvent: function() {

    var value = this.getValue();

    if (this.lastValue != value) {

      this.callback(this.element, value);

      this.lastValue = value;

    }

  }

}



//【Form.Element.Observer类】实现了getvalue方法

Form.Element.Observer = Class.create();

Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {

  getValue: function() {

    return Form.Element.getValue(this.element);

  }

});



//【Form.Observer类】实现了getvalue方法,只要有一个表单域发生变化就会触发回调

Form.Observer = Class.create();

Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {

  getValue: function() {

    return Form.serialize(this.element);

  }

});



//【Abstract.EventObserver 类】基于事件的Oberver模式的基类 后面的具体类都继承这个类



Abstract.EventObserver = function() {}

Abstract.EventObserver.prototype = {

  initialize: function(element, callback) {

    this.element  = $(element);

    this.callback = callback;



    this.lastValue = this.getValue();

    if (this.element.tagName.toLowerCase() == 'form')

      this.registerFormCallbacks();

    else

      this.registerCallback(this.element);

  },

//每次发生事件时被调用 内部调用 无需显示调用

  onElementEvent: function() {

    var value = this.getValue();

    if (this.lastValue != value) {

      this.callback(this.element, value);

      this.lastValue = value;

    }

  },

//注册表表单域事件

  registerFormCallbacks: function() {

    var elements = Form.getElements(this.element);

    for (var i = 0; i < elements.length; i++)

      this.registerCallback(elements[i]);

  },



  registerCallback: function(element) {

    if (element.type) {

      switch (element.type.toLowerCase()) {

        case 'checkbox':

        case 'radio':

          Event.observe(element, 'click', this.onElementEvent.bind(this));

          break;

        case 'password':

        case 'text':

        case 'textarea':

        case 'select-one':

        case 'select-multiple':

          Event.observe(element, 'change', this.onElementEvent.bind(this));

          break;

      }

    }

  }

}

//【Form.Element.EventObserver】

Form.Element.EventObserver = Class.create();

Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {

  getValue: function() {

    return Form.Element.getValue(this.element);

  }

});

//【Form.EventObserver】

Form.EventObserver = Class.create();

Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {

  getValue: function() {

    return Form.serialize(this.element);

  }

});







/*========================================================================================*/

/*【事件处理:对Event对象的扩展】对javascript事件处理机制进行封装*/

/*========================================================================================*/

if (!window.Event) {

  var Event = new Object();

}



Object.extend(Event, {

  KEY_BACKSPACE: 8,

  KEY_TAB:       9,

  KEY_RETURN:   13,

  KEY_ESC:      27,

  KEY_LEFT:     37,

  KEY_UP:       38,

  KEY_RIGHT:    39,

  KEY_DOWN:     40,

  KEY_DELETE:   46,

//获取触发事件的对象

  element: function(event) {

    return event.target || event.srcElement;

  },



  isLeftClick: function(event) {

    return (((event.which) && (event.which == 1)) ||

            ((event.button) && (event.button == 1)));

  },



  pointerX: function(event) {

    return event.pageX || (event.clientX +

      (document.documentElement.scrollLeft || document.body.scrollLeft));

  },



  pointerY: function(event) {

    return event.pageY || (event.clientY +

      (document.documentElement.scrollTop || document.body.scrollTop));

  },

//停止事件的传递

  stop: function(event) {

    if (event.preventDefault) {

      event.preventDefault();

      event.stopPropagation();

    } else {

      event.returnValue = false;

      event.cancelBubble = true;

    }

  },



  // find the first node with the given tagName, starting from the

  // node the event was triggered on; traverses the DOM upwards

  findElement: function(event, tagName) {

    var element = Event.element(event);

    while (element.parentNode && (!element.tagName ||

        (element.tagName.toUpperCase() != tagName.toUpperCase())))

      element = element.parentNode;

    return element;

  },



  observers: false,



  _observeAndCache: function(element, name, observer, useCapture) {

    if (!this.observers) this.observers = [];

    if (element.addEventListener) {

      this.observers.push([element, name, observer, useCapture]);

      element.addEventListener(name, observer, useCapture);

    } else if (element.attachEvent) {

      this.observers.push([element, name, observer, useCapture]);

      element.attachEvent('on' + name, observer);

    }

  },

//删除所有通过Event.observers绑定的事件

  unloadCache: function() {

    if (!Event.observers) return;

    for (var i = 0; i < Event.observers.length; i++) {

      Event.stopObserving.apply(this, Event.observers[i]);

      Event.observers[i][0] = null;

    }

    Event.observers = false;

  },

//绑定一个事件

  observe: function(element, name, observer, useCapture) {

    var element = $(element);

    useCapture = useCapture || false;



    if (name == 'keypress' &&

        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)

        || element.attachEvent))

      name = 'keydown';



    this._observeAndCache(element, name, observer, useCapture);

  },

//取消事件绑定

  stopObserving: function(element, name, observer, useCapture) {

    var element = $(element);

    useCapture = useCapture || false;



    if (name == 'keypress' &&

        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)

        || element.detachEvent))

      name = 'keydown';



    if (element.removeEventListener) {

      element.removeEventListener(name, observer, useCapture);

    } else if (element.detachEvent) {

      element.detachEvent('on' + name, observer);

    }

  }

});



//【结点的位置处理:Position对象】

/* prevent memory leaks in IE */

Event.observe(window, 'unload', Event.unloadCache, false);

var Position = {

  // set to true if needed, warning: firefox performance problems

  // NOT neeeded for page scrolling, only if draggable contained in

  // scrollable elements

  includeScrollOffsets: false,



  // must be called before calling withinIncludingScrolloffset, every time the

  // page is scrolled用于计算滚动条的位置信息

  prepare: function() {

    this.deltaX =  window.pageXOffset

                || document.documentElement.scrollLeft

                || document.body.scrollLeft

                || 0;

    this.deltaY =  window.pageYOffset

                || document.documentElement.scrollTop

                || document.body.scrollTop

                || 0;

  },

//计算节点的绝对滚动位置返回包含两个元素的数组到文档左侧 顶部的距离

  realOffset: function(element) {

    var valueT = 0, valueL = 0;

    do {

      valueT += element.scrollTop  || 0;

      valueL += element.scrollLeft || 0;

      element = element.parentNode;

    } while (element);

    return [valueL, valueT];

  },

//计算结点相对于文档的绝对滚动位置,返回包含两个元素的数组到文档左侧 顶部的距离

  cumulativeOffset: function(element) {

    var valueT = 0, valueL = 0;

    do {

      valueT += element.offsetTop  || 0;

      valueL += element.offsetLeft || 0;

      element = element.offsetParent;

    } while (element);

    return [valueL, valueT];

  },

//相对位置

  positionedOffset: function(element) {

    var valueT = 0, valueL = 0;

    do {

      valueT += element.offsetTop  || 0;

      valueL += element.offsetLeft || 0;

      element = element.offsetParent;

      if (element) {

        p = Element.getStyle(element, 'position');

        if (p == 'relative' || p == 'absolute') break;

      }

    } while (element);

    return [valueL, valueT];

  },



  offsetParent: function(element) {

    if (element.offsetParent) return element.offsetParent;

    if (element == document.body) return element;



    while ((element = element.parentNode) && element != document.body)

      if (Element.getStyle(element, 'position') != 'static')

        return element;



    return document.body;

  },



  // caches x/y coordinate pair to use with overlap是否在element内

  within: function(element, x, y) {

    if (this.includeScrollOffsets)

      return this.withinIncludingScrolloffsets(element, x, y);

    this.xcomp = x;

    this.ycomp = y;

    this.offset = this.cumulativeOffset(element);



    return (y >= this.offset[1] &&

            y <  this.offset[1] + element.offsetHeight &&

            x >= this.offset[0] &&

            x <  this.offset[0] + element.offsetWidth);

  },



  withinIncludingScrolloffsets: function(element, x, y) {

    var offsetcache = this.realOffset(element);



    this.xcomp = x + offsetcache[0] - this.deltaX;

    this.ycomp = y + offsetcache[1] - this.deltaY;

    this.offset = this.cumulativeOffset(element);



    return (this.ycomp >= this.offset[1] &&

            this.ycomp <  this.offset[1] + element.offsetHeight &&

            this.xcomp >= this.offset[0] &&

            this.xcomp <  this.offset[0] + element.offsetWidth);

  },



  // within must be called directly before

  overlap: function(mode, element) {

    if (!mode) return 0;

    if (mode == 'vertical')

      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /

        element.offsetHeight;

    if (mode == 'horizontal')

      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /

        element.offsetWidth;

  },

//克隆结点 常用于拖放

  clone: function(source, target) {

    source = $(source);

    target = $(target);

    target.style.position = 'absolute';

    var offsets = this.cumulativeOffset(source);

    target.style.top    = offsets[1] + 'px';

    target.style.left   = offsets[0] + 'px';

    target.style.width  = source.offsetWidth + 'px';

    target.style.height = source.offsetHeight + 'px';

  },



  page: function(forElement) {

    var valueT = 0, valueL = 0;



    var element = forElement;

    do {

      valueT += element.offsetTop  || 0;

      valueL += element.offsetLeft || 0;



      // Safari fix

      if (element.offsetParent==document.body)

        if (Element.getStyle(element,'position')=='absolute') break;



    } while (element = element.offsetParent);



    element = forElement;

    do {

      valueT -= element.scrollTop  || 0;

      valueL -= element.scrollLeft || 0;

    } while (element = element.parentNode);



    return [valueL, valueT];

  },



  clone: function(source, target) {

    var options = Object.extend({

      setLeft:    true,

      setTop:     true,

      setWidth:   true,

      setHeight:  true,

      offsetTop:  0,

      offsetLeft: 0

    }, arguments[2] || {})



    // find page position of source

    source = $(source);

    var p = Position.page(source);



    // find coordinate system to use

    target = $(target);

    var delta = [0, 0];

    var parent = null;

    // delta [0,0] will do fine with position: fixed elements,

    // position:absolute needs offsetParent deltas

    if (Element.getStyle(target,'position') == 'absolute') {

      parent = Position.offsetParent(target);

      delta = Position.page(parent);

    }



    // correct by body offsets (fixes Safari)

    if (parent == document.body) {

      delta[0] -= document.body.offsetLeft;

      delta[1] -= document.body.offsetTop;

    }



    // set position

    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';

    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';

    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';

    if(options.setHeight) target.style.height = source.offsetHeight + 'px';

  },

//绝对定位

  absolutize: function(element) {

    element = $(element);

    if (element.style.position == 'absolute') return;

    Position.prepare();



    var offsets = Position.positionedOffset(element);

    var top     = offsets[1];

    var left    = offsets[0];

    var width   = element.clientWidth;

    var height  = element.clientHeight;



    element._originalLeft   = left - parseFloat(element.style.left  || 0);

    element._originalTop    = top  - parseFloat(element.style.top || 0);

    element._originalWidth  = element.style.width;

    element._originalHeight = element.style.height;



    element.style.position = 'absolute';

    element.style.top    = top + 'px';;

    element.style.left   = left + 'px';;

    element.style.width  = width + 'px';;

    element.style.height = height + 'px';;

  },

//相对定位

  relativize: function(element) {

    element = $(element);

    if (element.style.position == 'relative') return;

    Position.prepare();



    element.style.position = 'relative';

    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);

    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);



    element.style.top    = top + 'px';

    element.style.left   = left + 'px';

    element.style.height = element._originalHeight;

    element.style.width  = element._originalWidth;

  }

}



// Safari returns margins on body which is incorrect if the child is absolutely

// positioned.  For performance reasons, redefine Position.cumulativeOffset for

// KHTML/WebKit only.

if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {

  Position.cumulativeOffset = function(element) {

    var valueT = 0, valueL = 0;

    do {

      valueT += element.offsetTop  || 0;

      valueL += element.offsetLeft || 0;

      if (element.offsetParent == document.body)

        if (Element.getStyle(element, 'position') == 'absolute') break;



      element = element.offsetParent;

    } while (element);



    return [valueL, valueT];

  }

}



//--------->完成

[转载]JavaScript中的prototype - 轩脉刃 - 博客园

mikel阅读(969)

[转载]JS中的prototype – 轩脉刃 – 博客园.

JS中的prototype是JS中比较难理解的一个部分

 

本文基于下面几个知识点:

 

1 原型法设计模式

在.Net中可以使用clone()来实现原型法

原型法的主要思想是,现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展。我们称B的原型为A。

 

2 JavaScript的方法可以分为三类:

a 类方法

b 对象方法

c 原型方法

例子:

function People(name)
{
this.name=name;
//对象方法
this.Introduce=function(){
alert("My name is "+this.name);
}
}
//类方法
People.Run=function(){
alert("I can run");
}
//原型方法
People.prototype.IntroduceChinese=function(){
alert("我的名字是"+this.name);
}

//测试

var p1=new People("Windking");

p1.Introduce();

People.Run();

p1.IntroduceChinese();

3 obj1.func.call(obj)方法

意思是将obj看成obj1,调用func方法

 

 

好了,下面一个一个问题解决:

 

prototype是什么含义?

 

JavaScript中的每个对象都有prototype属性,Javascript中对象的prototype属性的解释是:返回对象类型原型的引用。

A.prototype = new B();

理解prototype不应把它和继承混淆。A的prototype为B的一个实例,可以理解A将B中的方法和属性全部克隆了一遍。A能使用B的方 法和属性。这里强调的是克隆而不是继承。可以出现这种情况:A的prototype是B的实例,同时B的prototype也是A的实例。

 

先看一个实验的例子:

function baseClass()
{
this.showMsg = function()
{
alert("baseClass::showMsg");
}
}

function extendClass()
{
}

extendClass.prototype = new baseClass();
var instance = new extendClass();
instance.showMsg(); // 显示baseClass::showMsg

我们首先定义了baseClass类,然后我们要定义extentClass,但是我们打算以baseClass的一个实例为原型,来克隆的extendClass也同时包含showMsg这个对象方法。

extendClass.prototype = new baseClass()就可以阅读为:extendClass是以baseClass的一个实例为原型克隆创建的。

那么就会有一个问题,如果extendClass中本身包含有一个与baseClass的方法同名的方法会怎么样?

下面是扩展实验2:

function baseClass()
{
this.showMsg = function()
{
alert("baseClass::showMsg");
}
}

function extendClass()
{
this.showMsg =function ()
{
alert("extendClass::showMsg");
}
}

extendClass.prototype = new baseClass();
var instance = new extendClass();

instance.showMsg();//显示extendClass::showMsg

实验证明:函数运行时会先去本体的函数中去找,如果找到则运行,找不到则去prototype中寻找函数。或者可以理解为prototype不会克隆同名函数。

那么又会有一个新的问题:

如果我想使用extendClass的一个实例instance调用baseClass的对象方法showMsg怎么办?

答案是可以使用call:

extendClass.prototype = new baseClass();
var instance = new extendClass();

var baseinstance = new baseClass();
baseinstance.showMsg.call(instance);//显示baseClass::showMsg

这里的baseinstance.showMsg.call(instance);阅读为“将instance当做baseinstance来调用,调用它的对象方法showMsg”

好了,这里可能有人会问,为什么不用baseClass.showMsg.call(instance);

这就是对象方法和类方法的区别,我们想调用的是baseClass的对象方法

最后,下面这个代码如果理解清晰,那么这篇文章说的就已经理解了:

<script type="text/javascript">// <![CDATA[
function baseClass()
{
    this.showMsg = function()
    {
        alert("baseClass::showMsg");   
    }
   
    this.baseShowMsg = function()
    {
        alert("baseClass::baseShowMsg");
    }
}
baseClass.showMsg = function()
{
    alert("baseClass::showMsg static");
}

function extendClass()
{
    this.showMsg =function ()
    {
        alert("extendClass::showMsg");
    }
}
extendClass.showMsg = function()
{
    alert("extendClass::showMsg static")
}

extendClass.prototype = new baseClass();
var instance = new extendClass();

instance.showMsg(); //显示extendClass::showMsg
instance.baseShowMsg(); //显示baseClass::baseShowMsg
instance.showMsg(); //显示extendClass::showMsg

baseClass.showMsg.call(instance);//显示baseClass::showMsg static

var baseinstance = new baseClass();
baseinstance.showMsg.call(instance);//显示baseClass::showMsg
// ]]></script>

 

作者:轩脉刃(yjf512)
出处:(http://www.cnblogs.com/yjf512/
版权声明:本文的版权归作者与博客园共有。欢迎转载阅读,转载时须注明本文的详细链接。

 

[参考文章]

http://tech.ddvip.com/2009-05/1243588303121461.html

http://jetway.iteye.com/blog/56533

http://xiaofeizm55333.iteye.com/blog/80913

[转载]javascript in-between原理与应用_前端开发

mikel阅读(1226)

[转载]javascript in-between原理与应用_前端开发.

简介

本文将介绍in-between的概念,以及in-between类库Tween.js的实现。接着,我将介绍一些常见的in-between的好玩的用法。最后,我还将介绍JQuery Effects对in-between的应用。

目录

  • 什么是tween
  • Tween.js
  • 有趣的应用
  • JQuery中的Tween

什么是tween

tween是in-between的另一种写法。一个tween指的是让对象的属性值平滑变化的一个过程

那么,什么是平滑变化?假设在9点10分的时候,对象foo.a的值为0。在9点20分的时候,我希望它的值变成1。如果foo.a是非平滑变化的,在9点10分到9点20分(除9点20分外)之间它依然是0。如果它是平滑变化的,那么它应该在9点10分到9点20分之间的每一个时间点上的值都不同,并且是根据一定函数规律变化的。

tween就是这个平滑变化的过程。

javascript in-between原理与应用

这就好比一个人溜冰一样。你要从点a滑到点b,你是不可能一开始一直呆在a点,直到最后通过超时空转换直接把自己变到b点的。要从a点滑到b点,你必须经过一个路径,从而平滑地从a点滑到b点。

Tween.js

Tween.js是用来在JavaScript当中实现tween的一个工具库。我们接下来讲解它的实现。在实际应用中,我们一般自己编写自己的Tween类,或者复制并修改开源工具库中的Tween类,因为自己编写的总是最符合自己业务需求的。大部分Tween工具库包含了很多你用不到的东西,在后面我会提到。

为了使用Tween.js,你需要先有一个待变化的对象。在下面的例子里,我们将对象foo初始化为{a: 1}(初始状态),并要求它在3000毫秒后变成{a: 4}(目标状态)。变化过程采用线性变化,即每个时间点的变化速率相等。

var foo = {a: 1}, /*初始状态*/
  tween = new TWEEN.Tween(foo)
    .to({a: 4} /*目标状态*/, 3000 /*变化时间*/)
    .start();

  (function animate() {
    if ( foo.a < 4 ) {
      requestAnimationFrame(animate);
    }
    TWEEN.update();
    console.log(foo);
  })();

如果你查看Chrome Inspector(或者Firefox下的Firebug插件),你将会看到控制台中输出了下面的数据

Object {a: 1.0001740000443533} 
Object {a: 1.0924470000900328} 
Object {a: 1.1527340000029653} 
Object {a: 1.1701550000580028} 
Object {a: 1.185736000072211}
... ...

喘口气

回过头来,我们来稍微解释一下上面的代码段。首先我们创建一个foo对象的tween

var foo = {a: 1}, /*初始状态*/
  tween = new TWEEN.Tween(foo);

接下来,我们需要将确认foo对象的目标状态,在这里是{a: 4},并且要求它正好在3000毫秒后到达目标状态。

tween.to({a: 4} /*目标状态*/, 3000 /*变化时间*/);

最后,我们需要激活这个tween,代表开始变化。调用tween.start()的时间就是开始变化的时间时间戳,除非你调用了tween.delay()方法。你还可以给tween.start(time)传入一个额外参数time,直接指定开始变化的时间戳。我们可以通过源码验证这点

_startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
_startTime += _delayTime;

值得注意的是,在没有delay和指定time参数的情况下,Tween.js将优先使用window.performance.now()获取当前的时间戳,这样的时间戳是高精度时间戳(精度为10μs)。这是HTML5当中的新增的DOMHighResTimeStamp API。
询问进度

我们通过animate函数来轮询foo对象目前的状态。采用requestAnimationFrame进行异步调用,效率会更高。你也可以选择用setTimeout,JQuery就是这样做的。

在询问的时候,你首先需要调用TWEEN.update()更新所有的tween。

(function animate() {
  if ( foo.a < 4 ) {
    requestAnimationFrame(animate);
  }
  TWEEN.update();
  console.log(foo);
})();

精髓

使用in-between的精髓就在于,它将属性的变化和询问分离。如果你熟悉MVC,属性的变化就好像是MVC里面的Model,而询问就好像是Controller,最后我们输出到控制台(Console)就好像是View。

“历史总是惊人地相似”

分离带来的好处是什么呢?它使得我们可以统一管理所有页面上的Tween,而不用关心它们究竟用于什么途径。接下来,我们通过实践来证明这一点。
有趣的应用

首先你需要先将Tween.js的GitHub代码仓库复制到本地

git clone git@github.com:sole/tween.js.git
cd tween.js

在examples目录里面有许多有趣的应用,我们只看其中第二个例子01_bars.html。在这个例子中,有1000个彩条在屏幕上水平移动。每个彩条都对应两个tween,一个是从出发位置到目标位置的,一个是返回出发位置的。

var tween = new TWEEN.Tween(elem)
  .to({ x: endValue }, 4000)
  .delay(Math.random() * 1000)
  .onUpdate(updateCallback)
  .easing(TWEEN.Easing.Back.Out)
  .start();

var tweenBack = new TWEEN.Tween(elem, false)
  .to({ x: startValue}, 4000)
  .delay(Math.random() * 1000)
  .onUpdate(updateCallback)
  .easing(TWEEN.Easing.Elastic.InOut);

tween.chain(tweenBack);
tweenBack.chain(tween);

Tween.js支持事件onUpdate,每当TWEEN.update()被调用的时候,会触发所有tween的update事件。另外,你还能为每个tween设置easing function。如果你不清楚什么是easing function,可以看我昨天写的文章《JavaScript动画实现初探》。

由于Tween.js和其他支持in-between的类库都含有大量预置的easing function,其中有很多我们用不到的。所以,就像本文前面提到的一样,我们经常需要定制自己的Tween类库。

这里还用到了chaining来循环动画,tween结束后将启动tweenBack,tweenBack启动后会再次启动tween。
jQuery中的Tween

jQuery中也采用了Tween来管理动画的效果进度。在jQuery 1.8之后,引入了Tween来管理动画效果进度,原先的jQuery.fx和Tween.prototype.init是相同的。之所以保留jQuery.fx,是为了兼容以前的插件。

jQuery.fx = Tween.prototype.init;

对于动画中需要变化的每一个属性,jQuery都为其创建一个Tween。

jQuery.map( props, createTween, animation );

function createTween( value, prop, animation ) {
  var tween,
    collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
    index = 0,
    length = collection.length;
  for ( ; index < length; index++ ) {
    if ( (tween = collection[ index ].call( animation, prop, value )) ) {
      // we're done with this property
      return tween;
    }
  }
}

每隔一段时间,jQuery要求每隔DOM节点的tween根据当前进度更新style。

for ( ; index < length ; index++ ) {
  animation.tweens[ index ].run( percent /*当前动画的时间进度*/);
}

jQuery当中并没有用requestAnimationFrame一直去询问,而是采用setTimeout每隔13ms去询问,然后更新界面。13ms是一个平衡点,不会太长,也不会太短。

jQuery.fx.start = function() {
  if ( !timerId ) {
    timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
  }
};

总结

本文介绍了in-between,并介绍了它的原理以及一些应用。in-between主要用在页面效果动画,数据可视化当中。你可以让它和一些著名的数据可视化库(如d3.js)协同工作。你可以查看Tween.js的examples,学到更多相关的应用。
讨论

欢迎到我们的团队博客进行讨论,我在团队博客里面的讨论时间较多。

[转载]jQuery1.9.1源码分析--Animation模块 - LukeLin - 博客园

mikel阅读(897)

[转载]jQuery1.9.1源码分析–Animation模块 – LukeLin – 博客园.

JQuery1.9.1源码分析–Animation模块

var fxNow,
// 使用一个ID来执行动画setInterval
timerId,
rfxtypes = /^(?:toggle|show|hide)$/,
// eg: +=30.5px
// 执行exec匹配["+=30.5px", "+", "30.5", "px"]
rfxnum = new RegExp('^(?:([+-])=|)(' + core_pnum + ')([a-z%]*)$', 'i'),
// 以“queueHooks”结尾
rrun = /queueHooks$/,
animationPrefilters = [defaultPrefilter],
tweeners = {
// 在动画前再次对动画参数做调整
'*': [
function(prop, value) {
var end, unit,
// this指向animation对象
// 返回一个Tween构造函数实例
tween = this.createTween(prop, value),
// eg:["+=30.5px", "+", "30.5", "px"]
parts = rfxnum.exec(value),
// 计算当前属性样式值
target = tween.cur(),
start = +target || 0,
scale = 1,
maxIterations = 20;

if (parts) {
// 数值
end = +parts[2];
// 单位
// jQuery.cssNumber里面的值是不需要单位的
unit = parts[3] || (jQuery.cssNumber[prop] ? '' : 'px');

// We need to compute starting value
// 我们需要计算开始值
if (unit !== 'px' &amp;&amp; start) {
// Iteratively approximate from a nonzero starting point
// Prefer the current property, because this process will be trivial if it uses the same units
// Fallback to end or a simple constant
// 尝试从元素样式中获取开始值
start = jQuery.css(tween.elem, prop, true) || end || 1;

do {
// If previos iteration zeroed out, double until we get *something*
// Use a string for doubling factor so we don't accidentally see scale as unchanged below
scale = scale || '.5';

// Adjust and apply
start = start / scale;
jQuery.style(tween.elem, prop, start + unit);

// Update scale, tolerating zero or NaN from tween.cur()
// And breaking the loop if scale is unchanged or perfect. or if we've just had enough
} while (scale !== (scale = tween.cur() / target) &amp;&amp; scale !== 1 &amp;&amp; --maxIterations);
}

tween.unit = unit;
tween.start = start;
// If a +=/-= token was provided, we're doing a relative animation
tween.end = parts[1] ? start + (parts[1] + 1) * end : end;
}
return tween;
}
]
};

// Animations created synchronous will run synchronously
// TODO
// 返回一个时间戳,然后用setTimeout延时将fxNow设置为undefined

function createFxNow() {
setTimeout(function() {
fxNow = undefined;
});
return (fxNow = jQuery.now());
}

function createTweens(animation, props) {
// 遍历props动画属性对象,并执行回调
jQuery.each(props, function(prop, value) {
// 如果tweeners[prop]数组存在,将它和tweeners['*']连接
var collection = (tweeners[prop] || []).concat(tweeners['*']),
index = 0,
length = collection.length;

// 遍历函数数组
for (; index &lt; length; index++) {
// 如果该函数有返回值,且==true,退出函数
if (collection[index].call(animation, prop, value)) {
// We're done with this property
return;
}
}
});
}

function Animation(elem, properties, options) {
var result, stopped, index = 0,
length = animationPrefilters.length,
// deferred无论成功还是失败都会删除elem元素
deferred = jQuery.Deferred().always(function() {
// don't match elem in the :animated selector
// 在“:animated”选择器中不会匹配到它们
delete tick.elem;
}),
tick = function() {
if (stopped) {
return false;
}
var // 计算当前动画时间戳
currentTime = fxNow || createFxNow(),
// 结束时间减当前时间,计算出剩余时间
remaining = Math.max(0, animation.startTime + animation.duration - currentTime),
// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
// 剩余时间百分比
temp = remaining / animation.duration || 0,
// 已执行百分比
percent = 1 - temp,
index = 0,
// 动画属性对应的tweens
length = animation.tweens.length;

// 遍历tweens,并执行对应的run方法,将已执行百分比通过传参传入
// run方法通过缓动算法计算出样式值,然后应用到元素上
for (; index &lt; length; index++) {
animation.tweens[index].run(percent);
}

// 触发notify回调列表
deferred.notifyWith(elem, [animation, percent, remaining]);

// 如果执行进度为完成且tweens数组有元素
// 返回剩余时间
if (percent &lt; 1 &amp;&amp; length) {
return remaining;
} else {
// 否则表示已完成,触发resolve回调列表,
// 并返回false值
deferred.resolveWith(elem, [animation]);
return false;
}
},
animation = deferred.promise({
// 动画元素
elem: elem,
// 需要动画的属性
props: jQuery.extend({}, properties),
// 给optall添加specialEasing属性对象
opts: jQuery.extend(true, {
specialEasing: {}
}, options),
// 原始动画属性
originalProperties: properties,
// 原始的配置项optall
originalOptions: options,
// 动画开始时间,使用当前时间的毫秒数
startTime: fxNow || createFxNow(),
// 动画时长
duration: options.duration,
tweens: [],
createTween: function(prop, end) {
var tween = jQuery.Tween(elem, animation.opts, prop, end, animation.opts.specialEasing[prop] || animation.opts.easing);
animation.tweens.push(tween);
return tween;
},
stop: function(gotoEnd) {
var index = 0,
// if we are going to the end, we want to run all the tweens
// otherwise we skip this part
length = gotoEnd ? animation.tweens.length : 0;
if (stopped) {
return this;
}
stopped = true;
for (; index &lt; length; index++) {
animation.tweens[index].run(1);
}

// resolve when we played the last frame
// otherwise, reject
if (gotoEnd) {
deferred.resolveWith(elem, [animation, gotoEnd]);
} else {
deferred.rejectWith(elem, [animation, gotoEnd]);
}
return this;
}
}),
props = animation.props;

/*
将是动画属性转换成驼峰式,并设置其相应的缓动属性,
如果存在cssHooks钩子对象,则需要另作一番处理
*/
propFilter(props, animation.opts.specialEasing);

// 遍历动画预过滤器,并执行回调
// 其中defaultPrefilter为默认预过滤器,每次都会执行
for (; index &lt; length; index++) {
result = animationPrefilters[index].call(animation, elem, props, animation.opts);
// 如果有返回值,退出函数
if (result) {
return result;
}
}

createTweens(animation, props);

if (jQuery.isFunction(animation.opts.start)) {
animation.opts.start.call(elem, animation);
}

// 开始执行动画
jQuery.fx.timer(
jQuery.extend(tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
}));

// attach callbacks from options
return animation.progress(animation.opts.progress).done(animation.opts.done, animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always);
}

/**
* 动画属性调整与过滤
*
* 将是动画属性转换成驼峰式,并设置其相应的缓动属性,
* 如果存在cssHooks钩子对象,则需要另作一番处理
* @param {[type]} props [需要动画的属性]
* @param {[type]} specialEasing [description]
* @return {[type]} [description]
*/
function propFilter(props, specialEasing) {
var value, name, index, easing, hooks;

// camelCase, specialEasing and expand cssHook pass
for (index in props) {
// 驼峰化属性
name = jQuery.camelCase(index);
// TODO
easing = specialEasing[name];
// 属性值
value = props[index];
// 如果属性值是数组
if (jQuery.isArray(value)) {
easing = value[1];
// 取数组第一个元素为属性值
value = props[index] = value[0];
}

// 如果属性名精过驼峰化后,删除原有的属性名,减少占用内存
if (index !== name) {
props[name] = value;
delete props[index];
}

// 处理兼容性的钩子对象
hooks = jQuery.cssHooks[name];
// 如果存在钩子对象且有expand属性
if (hooks &amp;&amp; "expand" in hooks) {
// 返回expand处理后的value值
// 该类型是一个对象,属性是
// (margin|padding|borderWidth)(Top|Right|Bottom|Left)
value = hooks.expand(value);

// 我们已经不需要name属性了
delete props[name];

// not quite $.extend, this wont overwrite keys already present.
// also - reusing 'index' from above because we have the correct "name"
for (index in value) {
// 如果props没有(margin|padding|borderWidth)(Top|Right|Bottom|Left)属性
// 添加该属性和对应的值,并设置缓动属性
if (!(index in props)) {
props[index] = value[index];
specialEasing[index] = easing;
}
}
} else {
// 没有钩子对象就直接设置其为缓动属性
specialEasing[name] = easing;
}
}
}

jQuery.Animation = jQuery.extend(Animation, {

tweener: function(props, callback) {
if (jQuery.isFunction(props)) {
callback = props;
props = ["*"];
} else {
props = props.split(" ");
}

var prop, index = 0,
length = props.length;

for (; index &lt; length; index++) {
prop = props[index];
tweeners[prop] = tweeners[prop] || [];
tweeners[prop].unshift(callback);
}
},
// 为animationPrefilters回调数组添加回调
prefilter: function(callback, prepend) {
if (prepend) {
animationPrefilters.unshift(callback);
} else {
animationPrefilters.push(callback);
}
}
});

/**
* 动画预处理
* 添加fx队列缓存(没有的话),对动画属性“width/height,overflow”, 值有“toggle/show/hide”采取的一些措施
*
* @param {[type]} elem [动画元素]
* @param {[type]} props [动画属性]
* @param {[type]} opts [动画配置项]
* @return {[type]} [description]
*/
function defaultPrefilter(elem, props, opts) { /*jshint validthis:true */
var prop, index, length, value, dataShow, toggle, tween, hooks, oldfire,
// animation对象(同时是个deferred对象)
anim = this,
style = elem.style,
orig = {},
handled = [],
hidden = elem.nodeType &amp;&amp; isHidden(elem);

// handle queue: false promises
if (!opts.queue) {
// 获取或者设置动画队列钩子
hooks = jQuery._queueHooks(elem, "fx");
// 如果hooks.unqueued为null/undefined
if (hooks.unqueued == null) {
hooks.unqueued = 0;
// 获取旧的empty回调对象
// 用于清除动画队列缓存
oldfire = hooks.empty.fire;
// 装饰,添加新的职责
hooks.empty.fire = function() {
// 当hooks.unqueued为0时执行清除动画队列缓存
if (!hooks.unqueued) {
oldfire();
}
};
}
hooks.unqueued++;

anim.always(function() {
// doing this makes sure that the complete handler will be called
// before this completes
// 延迟处理,确保该回调完成才调用下面回调
anim.always(function() {
hooks.unqueued--;
// 如果动画队列没有元素了,清空缓存
if (!jQuery.queue(elem, "fx").length) {
hooks.empty.fire();
}
});
});
}

// height/width overflow pass
// 对width或height的DOM元素的动画前的处理
if (elem.nodeType === 1 &amp;&amp; ("height" in props || "width" in props)) {
// Make sure that nothing sneaks out
// Record all 3 overflow attributes because IE does not
// change the overflow attribute when overflowX and
// overflowY are set to the same value
// IE不会改变overflow属性当iverflowX和overflowY的值相同时。
// 因此我们要记录三个overflow的属性
opts.overflow = [style.overflow, style.overflowX, style.overflowY];

// Set display property to inline-block for height/width
// animations on inline elements that are having width/height animated
// 将inline元素(非浮动的)设置为inline-block或者BFC(iE6/7),使它们的width和height可改变
if (jQuery.css(elem, "display") === "inline" &amp;&amp; jQuery.css(elem, "float") === "none") {

// inline-level elements accept inline-block;
// block-level elements need to be inline with layout
if (!jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay(elem.nodeName) === "inline") {
style.display = "inline-block";

} else {
style.zoom = 1;
}
}
}

if (opts.overflow) {
style.overflow = "hidden";
// 如果不支持父元素随着子元素宽度改变而改变
// 动画结束后将style设置为初始状态
if (!jQuery.support.shrinkWrapBlocks) {
anim.always(function() {
style.overflow = opts.overflow[0];
style.overflowX = opts.overflow[1];
style.overflowY = opts.overflow[2];
});
}
}

// show/hide pass
// 遍历动画属性
for (index in props) {
// 获取目标值
value = props[index];
// 判断值是否有toggle|show|hide
if (rfxtypes.exec(value)) {
delete props[index];
// 是否需要toggle
toggle = toggle || value === "toggle";
// 如果hide(或者show)状态的初始值和我们动画的值相同,就不需要做处理
if (value === (hidden ? "hide" : "show")) {
continue;
}
// 将需要show/hide/toggle的属性保存到handled数组中
handled.push(index);
}
}

length = handled.length;
// 如果handled数组有元素
// 对需要toggle|show|hide的属性处理
if (length) {
// 获取或者设置元素的fxshow缓存(保存显示状态)
dataShow = jQuery._data(elem, "fxshow") || jQuery._data(elem, "fxshow", {});
// 如果元素已经有hidden属性,说明我们设置过了,
// 取该值
if ("hidden" in dataShow) {
hidden = dataShow.hidden;
}

// store state if its toggle - enables .stop().toggle() to "reverse"
// 如果需要toggle,将hidden状态取反
if (toggle) {
dataShow.hidden = !hidden;
}
// 如果元素隐藏了就显示出来,为了后期的动画
if (hidden) {
jQuery(elem).show();
} else {
// 否则动画结束后才隐藏
anim.done(function() {
jQuery(elem).hide();
});
}
// 动画结束后删除fxshow缓存,并恢复元素原始样式
anim.done(function() {
var prop;
jQuery._removeData(elem, "fxshow");
for (prop in orig) {
jQuery.style(elem, prop, orig[prop]);
}
});
for (index = 0; index &lt; length; index++) {
prop = handled[index];
// 创建Tween实例
tween = anim.createTween(prop, hidden ? dataShow[prop] : 0);
// 获取元素原始样式值
orig[prop] = dataShow[prop] || jQuery.style(elem, prop);

// 如果dataShow引用的缓存没有show|hide|toggle属性
if (!(prop in dataShow)) {
// 添加该属性,并赋初值
dataShow[prop] = tween.start;
if (hidden) {
tween.end = tween.start;
tween.start = prop === "width" || prop === "height" ? 1 : 0;
}
}
}
}
}

// 实例化init构造函数
// 对单个动画属性,在初始化的时候计算开始值
function Tween(elem, options, prop, end, easing) {
return new Tween.prototype.init(elem, options, prop, end, easing);
}
jQuery.Tween = Tween;

Tween.prototype = {
constructor: Tween,
init: function(elem, options, prop, end, easing, unit) {
this.elem = elem;
this.prop = prop;
this.easing = easing || "swing";
this.options = options;
this.start = this.now = this.cur();
this.end = end;
this.unit = unit || (jQuery.cssNumber[prop] ? "" : "px");
},
cur: function() {
var hooks = Tween.propHooks[this.prop];

return hooks &amp;&amp; hooks.get ? hooks.get(this) : Tween.propHooks._default.get(this);
},
// 通过缓动算法计算出样式值,然后应用到元素上
run: function(percent) {
var eased, hooks = Tween.propHooks[this.prop];

// 当前执行位置,
// 如果有时长,就用缓动算法
if (this.options.duration) {
this.pos = eased = jQuery.easing[this.easing](
percent, this.options.duration * percent, 0, 1, this.options.duration);
} else {
this.pos = eased = percent;
}
// 当前时间戳
this.now = (this.end - this.start) * eased + this.start;

if (this.options.step) {
this.options.step.call(this.elem, this.now, this);
}

// 有钩子对象就执行set方法,否则使用默认set方法
if (hooks &amp;&amp; hooks.set) {
hooks.set(this);
} else {
Tween.propHooks._default.set(this);
}
return this;
}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
_default: {
// 默认的获取样式初始值方法
get: function(tween) {
var result;

if (tween.elem[tween.prop] != null &amp;&amp; (!tween.elem.style || tween.elem.style[tween.prop] == null)) {
return tween.elem[tween.prop];
}

// passing an empty string as a 3rd parameter to .css will automatically
// attempt a parseFloat and fallback to a string if the parse fails
// so, simple values such as "10px" are parsed to Float.
// complex values such as "rotate(1rad)" are returned as is.
result = jQuery.css(tween.elem, tween.prop, "");
// Empty strings, null, undefined and "auto" are converted to 0.
return !result || result === "auto" ? 0 : result;
},
// 设置元素样式
set: function(tween) {
// use step hook for back compat - use cssHook if its there - use .style if its
// available and use plain properties where available
if (jQuery.fx.step[tween.prop]) {
jQuery.fx.step[tween.prop](tween);
} else if (tween.elem.style &amp;&amp; (tween.elem.style[jQuery.cssProps[tween.prop]] != null || jQuery.cssHooks[tween.prop])) {
jQuery.style(tween.elem, tween.prop, tween.now + tween.unit);
} else {
tween.elem[tween.prop] = tween.now;
}
}
}
};

// Remove in 2.0 - this supports IE8's panic based approach
// to setting things on disconnected nodes
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
set: function(tween) {
if (tween.elem.nodeType &amp;&amp; tween.elem.parentNode) {
tween.elem[tween.prop] = tween.now;
}
}
};

jQuery.each(["toggle", "show", "hide"], function(i, name) {
var cssFn = jQuery.fn[name];
jQuery.fn[name] = function(speed, easing, callback) {
return speed == null || typeof speed === "boolean" ? cssFn.apply(this, arguments) : this.animate(genFx(name, true), speed, easing, callback);
};
});

jQuery.fn.extend({
fadeTo: function(speed, to, easing, callback) {

// show any hidden elements after setting opacity to 0
return this.filter(isHidden).css("opacity", 0).show()

// animate to the value specified
.end().animate({
opacity: to
}, speed, easing, callback);
},
animate: function(prop, speed, easing, callback) {
var // prop对象是否为空
empty = jQuery.isEmptyObject(prop),
// 返回{complete, duration, easing, queue, old}
optall = jQuery.speed(speed, easing, callback),
// TODO
doAnimation = function() {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation(this, jQuery.extend({}, prop), optall);
doAnimation.finish = function() {
anim.stop(true);
};
// Empty animations, or finishing resolves immediately
if (empty || jQuery._data(this, "finish")) {
anim.stop(true);
}
};
doAnimation.finish = doAnimation;

// 如果prop为空对象或者queue为false(即不进行动画队列),
// 遍历元素集并执行doAnimation回调
return empty || optall.queue === false ? this.each(doAnimation) :
// 否则prop不为空且需要队列执行,
// 将doAnimation添加到该元素的队列中
// jQuery.queue('fx', doAnimation)
this.queue(optall.queue, doAnimation);
},
// 停止所有在指定元素上正在运行的动画。
stop: function(type, clearQueue, gotoEnd) {
var stopQueue = function(hooks) {
var stop = hooks.stop;
delete hooks.stop;
stop(gotoEnd);
};

if (typeof type !== "string") {
gotoEnd = clearQueue;
clearQueue = type;
type = undefined;
}
if (clearQueue &amp;&amp; type !== false) {
this.queue(type || "fx", []);
}

return this.each(function() {
var dequeue = true,
index = type != null &amp;&amp; type + "queueHooks",
timers = jQuery.timers,
data = jQuery._data(this);

if (index) {
if (data[index] &amp;&amp; data[index].stop) {
stopQueue(data[index]);
}
} else {
for (index in data) {
if (data[index] &amp;&amp; data[index].stop &amp;&amp; rrun.test(index)) {
stopQueue(data[index]);
}
}
}

for (index = timers.length; index--;) {
if (timers[index].elem === this &amp;&amp; (type == null || timers[index].queue === type)) {
timers[index].anim.stop(gotoEnd);
dequeue = false;
timers.splice(index, 1);
}
}

// start the next in the queue if the last step wasn't forced
// timers currently will call their complete callbacks, which will dequeue
// but only if they were gotoEnd
if (dequeue || !gotoEnd) {
jQuery.dequeue(this, type);
}
});
},
finish: function(type) {
if (type !== false) {
type = type || "fx";
}
return this.each(function() {
var index, data = jQuery._data(this),
queue = data[type + "queue"],
hooks = data[type + "queueHooks"],
timers = jQuery.timers,
length = queue ? queue.length : 0;

// enable finishing flag on private data
data.finish = true;

// empty the queue first
jQuery.queue(this, type, []);

if (hooks &amp;&amp; hooks.cur &amp;&amp; hooks.cur.finish) {
hooks.cur.finish.call(this);
}

// look for any active animations, and finish them
for (index = timers.length; index--;) {
if (timers[index].elem === this &amp;&amp; timers[index].queue === type) {
timers[index].anim.stop(true);
timers.splice(index, 1);
}
}

// look for any animations in the old queue and finish them
for (index = 0; index &lt; length; index++) {
if (queue[index] &amp;&amp; queue[index].finish) {
queue[index].finish.call(this);
}
}

// turn off finishing flag
delete data.finish;
});
}
});

// Generate parameters to create a standard animation
/**
* 用于填充slideDown/slideUp/slideToggle动画参数
* @param {[String]} type [show/hide/toggle]
* @param {[type]} includeWidth [是否需要包含宽度]
* @return {[type]} [description]
*/
function genFx(type, includeWidth) {
var which,
attrs = {
height: type
},
i = 0;

// if we include width, step value is 1 to do all cssExpand values,
// if we don't include width, step value is 2 to skip over Left and Right
includeWidth = includeWidth ? 1 : 0;
// 不包含宽度,which就取“Top/Bottom”,
// 否则“Left/Right”
for (; i &lt; 4; i += 2 - includeWidth) { which = cssExpand[i]; attrs["margin" + which] = attrs["padding" + which] = type; } if (includeWidth) { attrs.opacity = attrs.width = type; } return attrs; } // Generate shortcuts for custom animations jQuery.each({ slideDown: genFx("show"), slideUp: genFx("hide"), slideToggle: genFx("toggle"), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function(name, props) { jQuery.fn[name] = function(speed, easing, callback) { return this.animate(props, speed, easing, callback); }; }); /** * 配置动画参数 * * 配置动画时长,动画结束回调(经装饰了),缓动算法,queue属性用来标识是动画队列 * @param {[Number|Objecct]} speed [动画时长] * @param {[Function]} easing [缓动算法] * @param {Function} fn [动画结束会掉] * @return {[Object]} [description] */ jQuery.speed = function(speed, easing, fn) { var opt = // speed是否为对象 speed &amp;&amp; typeof speed === "object" ? // 如果是,克隆speed对象 jQuery.extend({}, speed) : // 否则返回一个新的对象 { // complete是我们的animate的回调方法, // 即动画结束时的回调 // (speed, easing, fn) // (speed || easing, fn) // (fn) complete: fn || !fn &amp;&amp; easing || jQuery.isFunction(speed) &amp;&amp; speed, // 动画时长 duration: speed, // 缓动 easing: fn &amp;&amp; easing || easing &amp;&amp; !jQuery.isFunction(easing) &amp;&amp; easing }; opt.duration = // jQuery.fx.off是否为真,如果是则将opt.duration设置为0, // 这将会停止所有动画 jQuery.fx.off ? 0 : // 否则判断duration属性值是否为数字类型,是则使用 typeof opt.duration === "number" ? opt.duration : // 否则判断duration属性值字符串是否在jQuery.fx.speeds(jQuery的预配置动画时长)属性key字段中,是则使用 opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : // 否则就是用默认动画时长 jQuery.fx.speeds._default; // normalize opt.queue - true/undefined/null -&gt; "fx"
// 如果opt.queue的值是true/undefined/null之一,
// 将其值设置为"fx"字符串,标示动画队列
if (opt.queue == null || opt.queue === true) {
opt.queue = "fx";
}

// Queueing
// 将旧的回调(即我们添加的回调)存入opt.old
opt.old = opt.complete;

// 给opt.complete重新定义,
// 在旧方法中通过装饰包装
opt.complete = function() {
if (jQuery.isFunction(opt.old)) {
// 执行我们的回调
opt.old.call(this);
}

// 如果有队列,执行我们下一个队列
if (opt.queue) {
jQuery.dequeue(this, opt.queue);
}
};

// 返回opt
/*
{complete, duration, easing, queue, old}
*/
return opt;
};

jQuery.easing = {
linear: function(p) {
return p;
},
swing: function(p) {
return 0.5 - Math.cos(p * Math.PI) / 2;
}
};

// 全局timers数组,保存着所有动画tick
jQuery.timers = [];
jQuery.fx = Tween.prototype.init;
// setInterval回调
jQuery.fx.tick = function() {
var timer, timers = jQuery.timers,
i = 0;

fxNow = jQuery.now();

// 遍历所有tick
for (; i &lt; timers.length; i++) {
timer = timers[i];
// Checks the timer has not already been removed
// 如果当前tick返回的为假(经弱转换)
// 移除该tick
// 然后继续遍历当前项,因为数组长度被改变了
if (!timer() &amp;&amp; timers[i] === timer) {
timers.splice(i--, 1);
}
}

// 如果没有tick回调了,停止定时器
if (!timers.length) {
jQuery.fx.stop();
}
fxNow = undefined;
};

/**
*
*
* @param {Object} timer tick回调
*/
jQuery.fx.timer = function(timer) {
if (timer() &amp;&amp; jQuery.timers.push(timer)) {
jQuery.fx.start();
}
};

jQuery.fx.interval = 13;

// 动画正式开始
jQuery.fx.start = function() {
if (!timerId) {
// 间隔执行jQuery.fx.tick
timerId = setInterval(jQuery.fx.tick, jQuery.fx.interval);
}
};

jQuery.fx.stop = function() {
clearInterval(timerId);
timerId = null;
};

jQuery.fx.speeds = {
slow: 600,
fast: 200,
// Default speed
_default: 400
};

// Back Compat jQuery.fx.step = {};

if (jQuery.expr &amp;&amp; jQuery.expr.filters) {
jQuery.expr.filters.animated = function(elem) {
return jQuery.grep(jQuery.timers, function(fn) {
return elem === fn.elem;
}).length;
};
}