这个部分是jquery一些常用的工具方法. 包括为jquery对象扩展了一些数组里的方法.一些测试方法,函数代理和浏览器的特性检测.
数组和对象操作.这部分的很多方法都已经成为javascript1.6的标准.
这部分包括一些原型函数,静态函数,内部函数.
原型函数主要通过api暴露给外界. 静态方法主要包含了原型方法的具体逻辑实现. 内部函数主要供内部调用.
prototype上的方法一般设计得比较简单, 主要充当一个控制层的作用. 而具体的实现逻辑, 大多数都放在了底层的静态方法中. 这样将来版本升级的时候, 一般只要修改静态方法就可以了,跟api紧密耦合的prototype方法不会受到太多牵连.
这部分其中有一些方法已经在jquery1.43源码分析之核心部分写过了, 看看另外一些方法的实现.
jQuery.isArray
- isArray: function( obj ) {
- return toString.call(obj) === "[object Array]";
- }
判断对象是不是Array类型.这个简单的需求经历了漫长的演变过程.
一般我们判断对象类型, 会想到下面几种方式.
1 typeof typeof 只能判断大概区分是不是对象类型, 不论是Array还是Date还是null,还是通过自定义构造函数生成的对象.返回的都是object
2 instanceof和constructor 都非常容易在原型继承中出现问题,
比如- var A = function(){
-
- }
- A.prototype = new Array;
-
- var a = new A;
-
- alert (a instanceof Array)
而且在不同的iframe中, 判断也不准确, 因为不同的iframe不共享原型链. instanceof和constructor自然也会失效.
3 后来就有了流行一时的鸭式变型. 如果一只鸭子, 它会呱呱叫, 也会像鸭子一样走路. 那么就认为它就是鸭子.
- isArray: function(object) {
- return object != null && typeof object == "object" &&
- 'splice' in object && 'join' in object;
- }
不过也许有那么一只聪明的鸡学会了呱呱叫, 也学会了像鸭子一样走路.
比如 var a = {}; a.splice = 1; a.join = 2;
前面的方法显然都不完美, 直到有个人发现了Object.prototype.toString.call(obj) === "[object Array]", 世界才变美好.其实就是利用toString方法来得到一个包含这个对象内部属性的字符串,这个字符串就包含了此对象构造器的信息.
jQuery.prototype.each
顾名思义, each方法是对数组或者对象中的每个元素都做一些类似的操作. 我们在看源码之前先自己实现一个Array.prototype.each.
- Array.prototype.each = function(fn){
- for (var i = 0, l = this.length; i < l; i++){
- fn(this[i]);
- }
- };
-
- [1, 2, 3].each(function(i){
- alert (i);
- })
很简陋, 然后想想它有哪些不足.
1, 在回调函数里我不知道当前循环到了第几个元素.
2, 回调函数里的this指向了window, 这个没任何意义.
所以现在来稍微改一下.
- Array.prototype.each = function(fn){
- for (var i = 0, l = this.length; i < l; i++){
- fn.call(this[i], i, this[i]);
- }
- };
-
- [1, 2, 3].each(function(i, n){
- alert ([this, i, n]);
- })
现在在回调函数里可以取到3个值, this指向当前元素. i表示循环到了第几个. n也表示当前元素. 当然你也可以把this指向别的东西. jquery就指向了原始元素.
不过如果我想在循环之中退出怎么办, 比如我找到了2, 就想退出循环.那么再修改一下.
- Array.prototype.each = function(fn){
- for (var i = 0, l = this.length; i < l; i++){
- if ( fn.call(this[i], i, this[i]) === false ) break;
- }
- };
-
- [1, 2, 3].each(function(i, n){
- if (n >= 2) {
- return false;
- }
- alert (n)
- })
现在已经基本达到目的了, 再来看看jquery里each的实现.
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
- },
直接交给静态方法jQuery.each来操作. 看看jQuery.each
- jQuery.each
- each: function( object, callback, args ) {
-
-
-
- var name, i = 0,
- length = object.length,
- isObj = length === undefined || jQuery.isFunction(object);
-
-
-
-
- if ( args ) {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.apply( object[ name ], args ) === false ) {
-
-
-
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.apply( object[ i++ ], args ) === false ) {
- break;
- }
- }
- }
-
- } else {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
- break;
- }
- }
- } else {
- for ( var value = object[0];
- i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
- }
- }
-
- return object;
- },
其实each方法还有一个小小的缺陷. 当要遍历一个对象的时候.
比如 { name: "John", lang: "JS" }这个对象. 这样写是没问题的.
- $.each( { name: "John", lang: "JS" }, function(i, n){
- alert( "Name: " + i + ", Value: " + n );
- })
但其实很多时候我们可能希望这样写 - $({ name: "John", lang: "JS" }).each(function(i, n){
- alert( "Name: " + i + ", Value: " + n );
- })
但这样写不行. 因为现在的目标对象object实际上是一个经过包装了的jquery对象. 即使里面是单个元素, 它的length也为1. isObj为false. jquery不会遍历它的属性.
这个方法里的代码其实也可以更精简一点,2个大的条件分支完全可以合并成一个.
jQuery.prototype.map
将一组元素转换成其他数组(不论是否是元素数组)
其实很容易联想到, map方法就是让集合里的每个元素都执行一次同一个函数, 把返回值填充到一个数组并返回这个新的数组.
看个例子- var ary = $.map( [0,1,2], function(n){
- return n + 4;
- });
-
- alert (ary);
ary已经被转化成为[4,5,6].
再看源码- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
- },
看起来有一点点复杂, 其实callback.call( elem, i, elem )得到的是每个元素通过转化之后的值.在jQuery.map里,这些值都会被填充进一个新的数组.最后把原来的引用存入pushStack,方便回溯.
看看jquery.map- map: function( elems, callback, arg ) {
- var ret = [], value;
-
-
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
-
-
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
-
- return ret.concat.apply( [], ret );
-
- },
注意value != null, 这里只有2个等号, 意味着可以也过滤掉返回值为undefined的元素, 比如在map一堆dom节点的子节点时, 如果某个元素没有子节点, 那个元素就会被过滤掉.
可以看测试代码- <body>
- <div><span></span></div>
- <div><span></span></div>
- <div><span></span></div>
- <div><span></span></div>
- <div></div>
- </body>
- <script>
-
- var ary = $.map( document.getElementsByTagName('div'), function(n){
- return n.childNodes[0];
-
- });
-
- alert (ary.length)
- </script>
jQuery.prototype.grep
使用一个过滤函数闭包来按照某种条件来过滤数组
- grep: function( elems, callback, inv ) {
-
- var ret = [];
-
- for ( var i = 0, length = elems.length; i < length; i++ ) {
-
- if ( !inv !== !callback( elems[ i ], i ) ) {
-
-
- ret.push( elems[ i ] );
- }
- }
- return ret;
- },
当过滤函数的返回结果不为undefined, "", 0, false, null这5种情况之一时, 通过!转化恰好不等于默认的inv(默认为false). 条件成立,这个就是需要的元素. 不过一般情况下我们让过滤函数返回true就可以了. 注意并没有用callback.call的形式来调用callback. 所以callback里的this是指向window的.
jQuery.prototype.ready
用来替代window.onload. 每个主流库中都有这个方法的相应实现.
如果使用window.onload, 你必须得等到页面的所有图片,视频等都加载完,才会触发window.onload里面的方法.
下面是页面加载的具体顺序.
onContentReady,这时DOM树完成
script defer 这时开始执行设定了defer属性的script.
某些库比如ext用到了这个属性
ondocumentready complete这时可以使用HTC组件与XHR
html.doScroll 这时可以让HTML元素使用doScroll方法
window.onload 这时图片flash等资源都加载完毕
来自http://www.cnblogs.com/rubylouvre/archive/2009/12/30/1635645.html
jQuery分别用到了onContentReady, html.doScroll, window.onload来确认dom最早加载完成的时间.
jQuery的实现可以大致分为这几个步骤.
1首先把需要在页面加载完成后执行的函数都存到一个list中, 加载完成后再依次触发.并设置一个控制器, 保证一个页面只有一个监听函数在执行.
2判断document.readyState是不是为complete.表示dom是否加载完毕
3 如果2步骤失败, 根据浏览器的不同,给document增加DOMContentLoaded或者onreadystatechange事件,当该事件被触发的时候执行readyList里的方法.
4在IE浏览器中, 设置一个定时器,不停的查看是否已经可以执行这个操作.document.documentElement.doScroll("left");如果执行这个操作时不再抛出异常,说明dom已经加载完毕了.这个办法可能比步骤3更快.
看看代码
- ready: function( fn ) {
- jQuery.bindReady();
-
- if ( jQuery.isReady ) {
- fn.call( document, jQuery );
-
- } else if ( readyList ) {
- readyList.push( fn );
- }
- return this;
- },
这里的逻辑并不复杂, 当调用$().ready(fn) 时, 先通过jQuery.isReady看dom有没有加载完成(jQuery.isReady默认是false, 当加载完成时会被设置为true).如果dom已经加载完成了,就立刻执行fn. 否则, 把fn添加进待执行的数组.等dom加载完成时再执行.
看看bindReady的实现- bindReady: function() {
- if ( readyBound ) {
- return;
- }
- readyBound = true;
-
- if ( document.readyState === "complete" ) {
-
- return jQuery.ready();
- }
- if ( document.addEventListener ) {
-
- document.addEventListener("DOMContentLoaded",DOMContentLoaded, false );
-
-
-
- window.addEventListener( "load", jQuery.ready, false );
-
-
- } else if ( document.attachEvent ) {
- document.attachEvent("onreadystatechange", DOMContentLoaded);
-
-
-
- window.attachEvent( "onload", jQuery.ready );
-
-
-
-
- var toplevel = false;
- try {
- toplevel = window.frameElement == null;
-
- } catch(e) {}
- if ( document.documentElement.doScroll && toplevel ) {
- doScrollCheck();
-
- }
- }
- }
再看看bindReady方法中涉及到的几个方法.
- jQuery.ready
- ready: function() {
- if ( !jQuery.isReady ) {
- if ( !document.body ) {
-
- return setTimeout( jQuery.ready, 13 );
-
-
- }
- jQuery.isReady = true;
- if ( readyList ) {
- var fn, i = 0;
- while ( (fn = readyList[ i++ ]) ) {
- fn.call( document, jQuery );
-
- }
- readyList = null;
-
-
-
-
- }
-
- if ( jQuery.fn.triggerHandler ) {
-
- jQuery( document ).triggerHandler( "ready" );
- }
- }
- }
doScrollCheck
- function doScrollCheck() {
- if ( jQuery.isReady ) {
- return;
- }
-
- try {
- document.documentElement.doScroll("left");
- } catch( error ) {
- setTimeout( doScrollCheck, 1 );
- return;
- }
-
-
- jQuery.ready();
- }
jQuery.proxy
proxy: function( fn, proxy, thisObject ) 返回一个新函数,这个函数的this指向你指定的对象.
jquery1.4总算提供了this代理的方法了, 以前总是要自己实现一个Function.prototype.bind方法.
可能很多同学对this的调用和指向还是有点模糊. 那在此之前,先讲一下this的几种指向情况.
1 普通函数调用, this指向window.
比如
var fn = function(){
alert (this === window)
};
fn();
结果为true
2 对象属性调用, this指向拥有这个属性的对象.
比如 var obj = {
fn: function(){
alert (this === obj);
}
obj.fn();
}
结果为true
或者
document.getElementById("id1").onclick = function(){
alert (this.id);
}
点击后弹出id1, 此时this指向onclick的拥有者id1.
3 通过构造函数调用this时,this指向通过构造函数生成的对象.
比如 function A(){
this.b = 1;
}
var a = new A();
当a去调用A的构造函数时, this是指的a.
4 call或者apply, 这里的this是由自己指定.
比如- (function(){
- alert(this.name);
- }).call({name: "__游乐场"})
这里需要一个括号把function(){alert(this.name)}包围起来是为了让引擎把括号里面的语句当成一个表达式而不是函数声明, 函数声明是不能调用方法的. 编译期进行语法检测的时候就会报错.
其实我们在开发很容易就不知不觉弄丢了this.举个例子.我要点击一个div的时候,弹出这个div的id.- document.getElementById('div1').onclick = function(){
- ~function(){
- alert (this.id);
- }()
- }
这个方法里的this就已经是window了. 一般我们可以改成这样- document.getElementById('div1').onclick = function(){
- var self = this;
- ~function(){
- alert (self.id);
- }()
- }
也许你不喜欢self这个临时变量.那换一种方法.我们扩展一下Function的原型,实现一个最简单的bind方法.
- Function.prototype.bind = function(obj){
- var self = this;
- return function(){
- self.call(obj);
- }
- }
- document.getElementById('div1').onclick = function(){
- ~function(){
- alert (this.id);
- }.bind(this)()
- }
现在已经OK了,没有了讨厌的临时变量. 这里的proxy方法肯定也是利用call或者apply方法来指定this. 看看api上的例子
- var obj = {
- name: "John",
- test: function() {
- alert( this.name );
- $("#test").unbind("click", obj.test);
- } };
- $("#test").click( jQuery.proxy( obj, "test" ) );
-
-
-
-
proxy根据参数传递的不同有2种调用方式,
1, 参数分别为obj对象, 被代理函数(必须是obj对象的属性). 结果是返回obj.test函数, this指向obj.
2, 参数分别为被代理函数(obj.test), this指向obj.
第二种方式看来顺眼得多.再看看源码的具体实现
- proxy: function( fn, proxy, thisObject ) {
-
- if ( arguments.length === 2 ) {
- if ( typeof proxy === "string" ) {
-
- thisObject = fn;
-
-
- fn = thisObject[ proxy ];
-
- proxy = undefined;
-
-
- } else if ( proxy && !jQuery.isFunction( proxy ) ) {
-
- thisObject = proxy;
-
- proxy = undefined;
-
- }
- }
-
- if ( !proxy && fn ) {
-
- proxy = function() {
- return fn.apply( thisObject || this, arguments );
-
- };
- }
-
- if ( fn ) {
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
- }
-
- return proxy;
- }
浏览器特性检测
从jquery1.3版本开始, 写不同浏览器的兼容代码之前, 不赞成再去判断浏览器的类型, 而是直接判断支不支持某个特性, 比如盒模型. 透明度这些. 就像一个老外向你问路的时候, 他肯定是先说can you speak English. 而不是are you Amercan.
关于特性检测的具体实现, 可以参考下面文章.
http://peter./articles/feature-detection-state-of-the-art-browser-scripting
http://yura./cft/
http://www./faq/faq_notes/not_browser_detect.html
|