prototype[1].js 源码解读

阅读更多

prototype.js 源码解读

prototype 1.3.1 版本和之前的 1.2.0 版本有了不少改进,并增加了新的功能:
1. 增加了事件注册管理
2. 增加了空间定位的常用函数
3. 改善了 xmlhttp 的封装
4. 移除了 Effect.js,交给 Rico 或者 script.aculo.us 这些扩展库类实现。
5. bug 修复
代码:
/**
* 定义一个全局对象, 属性 Version 在发布的时候会替换为当前版本号
*/
var Prototype = {
Version: '1.3.1',
// 一个空方法,其后的代码常会用到,先前的版本该方法被定义于 Ajax 类中。
emptyFunction: function() {}
}
/**
* 创建一种类型,注意其属性 create 是一个方法,返回一个构造函数。
* 一般使用如下
*   var X = Class.create(); 返回一个类型,类似于 java 的一个Class实例。
* 要使用 X 类型,需继续用 new X()来获取一个实例,如同 java 的 Class.newInstance()方法。
*
* 返回的构造函数会执行名为 initialize 的方法, initialize 是 Ruby 对象的构造器方法名字。
* 此时initialize方法还没有定义,其后的代码中创建新类型时会建立相应的同名方法。
*
*/
var Class = {
create: function() {
  return function() {
    this.initialize.apply(this, arguments);
  }
}
}

/**
* 创建一个对象,从变量名来思考,本意也许是定义一个抽象类,以后创建新对象都 extend 它。
* 但从其后代码的应用来看, Abstract 更多是为了保持命名空间清晰的考虑。
* 也就是说,我们可以给 Abstract 这个对象实例添加新的对象定义。
*/
var Abstract = new Object();

Object.extend = function(destination, source) {
for (property in source) {
  destination[property] = source[property];
}
return destination;
}
/**
* 获取参数对象的所有属性和方法,有点象多重继承。但是这种继承是动态获得的。
* 如:
*   var a = new ObjectA(), b = new ObjectB();
*   var c = a.extend(b);
* 此时 c 对象同时拥有 a 和 b 对象的属性和方法。但是与多重继承不同的是,c instanceof ObjectB 将返回false。
*
* 旧版本的该方法定义如下:
* Object.prototype.extend = function(object) {
*   for (property in object) {
*       this[property] = object[property];
*   }
*   return this;
* }
*
* 新的形式新定义了一个静态方法 Object.extend,这样做的目的大概是为了使代码更为清晰
*/
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
}

/**
* 这个方法很有趣,它封装一个javascript函数对象,返回一个新函数对象,新函数对象的主体和原对象相同,但是bind()方法参数将被用作当前对象的对象。
* 也就是说新函数中的 this 引用被改变为参数提供的对象。
* 比如:
*  
*  
*   .................
*  
* 那么,调用aaa.showValue 将返回"aaa", 但调用aaa.showValue2 将返回"bbb"。
*
* apply 是ie5.5后才出现的新方法(Netscape好像很早就支持了)。
* 该方法更多的资料参考MSDN [url]http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthApply.asp[/url]
* 阅读其后的代码就会发现,bind 被应用的很广泛,该方法和 Object.prototype.extend 一样是 Prototype 的核心。
* 还有一个 call 方法,应用起来和 apply 类似。可以一起研究下。
*/
Function.prototype.bind = function(object) {
var __method = this;
return function() {
  __method.apply(object, arguments);
}
}
/**
* 和bind一样,不过这个方法一般用做html控件对象的事件处理。所以要传递event对象
* 注意这时候,用到了 Function.call。它与 Function.apply 的不同好像仅仅是对参数形式的定义。
*/
Function.prototype.bindAsEventListener = function(object) {
var __method = this;
return function(event) {
  __method.call(object, event || window.event);
}
}
/**
* 将整数形式RGB颜色值转换为HEX形式
*/
Number.prototype.toColorPart = function() {
var digits = this.toString(16);
if (this < 16) return '0' + digits;
return digits;
}
/**
* 典型 Ruby 风格的函数,将参数中的方法逐个调用,返回第一个成功执行的方法的返回值
*/
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;
}
}
/*--------------------------------------------------------------------------*/

/**
* 一个设计精巧的定时执行器
* 首先由 Class.create() 创建一个 PeriodicalExecuter 类型,
* 然后用对象直接量的语法形式设置原型。
*
* 需要特别说明的是 rgisterCallback 方法,它调用上面定义的函数原型方法bind, 并传递自己为参数。
* 之所以这样做,是因为 setTimeout 默认总以 window 对象为当前对象,也就是说,如果 registerCallback 方法定义如下的话:
*   registerCallback: function() {
*       setTimeout(this.onTimerEvent, this.frequency * 1000);
*   }
* 那么,this.onTimeoutEvent 方法执行失败,因为它无法访问 this.currentlyExecuting 属性。
* 而使用了bind以后,该方法才能正确的找到this,也就是PeriodicalExecuter的当前实例。
*/
var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
initialize: function(callback, frequency) {
  this.callback = callback;
  this.frequency = frequency;
  this.currentlyExecuting = false;
  this.registerCallback();
},

registerCallback: function() {
  setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},

onTimerEvent: function() {
  if (!this.currentlyExecuting) {
    try {
    this.currentlyExecuting = true;
    this.callback();
    } finally {
    this.currentlyExecuting = false;
    }
  }
}
}

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

/**
* 这个函数就 Ruby 了。我觉得它的作用主要有两个
* 1. 大概是 document.getElementById(id) 的最简化调用。
* 比如:$("aaa") 将返回 aaa 对象
* 2. 得到对象数组
* 比如: $("aaa","bbb") 返回一个包括id为"aaa"和"bbb"两个input控件对象的数组。
*/
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;
}

/**
* 为兼容旧版本的浏览器增加 Array 的 push 方法。
*/
if (!Array.prototype.push) {
Array.prototype.push = function() {
    var startLength = this.length;
    for (var i = 0; i < arguments.length; i++)
    this[startLength + i] = arguments[i];
  return this.length;
}
}

/**
* 为兼容旧版本的浏览器增加 Function 的 apply 方法。
*/
if (!Function.prototype.apply) {
// Based on code from [url]http://www.youngpup.net/[/url]
Function.prototype.apply = function(object, parameters) {
  var parameterStrings = new Array();
  if (!object)   object = window;
  if (!parameters) parameters = new Array();
 
  for (var i = 0; i < parameters.length; i++)
    parameterStrings[i] = 'parameters[' + i + ']';
 
  object.__apply__ = this;
  var result = eval('object.__apply__(' +
    parameterStrings.join(', ') + ')');
  object.__apply__ = null;
 
  return result;
}
}

/**
* 扩展 javascript 内置的 String 对象
*/
String.prototype.extend({
/**
  * 去掉字符串中的标签
  */
stripTags: function() {
  return this.replace(/<\/?[^>]+>/gi, '');
},
/**
  * 这个方法很常见,通常的实现都是用正则表达式替换特殊字符为html规范定义的命名实体或者十进制编码,比如:
  * string.replace(/&/g, "&").replace(//g, ">");
  * 而这里的实现借用浏览器自身的内部替换,确实巧妙。
  */
escapeHTML: function() {
  var div = document.createElement('div');
  var text = document.createTextNode(this);
  div.appendChild(text);
  return div.innerHTML;
},
  /**
  * 同上
  */
unescapeHTML: function() {
  var div = document.createElement('div');
  div.innerHTML = this.stripTags();
  return div.childNodes[0].nodeValue;
}
});
/**
* 定义 Ajax 对象, 静态方法 getTransport 方法返回一个 XMLHttp 对象
*/
var Ajax = {
getTransport: function() {
  return Try.these(
    function() {return new ActiveXObject('Msxml2.XMLHTTP')},
    function() {return new ActiveXObject('Microsoft.XMLHTTP')},
    function() {return new XMLHttpRequest()}
  ) || false;
}
}
/**
* 我以为此时的Ajax对象起到命名空间的作用。
* Ajax.Base 声明为一个基础对象类型
* 注意 Ajax.Base 并没有使用 Class.create() 的方式来创建,我想是因为作者并不希望 Ajax.Base 被库使用者实例化。
* 作者在其他对象类型的声明中,将会继承于它。
* 就好像 java 中的私有抽象类
*/
Ajax.Base = function() {};
Ajax.Base.prototype = {
/**
  * extend (见上) 的用法真是让人耳目一新
  * options 首先设置默认属性,然后再 extend 参数对象,那么参数对象中也有同名的属性,那么就覆盖默认属性值。
  * 想想如果我写这样的实现,应该类似如下:
  setOptions: function(options) {
    this.options.methed = options.methed? options.methed : 'post';
    ..........
  }
  我想很多时候,java 限制了 js 的创意。
  */
setOptions: function(options) {
  this.options = {
    method:     'post',
    asynchronous: true,
    parameters:   ''
  }.extend(options || {});
},
/**
  * 如果 xmlhttp 调用返回正确的HTTP状态值,函数返回ture, 反之false。
  * xmlhttp 的 readyState 属性不足以准确判断 xmlhttp 远程调用成功,该方法是readyState判断的一个前提条件
  */
responseIsSuccess: function() {
  return this.transport.status == undefined
    || this.transport.status == 0
    || (this.transport.status >= 200 && this.transport.status < 300);
},
/**
  * 如果 xmlhttp 调用返回错误的HTTP状态值,函数返回ture, 反之false。
  */
responseIsFailure: function() {
  return !this.responseIsSuccess();
}
}
/**
* Ajax.Request 封装 XmlHttp
*/
Ajax.Request = Class.create();

/**
* 定义四种事件(状态), 参考[url]http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/readystate_1.asp[/url]
*/
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

/**
* 相比先前的版本,对于 xmlhttp 的调用和返回值处理分离得更为清晰
*/
Ajax.Request.prototype = (new Ajax.Base()).extend({
initialize: function(url, options) {
  this.transport = Ajax.getTransport();
  this.setOptions(options);
  this.request(url);
},

 /**
  * 新增加 request 方法封装 xmlhttp 的调用过程。
  */
request: function(url) {
  var parameters = this.options.parameters || '';
  if (parameters.length > 0) parameters += '&_=';

  try {
    if (this.options.method == 'get')
    url += '?' + parameters;

    this.transport.open(this.options.method, 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) {
  }
},

/**
  * 新增加的 setRequestHeaders 方法允许添加自定义的http header
  */
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');
  }

  /**
  * 其后的 apply 方法的调用有些奇技淫巧的意味
  * 从上下文中我们可以分析出 this.options.requestHeaders 是调用者自定义的http header数组。
  * requestHeaders 也是一个数组,将一个数组中的元素逐个添加到另一个元素中,直接调用
  * requestHeaders.push(this.options.requestHeaders)
  * 是不行的,因为该调用导致 this.options.requestHeaders 整个数组作为一个元素添加到 requestHeaders中。
  * javascript的Array对象还提供一个concat 的方法表面上满足要求,但是concat实际上是创建一个新数组,将两个数组的元素添加到新数组中。
  * 所以,下面的代码也可以替换为
  * requestHeaders = requestHeaders.concat(this.options.requestHeaders);
  * 很显然,作者不喜欢这样的代码方式
  * 而 apply 方法的语法 apply([thisObj[,argArray]]) 本身就要求第二个参数是一个数组或者arguments对象。
  * 所以巧妙的实现了 concat 函数的作用。
  * 令人拍案叫绝啊!
  */
  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]);
},


onStateChange: function() {
  var readyState = this.transport.readyState;
  /**
  * 如果不是 Loading 状态,就调用回调函数
  */
  if (readyState != 1)
    this.respondToReadyState(this.transport.readyState);
},

/**
  * 回调函数定义在 this.options 属性中,比如:
    var option = {
      onLoaded : function(req) {...};
      ......
    }
    new Ajax.Request(url, option);
  */
respondToReadyState: function(readyState) {
  var event = Ajax.Request.Events[readyState];

  /**
  * 新增的回调函数处理,调用者还可以在options中定义 on200, onSuccess 这样的回调函数
  * 在 readyState 为完成状态的时候调用
  */
  if (event == 'Complete')
    (this.options['on' + this.transport.status]
    || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
    || Prototype.emptyFunction)(this.transport);

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

  /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
  if (event == 'Complete')
    this.transport.onreadystatechange = Prototype.emptyFunction;
}
});

/**
* Ajax.Updater 用于绑定一个html元素与 XmlHttp调用的返回值。类似与 buffalo 的 bind。
* 如果 options 中有 insertion(见后) 对象的话, insertion 能提供更多的插入控制。
*/
Ajax.Updater = Class.create();
Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)';

Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
initialize: function(container, url, options) {

  /**
  * containers 就是被绑定的 html 对象,xmlhttp的返回值被赋给该对象的 innerHTML 属性。
  * 相比新版本,containers 根据container参数定义 success 和 failure 引用,如果它们被定义的话,根据xmlhttp调用是否成功来选择
  * 更新对象,假想调用可能如下:
  * var c = {success: $("successDiv"), failure: $("failureDiv")};
  * new Ajax.Updater(c, url, options);
  * 那么调用成功则 successDiv 显示成功信息或者数据,反之 failureDiv 显示错误信息
  */
  this.containers = {
    success: container.success ? $(container.success) : $(container),
    failure: container.failure ? $(container.failure) :
    (container.success ? null : $(container))
  }

  this.transport = Ajax.getTransport();
  this.setOptions(options);

  var onComplete = this.options.onComplete || Prototype.emptyFunction;
  this.options.onComplete = (function() {
    this.updateContent();
    onComplete(this.transport);
  }).bind(this);

  this.request(url);
},

updateContent: function() {
  var receiver = this.responseIsSuccess() ?
    this.containers.success : this.containers.failure;

  var match   = new RegExp(Ajax.Updater.ScriptFragment, 'img');
  var response = this.transport.responseText.replace(match, '');
  var scripts = this.transport.responseText.match(match);

  if (receiver) {
    if (this.options.insertion) {
    new this.options.insertion(receiver, response);
    } else {
    receiver.innerHTML = response;
    }
  }

  if (this.responseIsSuccess()) {
    if (this.onComplete)
    setTimeout((function() {this.onComplete(
      this.transport)}).bind(this), 10);
  }

  /**
  * 如果调用者在传入的options参数中定义 evalScripts=true,同时xmlhttp返回值的html中包含 文本。
    * 没有g, scripts[i].match(match)[1] 匹配的就是

你可能感兴趣的:(prototype,Ajax,正则表达式,JavaScript,IE)