只更新一条记录 显示多条
分类: Uncategorized
-
跨系统数据同步工具实战经验总结
多个系统间的数据同步是比较常见的,也是可以抽象为通用的场景。以推为例,向外输出数据的时候,可以描述为以下几个点:
1.DataSync的重用
2.有单表、主从表之分。
3.数据不要重复推。
4.单次推送容量控制
针对性的解决方案:
一、DataSync重用
DataSync重用的解决是通过启动传参+配置文件+Sql模板+TableToJson的形式来解决。启动的时候要求指定所使用的配置文件即要启动传参,比如 DataSync Order2OMS,这样子不同的业务场景下,可使用相应的配置运行。而配置文件再指定了取数sql,防重key值,目标URL,数据模板,基本上可以应对大多数业务数据推送。看配置文件:
SqlTemplate:通过sql取数,非常灵活!可以通过sql关键词as,做字段名的转化。
KeyName:指定了用于防重的数据戳
KeyValue:记录了当前最大的数据戳,下次推送就不会重复推了。取数的时候,模板中的{KeyValue}会被替换成实际值。
TargetUrl:数据接取的服务URL,这里可以配置Get参数,可能大多数会使用Post参数,所以这里看不到Get参数
PostDataTemplate:这个定义了数据传输模板,可依据对方接收的不同,定制参数。
NextAction:这个东东有点链式执行的方式,取值为配置文件名,当前推送执行完之后,会执行这个NextAction.这个主要用于解决主从表推送,后续会讲到。
二、单表、主从表
单表、主从表都拉平为单表的目的在于,便于将推送的数据映射到目标DB中,也减少推送方组装业务实体。以下看看怎么拉平处理。
单表,可直接序列化为JSON串,借助开源的JSON类库,可以非常方便的达到此目的。JsonConvert.SerializeObject(dt, new DataTableConverter());
主从表,则依据单表组合而来,为了保持推送模型的简单与统一,对外输出均以单表形式输出。主表先推,从表随后。通过设计一个下一跳动作满足主从表的数据约束。
因此,解决主从表,则从“下一跳”的推送机制来下手,从配置文件来看这种设计:
请关注这个配置中的NextAction,这个指明了,紧接着所需要做的动作。
三、数据防重推
防止重推,增加了一个key值,这个相当于数据戳,前面有介绍到。这里要补充到的是,KeyValue并没有限定取值类型,执行的时候会用于替换Sql模板中的{KeyValue}。一般建议使用自增id,其他类型的也可以试试,只要排序是稳定的。Order主表使用Order_id,明细表使用Order_detail_id,在实际配置的时候,Order推送配置的Naction是OrderDetail,OrderDetail推送配置KeyName应该是Order_Id还是Order_detail_id呢?
四、单次推送容量控制
与top的使用,别一次把整个表给拖出来,搞不好会卡死。
上面讲了推送,下面讲一下接收。接收的话,基于推送拉平成了单表推送,因此可以简单粗暴的映射到持久层。如果不是直接落地,还要向后推,则可以入库后,也配置一个DataSync。
JSON配置尝试,简单的序列化即可以生成好持久化配置文件,感觉非常棒。但是JSON形式的配置有一个尴尬的转义,提示的错误比较古怪。整体来讲,还是不错的。
总结:
NaxtAction的实现方式比较巧妙。目前并没有实际使用。JSON用于配置、推送数据都比较给力。
-
IDC行业两大神坑:景安&电信通(时代宏远)
被电信通忽悠的年代好久了,但是伤痛还在!
服务器挂掉,数据全部丢失!
电信通接着改名为“时代宏远”,给人感觉服务器不稳定!
新增加服务器就不用电信通、时代宏远了!
但是存量的网站还是用时代宏远,也没有去推广,也没有心情去推广,问题太多,经不起折腾!
掉入景安的坑,也是一个意外,好多人在发景安的广告,就用了一个。
结果,也是坑得要死!从8月8号出问题到现在还没有完全处理好!看看这个快云的处理进展!
-
jQuery源码剖析(一)——概览&工具方法(转)
前言
其实这剖析jQuery源码系列文章是我自己酝酿很久的想法,当然因为之前的不成熟以及理解不深刻一直不敢提笔写。近期阅读的书籍多,激发我写文章的欲望,于是把这个计划开始实施。
为什么要选择jQuery源码,我想对于一个前端开发人员来说,不言自明。jQuery的出现对于众多前端开发人员来说,是一个无比强大的武器。那为什么jQuery如此流行,深得大家喜欢?我认为秘籍就在于jQuery的API设计。
在jQuery的设计中,最主要的两块就是(个人看法):
- 提供工具方法;
- DOM元素的操作,包括选取,属性取值等等。 对于一个库来说,第一点是必然要有所体现的,没有基础的工具方法,如何成为库。
对于Javascript来说,最重要的一块就是DOM操作,jQuery在DOM操作这块的贡献是巨大的,以至于现在很多新手完全不会用原生的JS API(例如document.getElementById等)了,jQuery把复杂的DOM选取映射到了简单的CSS选择器,对复杂的DOM操作(不同浏览器的DOM操作接口不一致)封装非常简单的委托API,以达到其核心的目的:The Write Less, Do More。
我选择的jQuery版本是1.9.0,一开始肯定是先把jQuery最简单的部分做剖析。
1.外层沙箱以及命名空间$
几乎稍微有点经验前端人员都这么做,为了避免声明了一些全局变量而污染,把代码放在一个“沙箱执行”,然后在暴露出命名空间(可以为API,函数,对象):
(function( window, undefined ) { //用一个函数域包起来,就是所谓的沙箱 //在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局 //把当前沙箱需要的外部变量通过函数参数引入进来 //只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数 "use strict"; window.jQuery = window.$ = jQuery; })( window );有人会疑问,为什么要第二个参数undefined 。在这里,jquery中有一个针对压缩的小小策略。
先看以下代码:
(function( window, undefined ) { var a = undefined; if (a == undefined){blabla...} .... if (c == undefined) return; })( window );经过压缩后,可以变成:
(function(w, u) { var a = u; if (a == u){blabla...} .... if (c == u) return; })(w);因为这个外层函数只传了一个参数,因此沙箱执行时,u自然会undefined,把9个字母缩成1个字母,可以看出压缩后的代码减少一些字节数。 评论中nodejser对undefined的补充中也给出了另一种解答:
在ECMAScript5之前undefined都是可写的,也就是undefined可以赋值的。jQuery作者这么做的目的还有防止2B程序员对undefined进行赋值后使得代码出现了不可预料的bug。
沙箱中第一句”use strict”;是表示使用javascript的严格模式,对于低级的浏览器,这里相当一字符串,所以兼容性是没问题的,详细的话,在阮一峰的文章Javascript 严格模式详解有介绍。
最后jQuery暴露一个全局的命名空间jQuery(为了书写更简单,一个简写就是$,幸好Javascript用$来做变量命名是合法的)
实际上jQuery是一个函数,为什么要这样设计呢,是因为:
- 函数也是对象,于是在jQuery这个命名空间上可以绑定工具方法
- 函数可以有原型prototype,每当通过dom = $(“#id”)取得的所谓jQuery对象,本质就是dom = new jQuery(‘#id’); 如果懂得原型的话,就知道如果在jQuery的原型上绑定方法,像上边那样生成的实例dom可以调用这些方法。
简单来说,就是把jQuery看成是一个类,在原型上绑定方法就相当于成员方法,在jQuery上绑定工具方法,相当于类的静态方法,举例如下:
jQuery.A = function(){}; jQuery.prototype.B = function(){};相当于:
Class jQuery{ public static A(){} public B(){} }面向对象的思想在jQuery是有所体现的,也给我很多思考,面向对象的思想指导了如何设计一个更合理的API,乃至于库。所谓的封装,继承,通通的都是为了前边那个目的,如何设计出更好的API,我认为这才是面向对象的精髓。
2、jQuery核心的工具方法
以下方法是在jQuery的core定义的工具方法(可以去github的jQuery项目),core是整个jQuery最核心的组成部分,所以从这部分先剖析: $.trim() 去除字符串两端的空格。(内部调用7次) $.each() 遍历数组或对象,这个方法在jQuery内部中被使用很多次,有几个不错的用法,之后剖析再举例吧。(内部调用59次) $.inArray() 返回一个值在数组中的索引位置。如果该值不在数组中,则返回-1。(内部调用9次) $.grep() 返回数组中符合某种标准的元素。(内部调用6次) $.merge() 合并两个数组。(内部调用11次) $.map() 将一个数组中的元素转换到另一个数组中。(内部调用12次) $.makeArray() 将对象转化为数组。(内部调用6次) $.globalEval() 在全局作用域下执行一段js脚本。(内部调用2次) $.proxy() 接受一个函数,然后返回一个新函数,并且这个新函数始终保持了特定的上下文(context)语境。(内部调用0次) $.nodeName() 返回DOM节点的节点名字,或者判断DOM节点名是否为某某名字。(内部调用51次) $.extend() 将多个对象,合并到第一个对象。(内部调用42次)
以下均是对类型的判断,本文只是针对$.type做一下讨论,isXXX的方法基本都是调用$.type来实现,不对它们做细节探讨。 $.type() 判断对象的类别(函数对象、日期对象、数组对象、正则对象等等)。这个方法的实现就是用$.each辅助的。(内部调用65次)$.isArray() 判断某个参数是否为数组。(内部调用12次) $.isEmptyObject() 判断某个对象是否为空(不含有任何属性)。(内部调用4次) $.isFunction() 判断某个参数是否为函数。(内部调用32次) $.isPlainObject() 判断某个参数是否为用”{}”或”new Object”建立的对象。(内部调用4次) $.isWindow() 判断是否为window对象。(内部调用6次)
以下三个函数比较简单,没必要在文章剖析。 $.noop() 一个空函数,个人觉得是用来作为一个默认的回调函数,无需每次去定义一个空的function消耗资源。(内部调用2次) $.now() 获取当前时间戳,代码很简单:return (new Date()).getTime();。(内部调用4次) $.error() 报错,对外抛出一个异常,代码很简单:throw new Error(msg);。(内部调用2次)
以下三个是jQuery主要用来在ajax处理返回数据时使用,其中parseJSON这个接口在实际工程中被用得最多,经常用来把一段文本解析成json格式 $.parseHTML() 解析HTML,之后再单独一节写。(内部调用2次) $.parseJSON() 解析JSON,之后再单独一节写。(内部调用2次) $.parseXML() 解析XML,之后再单独一节写。(内部调用1次)
其中我认为是内部辅助函数如下: $.access() 这个函数我更认为是jQuery内部的辅助函数,没必要暴漏出来,在内部用于去一些对象的属性值等,在之后剖析到DOM操作等再细细探讨一下。(内部调用9次) $.camelCase() 转化成骆驼峰命名。(内部调用12次)
开始窥探源码吧!
2.0 在开始之前
在jQuery一开始的定义里边,有这么一小段
class2type = {}, core_deletedIds = [], core_version = "1.9.0", // Save a reference to some core methods core_concat = core_deletedIds.concat, core_push = core_deletedIds.push, core_slice = core_deletedIds.slice, core_indexOf = core_deletedIds.indexOf, core_toString = class2type.toString, core_hasOwn = class2type.hasOwnProperty, core_trim = core_version.trim, //等同以下代码: core_concat = Array.prototype.concat, //文章一开始的介绍有稍微提到prototype //core_deletedIds是一个数组实例 //core_deletedIds.concat方法就相当于调了Array类中的成员方法concat。需要调用concat时可以通过以下方法调用,关于call跟apply的用法自行理解,:) var arr = []; 方式一:arr.concat(); 方式二:core_concat.call(arr); 方式三:core_concat.apply(arr);
思考下边2个问题:
- jQuery为什么要先把这些方法存储起来?
- jQuery为什么要采用方式二或者三,而不直接使用方式一的做法? 在不查阅资料的前提下,唯一让我觉得作者这么做的原因是因为效率问题。 以下是我的理解:
调用实例arr的方法concat时,首先需要辨别当前实例arr的类型是Array,在内存空间中寻找Array的concat内存入口,把当前对象arr的指针和其他参数压入栈,跳转到concat地址开始执行。 当保存了concat方法的入口core_concat时,完全就可以省去前面两个步骤,从而提升一些性能。 nodejser在评论中也给出了另一种答案: var obj = {}; 此时调用obj.concat是非法的,但是如果jQuery采用上边方式二或者三的话,能够解决这个问题。 也即是让类数组也能用到数组的方法(这就是call跟apply带来的另一种用法),尤其在jQuery里边引用一些DOM对象时,也能完美的用这个方法去解决,妙!
2.1 $.trim
jQuery的trim函数是用来去除字符串两端空格(jQuery源码里边使用了7次),这个函数也是使用频率很高的,因为时常要对用户在页面上输入的文本trim一下~
用法:$.trim(” 前尾有空格 “) === “前尾有空格”
jQuery的trim源码如下:
core_version = "1.9.0", core_trim = core_version.trim, rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, trim: core_trim && !core_trim.call("\uFEFF\xA0") ? function( text ) { return text == null ? "" : core_trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : ( text + "" ).replace( rtrim, "" ); }剖析之:
var core_trim = String.prototype.trim; if (core_trim && !core_trim.call(“\uFEFF\xA0”)) 相当于: if (String.prototype.trim && “\uFEFF\xA0”.trim() !== “”) 高级的浏览器已经支持原生的String的trim方法,但是jQuery还为了避免它没法解析全角空白,所以加多了一个判断:”\uFEFF\xA0″.trim() !== “”
\uFEFF是utf8的字节序标记,详见:字节顺序标记 “\xA0″是全角空格 如果以上条件成立了,那就直接用原生的trim函数就好了,展开也即是:
$.trim = function( text ) { return text == null ? "" : text.trim(); }如果上述条件不成立了,那jQuery就自己实现一个trim方法:
$.trim = function( text ) { return text == null ? "" : ( text + "" ).replace( rtrim, "" ); }当然你还得自己看懂rtrim这个正则表达的意思了。
2.2 $.each
jQuery的each是我使用最频繁的方法,在它内部也是使用很频繁的一个API(一共用了59处),使得遍历一个数组或者对象的代码段变得十分的简洁,API的设计准则应当如此。
先看几个each的例子:
//each接受2个参数, 数组[1,2,3],回调函数 $.each([1,2,3], function(key, value){ console.log("[" + key + "]=" + value); return false; }); //输出: [0]=1 [1]=2 [2]=3 [1,2,3] //可以看到回调函数具有两个参数,key是数组的索引,value是对应的元素 //each接受3个参数, 数组[1,2,3],回调函数,一个额外的参数数组args=[4,5] $.each([1,2,3], function(arg1, arg2){ console.log(this + "," + arg1 + "," + arg2); }, [4, 5]); //输出: 1,4,5 2,4,5 3,4,5 [1,2,3] //可以看到回调函数的两个参数就是each的第三个参数args,在函数里边的this就是遍历元素自己接着我们来看jQuery的each实现。
//其实我觉得jQuery这段each代码写得一点也不好 //代码重复率太高了!我下边对它进行解析并改造 //貌似看注释是为了让代码运行更快,具体没测试。 //源码如下 $.each = function( obj, callback, args ) { //obj 是需要遍历的数组或者对象 //callback是处理数组/对象的每个元素的回调函数,它的返回值实际会中断循环的过程 // var value, i = 0, length = obj.length, isArray = isArraylike( obj );//判断是不是数组 if ( args ) { if ( isArray ) {//数组 for ( ; i < length; i++ ) { value = callback.apply( obj[ i ], args ); //相当于: //args = [arg1, arg2, arg3]; //callback(args1, args2, args3)。然后callback里边的this指向了obj[i] if ( value === false ) { //注意到,当callback函数返回值会false的时候,注意是全等!循环结束 break; } } } else {//非数组 for ( i in obj ) {//遍历对象的做法 value = callback.apply( obj[ i ], args ); if ( value === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isArray ) { for ( ; i < length; i++ ) { value = callback.call( obj[ i ], i, obj[ i ] ); //相当于callback(i, obj[i])。然后callback里边的this指向了obj[i] if ( value === false ) { break; } } } else { for ( i in obj ) { value = callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } } } return obj; }each的实现比较简单,但是我不是很明白为什么jQuery的实现需要这么长,如果说jQuery为了效率写成以上那么长的代码的话,那我宁愿牺牲那一点点效率把代码量降低
//我改写的版本 //这里并不是代表我的代码没有bug,for in对于数组来说性能比较低而且存在某些bug //这里想表达的意思只是我希望用更简洁的代码来减少原先代码的重复性 $.each = function( obj, callback, args ) { var value, i = 0, length = obj.length; for ( i in obj ) { value = args ? callback.apply( obj[ i ], args ) : callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } return obj; }要熟记$.each的用法,因为在之后的剖析中肯定会多次用到each函数。:)
2.3 $.inArray
jQuery的inArray实现比较简单,有一个小小的点在这里讨论一下,代码如下:
core_deletedIds = [], core_indexOf = core_deletedIds.indexOf, //相当于 core_indexOf = Array.indexOf; //elem 规定需检索的值。 //arr 数组 //i 可选的整数参数。规定在数组中开始检索的位置。它的合法取值是 0 到 arr.length - 1。如省略该参数,则将从数组首元素开始检索。 inArray: function( elem, arr, i ) { var len; if ( arr ) { //原生的Array对象支持indexOf方法,直接调用 if ( core_indexOf ) { return core_indexOf.call( arr, elem, i ); } len = arr.length; //当i为负数的时候,从数组后边len+i的位置开始索引 i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays //jQuery这里的(i in arr)判断是为了跳过稀疏数组中的元素 //例如 var arr = []; arr[2] = 1; //此时 arr == [undefined, undefined, 1] //结果是 => (0 in arr == false) (1 in arr == false) (2 in arr == true) //但是不解的是这里 //测试了一下 $.inArray(undefined, arr, 0)是返回-1的 //也许你很不解,因为数组中明明第一个元素就是undefined,后边举个例子探讨一下 if ( i in arr && arr[ i ] === elem ) { return i; } } } //全部都不符合,返回-1 return -1; },测试了一下(在火狐下): var arr = [undefined, 1]; 输出是: arr == [undefined, 1] 0 in arr == true; arr.indexOf(undefined) == 0; arr[0] == undefined; 但是如果是以下代码: var arr = []; a[1] = 1; 输出是: arr == [undefined, 1] 0 in arr == false; arr.indexOf(undefined) == -1; arr[0] == undefined;
这就说明了在第二种情况中(就是注释中提到的稀疏数组),不能只是通过arr[ i ] === elem来得到元素在数组中的位置 而应该加入多一个条件;i in arr
2.4 $.grep
jQuery的grep是为了传入一个数组elems,通过过滤器callback后,等到过滤后的结果。 其中callback就是过滤器,$.grep是根据其返回值过滤; inv为true表示是callback过滤器返回true的那些被过滤掉。 说起来有点绕,看两个小例子就完全明白了:
$.grep( [0,1,2], function(n,i){ return n <= 0; }); //结果是:[0] $.grep( [0,1,2], function(n,i){ return n <= 0; }, true); //结果是:[1, 2]实现比较简单,但是不知道在这里设计的callback接受的参数顺序没跟$.each的callback一致,我认为这里这样设计的原因是往往过滤器只需使用value,这样就可以忽略第二个参数了 代码如下:
grep: function( elems, callback, inv ) { var retVal, ret = [], i = 0, length = elems.length; inv = !!inv;//转成布尔型 // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { retVal = !!callback( elems[ i ], i );//注意这里的callback参数是先value,后key if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }2.5 $.merge
$.merge([0,1,2], [2,3,4]) == [0, 1, 2, 2, 3, 4] merge的两个参数必须为数组,作用就是修改第一个数组,使得它末尾加上第二个数组。 其实单单从例子或者说明来看,merge函数的两个参数貌似必须为数组,但是看其源码就知道有个小细节,jQuery允许第二个参数不为数组。 $.merge()源码如下:
merge: function( first, second ) { //难道first 跟second参数必须为数组? var l = second.length, i = first.length, j = 0; //从这个判断来看,second可以是一个对象 if ( typeof l === "number" ) {//如果second是数组 for ( ; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else {//如果second不是数组 //假设second = {}; second[0] = 1; second[1] = 2; second[2] = 3; //那这里的逻辑也是可以成立的 while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }从上边源码来看,貌似jQuery的merge函数的first参数貌似是要数组类型,其实不然,first={length:3};first[0] = 1; first[1] = 2; first[2] = 3; 同样也是可以达到merge目的。 当然了,这里这样分析的目的是赞一下javascript的灵活性,实际使用merge函数时,我们应当保证两个参数都是数组类型,避免了书写出上述分析那样难以理解意思的代码。
2.6 $.map
从$.map代码上看,还算比较容易理解,把数组每一项经过callback处理后的值依次加入到返回数组中 例如
$.map( [0,1,2], function(n){ return n + 4; }); //结果:[4, 5, 6]$.map源码如下:
core_deletedIds = [], core_concat = core_deletedIds.concat, // arg is for internal usage only map: function( elems, callback, arg ) { var value, i = 0, length = elems.length, isArray = isArraylike( elems ), ret = []; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) {//如果返回值是null,则不加入结果中 ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays //这里相当于 var a = [];a.concat(ret) return core_concat.apply( [], ret ); },但是看到map实现代码的最后一句,为什么不直接return ret呢? 一开始很费解这里,但是如果看了以下例子之后,你就焕然大悟了,:)
$.map( [0,1,2], function(n){ return [ n, n + 1 ]; }); //输出:[0, 1, 1, 2, 2, 3] //如果是return ret的话,输出将会是:[[0,1], [1,2], [2,3]]在w3cschool里边对此方法的描述有误:.map(callback(index,domElement)) 其实应该是:.map(callback(domElement,index))
2.7 $.makeArray
$.makeArray将类数组对象转换为数组对象,源码简单,注释如下:
makeArray: function( arr, results ) { var ret = results || [];//不由得不赞js这个技巧 //等同于:var ret = (!results) ? [] : results; if ( arr != null ) { if ( isArraylike( Object(arr) ) ) { //如果arr是一个类数组对象,调用merge合到返回值 jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else {//如果不是数组,则将其放到返回数组末尾 //等同于ret.push(arr); core_push.call( ret, arr ); } } return ret; },2.8 $.globalEval
看名字就知道,这是一个在eval的变种而已,它的作用域是在window下,当然采用call方法。 源码如下:
// Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && jQuery.trim( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } },等同于
globalEval: function( data ) { if ( data && jQuery.trim( data ) ) {//data不是空字符串 var fn = window.execScript; //在IE跟旧版本的Chrome是支持此方法的 if (!window.execScript){//新版浏览器没有此api fn = function( data ) { window[ "eval" ].call( window, data ); //这里为何不能直接:eval.call( window, data ); //查了以下,在chrome一些旧版本里eval.call( window, data )无效 } } fn(data); } },那为什么需要使用globalEval呢?跟直接用eval的差别在于哪? 我认为以下场景需要用到: a.js在某个函数域里边动态加载b.js内容,然后执行。 b.js的代码为:function B(){},它是希望B函数暴漏在全局域的。 此时如果在a.js里边采用eval就会使得B只能成为一个局部域里边的函数,所以就必须借globalEval方法做到。 在jQuery源码里边,对于操作dom里边的script以及ajax拉回来的script都有globalEval的身影。
2.9 $.proxy
proxy这个函数用的较少,我认为使用它会造成一些困惑,所以还不如用一些比较好理解的方式去替代它。 源码如下:
// Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { var tmp, args, proxy; if ( typeof context === "string" ) { tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind //由此看出,proxy的参数必须是2个,否则有无法预料的bug args = core_slice.call( arguments, 2 ); proxy = function(/*arguments*/) { //需要注意到的是这里的arguments是当前函数的参数列表 return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed //之后再探讨以下guid是做什么的 proxy.guid = fn.guid = fn.guid || jQuery.guid++; return proxy; },乍看之下,会把上述proxy函数里边的arguments跟其外层的arguments搞混。 看个例子:
function a(){ console.log(arguments);//输出[1,2,3,4,5] } var b = $.proxy(a, this, 1, 2, 3);//这里arguments = [a, this, 1, 2, 3]; b(4,5);//这里arguments = [4,5];如果分析不够准确地化会把上边输出认为是[1,2,3] 其实理解透上边proxy的arguments,就知道 proxy函数里边的args.concat( core_slice.call( arguments )其实就是[1,2,3].concat([4,5])
2.10 $.nodeName
nodeName函数是获取dom节点的节点名字或者判断其名字跟传入参数是否匹配。代码很简单,这里可以看到一个技巧,这是在javascript里边惯用的。(见代码注释) 源码如下:
nodeName: function( elem, name ) { //IE下,DOM节点的nodeName是大写的,例如DIV //所以统一转成小写再判断 //这里不return elem.nodeName.toLowerCase(); //我认为原因是为了保持浏览器自身的对外的规则,避免所有引用nodeName都要做转换的动作 return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); //可以看到这里的&&使用技巧,以上代码等同于: //if (elem.nodeName) return elem.nodeName.toLowerCase() === name.toLowerCase(); //else return elem.nodeName; //如此之简洁 },2.11 $.type
接下来只需要介绍type函数,这里就是$.each的一个好样例。 先了解一下typeof以及toString:
typeof 1 == 'number' typeof {} == 'object' typeof [] == 'object' (1).toString() == "1" ({}).toString() == "[object Object]" //再针对一些边界的测试, typeof null == "object" typeof undefined == "undefined" (null).toString()//非法 (undefined).toString()//非法 //再看看很诡异的几个: ([]).toString() == "" (new Error()).toString() == "Error" //出现以上两个的结果的原因是,Array跟Error类重写了toSting方法 //如果用Object的toString方法的话,就是一下结果 Object.prototype.toString.call([]) == "[object Array]" Object.prototype.toString.call(new Error()) == "[object Error]"看完以上的实验,再回过头看一下$.type函数,其作用就是类似typeof这个操作符,简单来说,也即是: $.type = function(obj) { return typeof obj; } 但是对于数组来说,本质它还是算是一个对象,typeof并不能区分出它是Array类型,jQuery为了扩展typeof的表达力,因此扩展了type方法,针对一些特殊的对象(例如null,window,RegExp)也进行精准的类型判断。 它先将常见类型打表(打表的意思就是先存在一个Hash表class2type里边),源码如下:
// Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); $.type = function( obj ) { if ( obj == null ) {//如果是null或者undefined,直接转成String返回 //注意到这里是==而不是=== return String( obj ); } //RegExp,Array等都属于Object //为了精准判断类型,借由Object.prototype.toString跟class2type表 //这里为什么要用core_toString而不用obj.toString的原因在刚刚试验中说明了 return typeof obj === "object" || typeof obj === "function" ? class2type[ core_toString.call(obj) ] || "object" : typeof obj; },isXXX在这里就不详细展开了,源码如下
// See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, isWindow: function( obj ) { return obj != null && obj == obj.window; }, isNumeric: function( obj ) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object if ( obj.constructor && !core_hasOwn.call(obj, "constructor") && !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || core_hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; },2.12 $.extend
extend函数(注意extends是js里边的保留关键字,所以这里命名末尾是没有s的)是使用频率很高的一个函数,通常你写一个插件,会利用extend传进来的参数来覆盖插件原有的配置。 例如:
var Plugin = function(opt){ //opt为传入的配置 this.opt = $.extend({ 'title':'默认标题', 'content':'默认内容' }, opt); }; var p = new Plugin({title:'传入标题'})$.extend源码如下:
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { //第一个参数表示是否要深递归,类型是布尔值 deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { //$("#id").extend(dest)的时候 target = this; --i; } for ( ; i < length; i++ ) {//可以传入多个复制源 // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object //将每个源的属性全部复制到target上 for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { //防止有环,例如 extend(true, target, {'target':target}); continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { //如果是深复制 if ( copyIsArray ) { copyIsArray = false;//这句话我认为是多余的。 //克隆原来target上的原属性 clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them //递归深复制 target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values //可以看到undefined的属性对时不会复制到target上的 } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; };================================================================================== 至此,绑定在jQuery上一些对外的方法已经分析完毕,代码算比较简单清晰,篇幅略长。 下一篇会把jQuery对ready事件的处理进行讨论。
-
javascript ? : 多条语句
var m=1>0?’好’ :’不好’
这段相当于
if(1>0){
m=’好’ ;
}else{
m=’不好’;
}
复杂的:
i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
测试结果:
输入:i:-10;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-9;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-8;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-7;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-6;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-5;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:-4;len:5
T.html:12 结果:i:1;len:5
T.html:6 输入:i:-3;len:5
T.html:12 结果:i:2;len:5
T.html:6 输入:i:-2;len:5
T.html:12 结果:i:3;len:5
T.html:6 输入:i:-1;len:5
T.html:12 结果:i:4;len:5
T.html:6 输入:i:0;len:5
T.html:12 结果:i:0;len:5
T.html:6 输入:i:1;len:5
T.html:12 结果:i:1;len:5
T.html:6 输入:i:2;len:5
T.html:12 结果:i:2;len:5
T.html:6 输入:i:3;len:5
T.html:12 结果:i:3;len:5
T.html:6 输入:i:4;len:5
T.html:12 结果:i:4;len:5
T.html:6 输入:i:5;len:5
T.html:12 结果:i:5;len:5
T.html:6 输入:i:6;len:5
T.html:12 结果:i:6;len:5
T.html:6 输入:i:7;len:5
T.html:12 结果:i:7;len:5
T.html:6 输入:i:8;len:5
T.html:12 结果:i:8;len:5
T.html:6 输入:i:9;len:5
T.html:12 结果:i:9;len:5样本:
输入:i:-4;len:5
结果:i:1;len:5i = -4 ? -4 < 0 ? Math.max( 0, 5 -4 ) : -4 : 0;
第一步
i=-4?-4<0?1:-4:0;
第二步:
i=-4<0?1:-4;
i=1;
详细参考:
先看javascript对于布尔值的处理约定
布尔值代表真或假、开或关、是或否,只有两个值 true、false。
在JavaScript中,任意的JavaScript值都可以转换成布尔值:
undefined ,null ,0 ,-0 ,NaN ,“” 会被转换成false;
所有其他值,包括所有对象(数组)都会转换成true
假值(falsy value)–false和上面六个可以转换成false的值;
真值(truthy value) — true和可以转换成true的值;
在JavaScript期望使用一个布尔值时,假值会被当成false,真值会被当成true。
if(o !== null)……只有当o不是null时才会执行之后的代码
if(o)……o不是任何假值都会执行之后的代码
然后再看运算优先级
最基木的运算符优先
级就是所谓的“先乘除,后加减”。对于优先顺序处于同一层次上的运算符,按照从左
到右出现的顺序计算。下面给出javascript定义的所有运算符的优先级。
运算符 优先顺序
成员选择、括号、函数调用、数组下标 1
!、-(单目减)、++、–、typeof, new, void, delete 2
*、/、% 3
+、- 4
<<、>>、>>> 5
<、<=、<、>= 6
==、!=、===、!==、 7
& 8
^ 9
| 10
&& 11
|| 12
?: 13
=、+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、^=、|= 14
逗号运算符 (,) 15 -
这些年、我收集的JQuery代码(转)
1. 如何创建嵌套的过滤器
//允许你减少集合中的匹配元素的过滤器, //只剩下那些与给定的选择器匹配的部分。在这种情况下, //查询删除了任何没(:not)有(:has) //包含class为“selected”(.selected)的子节点。 .filter(":not(:has(.selected))")2. 如何重用元素搜索
var allItems = $("div.item"); var keepList = $("div#container1 div.item"); //现在你可以继续使用这些jQuery对象来工作了。例如, //基于复选框裁剪“keep list”,复选框的名称 //符合<DIV>class names:
$(formToLookAt + " input:checked").each(function () { keepList = keepList.filter("." + $(this).attr("name")); });</DIV>
3. 任何使用has()来检查某个元素是否包含某个类或是元素
//jQuery 1.4.*包含了对这一has方法的支持。该方法找出 //某个元素是否包含了其他另一个元素类或是其他任何的 //你正在查找并要在其之上进行操作的东东。 $("input").has(".email").addClass("email_icon");4. 如何使用jQuery来切换样式表
//找出你希望切换的媒体类型(media-type),然后把href设置成新的样式表。
$(‘link[media=”screen”]’).attr(‘href’, ‘Alternative.css’);
5. 如何限制选择范围(基于优化目的)
//尽可能使用标签名来作为类名的前缀, //这样jQuery就不需要花费更多的时间来搜索 //你想要的元素。还要记住的一点是, //针对于你的页面上的元素的操作越具体化, //就越能降低执行和搜索的时间。var in_stock = $('#shopping_cart_items input.is_in_stock');<ul id="shopping_cart_items"> <li><input type="radio" value="Item-X" name="item" class="is_in_stock" />Item X</li> <li><input type="radio" value="Item-Y" name="item" class="3-5_days" />Item Y</li> <li><input type="radio" value="Item-Z" name="item" class="unknown" />Item Z</li> </ul>6. 如何正确地使用ToggleClass
//切换(toggle)类允许你根据某个类的 //是否存在来添加或是删除该类。 //这种情况下有些开发者使用:a.hasClass('blueButton') ? a.removeClass('blueButton') : a.addClass('blueButton');//toggleClass允许你使用下面的语句来很容易地做到这一点
a.toggleClass(‘blueButton’);
7. 如何设置IE特有的功能
if ($.browser.msie) {
// Internet Explorer其实不那么好用}
8. 如何使用jQuery来代替一个元素
$(‘#thatdiv’).replaceWith(‘fnuh’);
9. 如何验证某个元素是否为空
if ($(‘#keks’).html().trim()) {
//什么都没有找到;}
10. 如何从一个未排序的集合中找出某个元素的索引号
$("ul > li").click(function () { var index = $(this).prevAll().length; });11. 如何把函数绑定到事件上
$('#foo').bind('click', function () { alert('User clicked on "foo."'); });12. 如何追加或是添加html到元素中
$(‘#lal’).append(‘sometext’);
13. 在创建元素时,如何使用对象字面量(literal)来定义属性
var e = $(“”, { href: “#”, class: “a-class another-class”, title: “…” });
14. 如何使用多个属性来进行过滤
//在使用许多相类似的有着不同类型的input元素时, //这种基于精确度的方法很有用var elements = $('#someid input[type=sometype][value=somevalue]').get();15. 如何使用jQuery来预加载图像
jQuery.preloadImages = function () { for (var i = 0; i < arguments.length; i++) { $("<img />").attr('src', arguments[i]); } };//用法 $.preloadImages(‘image1.gif’, ‘/path/to/image2.png’, ‘some/image3.jpg’);
16. 如何为任何与选择器相匹配的元素设置事件处理程序
$('button.someClass').live('click', someFunction); //注意,在jQuery 1.4.2中,delegate和undelegate选项 //被引入代替live,因为它们提供了更好的上下文支持 //例如,就table来说,以前你会用 //.live() $("table").each(function () { $("td", this).live("hover", function () { $(this).toggleClass("hover"); }); }); //现在用 $("table").delegate("td", "hover", function () { $(this).toggleClass("hover"); });17. 如何找到一个已经被选中的option元素
$(‘#someElement’).find(‘option:selected’);
18. 如何隐藏一个包含了某个值文本的元素
$(“p.value:contains(‘thetextvalue’)”).hide();
19. 如果自动滚动到页面中的某区域
jQuery.fn.autoscroll = function (selector) {
$(‘html,body’).animate( { scrollTop: $(this).offset().top },
500
);
}//然后像这样来滚动到你希望去到的class/area上。
$(‘.area_name’).autoscroll();
20. 如何检测各种浏览器
if( $.browser.safari) //检测Safari
if ($.browser.msie && $.browser.version > 6 ) //检测IE6及之后版本
if ($.browser.msie && $.browser.version <= 6 ) //检测IE6及之前版本
if ($.browser.mozilla && $.browser.version >= ‘1.8’ ) //检测FireFox 2及之后版本
21. 如何替换串中的词
var el = $(‘#id’); el.html(el.html().replace(/word/ig, ”));
22. 如何禁用右键单击上下文菜单
$(document).bind(‘contextmenu’, function (e) {
return false ;
});23. 如何定义一个定制的选择器
$.expr[':'].mycustomselector = function(element, index, meta, stack){ // element- 一个DOM元素 // index – 栈中的当前循环索引 // meta – 有关选择器的元数据 // stack – 要循环的所有元素的栈 // 如果包含了当前元素就返回true // 如果不包含当前元素就返回false }; // 定制选择器的用法: $('.someClasses:test').doSomething();24. 如何检查某个元素是否存在
if ($(‘#someDiv’ ).length) {
//你妹,终于找到了}
25. 如何使用jQuery来检测右键和左键的鼠标单击两种情况
$("#someelement").live('click', function (e) { if ((!$.browser.msie && e.button == 0) || ($.browser.msie && e.button == 1)) { alert("Left Mouse Button Clicked"); } else if (e.button == 2) { alert("Right Mouse Button Clicked"); } });26. 如何显示或是删除input域中的默认值
//这段代码展示了在用户未输入值时, //如何在文本类型的input域中保留 //一个默认值$(".swap").each(function (i) { wap_val[i] = $(this).val(); $(this).focusin(function () { if ($(this).val() == swap_val[i]) { $(this).val(""); } }).focusout(function () { if ($.trim($(this).val()) == "") { $(this).val(swap_val[i]); } }); });27. 如何在一段时间之后自动隐藏或关闭元素(支持1.4版本)
//这是1.3.2中我们使用setTimeout来实现的方式
setTimeout(function () { $('.mydiv').hide('blind', {}, 500) }, 5000); //而这是在1.4中可以使用delay()这一功能来实现的方式(这很像是休眠) $(".mydiv").delay(5000).hide('blind', {}, 500);28. 如何把已创建的元素动态地添加到DOM中
var newDiv = $(”);
newDiv.attr(‘id’, ‘myNewDiv’).appendTo(‘body’);
29. 如何限制“Text-Area”域中的字符的个数
jQuery.fn.maxLength = function (max) { this.each(function () { var type = this.tagName.toLowerCase(); var inputType = this.type ? this.type.toLowerCase() : null; if (type == "input" && inputType == "text" || inputType == "password") { this.maxLength = max; } else if (type == "textarea") { this.onkeypress = function (e) { var ob = e || event; var keyCode = ob.keyCode; var hasSelection = document.selection ? document.selection.createRange().text.length > 0 : this.selectionStart != this.selectionEnd; return !(this.value.length >= max && (keyCode > 50 || keyCode == 32 || keyCode == 0 || keyCode == 13) && !ob.ctrlKey && !ob.altKey && !hasSelection); }; this.onkeyup = function () { if (this.value.length > max) { this.value = this.value.substring(0, max); } }; } }); };//用法 $(‘#mytextarea’).maxLength(500);
30. 如何为函数创建一个基本的测试
//把测试单独放在模块中 module("Module B"); test("some other test", function () { //指明测试内部预期有多少要运行的断言 expect(2); //一个比较断言,相当于JUnit的assertEquals equals(true, false, "failing test"); equals(true, true, "passing test"); });31. 如何在jQuery中克隆一个元素
var cloned = $(‘#somediv’).clone();
32. 在jQuery中如何测试某个元素是否可见
if ($(element).is(‘:visible’) ) {
//该元素是可见的}
33. 如何把一个元素放在屏幕的中心位置
jQuery.fn.center = function () { this.css('position', 'absolute'); this.css('top', ($(window).height() - this.height())
/ +$(window).scrollTop() + 'px'); this.css('left', ($(window).width() - this.width())
/ 2 + $(window).scrollLeft() + 'px'); return this; }//这样来使用上面的函数: $(element).center();
34. 如何把有着某个特定名称的所有元素的值都放到一个数组中
var arrInputValues = new Array();
$(“input[name=’table[]’]”).each(function () {
arrInputValues.push($(this ).val());
});35. 如何从元素中除去HTML
(function ($) { $.fn.stripHtml = function () { var regexp = /<("[^"]*"|'[^']*'|[^'">])*>/gi; this.each(function () { $(this).html($(this).html().replace(regexp, "")); }); return $(this); } })(jQuery);//用法: $(‘p’).stripHtml();
36. 如何使用closest来取得父元素
$(‘#searchBox’).closest(‘div’);
37. 如何使用Firebug和Firefox来记录jQuery事件日志
// 允许链式日志记录 // 用法: $('#someDiv').hide().log('div hidden').addClass('someClass'); jQuery.log = jQuery.fn.log = function (msg) { if (console) { console.log("%s: %o", msg, this); } return this; };38. 如何强制在弹出窗口中打开链接
jQuery('a.popup').live('click', function () { newwindow = window.open($(this).attr('href'), '', 'height=200,width=150'); if (window.focus) { newwindow.focus(); } return false; });39. 如何强制在新的选项卡中打开链接
jQuery('a.newTab').live('click', function () { newwindow = window.open($(this).href); jQuery(this).target = "_blank"; return false; });40. 在jQuery中如何使用.siblings()来选择同辈元素
// 不这样做
$('#nav li').click(function () { $('#nav li').removeClass('active'); $(this).addClass('active'); });//替代做法是
$('#nav li').click(function () { $(this).addClass('active').siblings().removeClass('active'); });41. 如何切换页面上的所有复选框
var tog = false ;
// 或者为true,如果它们在加载时为被选中状态的话$('a').click(function () { $("input[type=checkbox]").attr("checked", !tog); tog = !tog; });42. 如何基于一些输入文本来过滤一个元素列表
//如果元素的值和输入的文本相匹配的话 //该元素将被返回$('.someClass').filter(function () { return $(this).attr('value') == $('input#someId').val(); })43. 如何获得鼠标垫光标位置x和y
$(document).ready(function () { $(document).mousemove(function (e) { $('#XY').html("X Axis : " + e.pageX + " | Y Axis " + e.pageY); }); });44. 如何把整个的列表元素(List Element,LI)变成可点击的
$("ul li").click(function () { window.location = $(this).find("a").attr("href"); return false; });<ul> <li><a href="#">Link 1</a></li> <li><a href="#">Link 2</a></li> <li><a href="#">Link 3</a></li> <li><a href="#">Link 4</a></li> </ul>45. 如何使用jQuery来解析XML(基本的例子)
function parseXml(xml) { //找到每个Tutorial并打印出author $(xml).find("Tutorial").each(function () { $("#output").append($(this).attr("author") + ""); }); }46. 如何检查图像是否已经被完全加载进来
$('#theImage').attr('src', 'image.jpg').load(function () { alert('This Image Has Been Loaded'); });47. 如何使用jQuery来为事件指定命名空间
//事件可以这样绑定命名空间 $('input').bind('blur.validation', function (e) { // ... }); //data方法也接受命名空间 $('input').data('validation.isValid', true);48. 如何检查cookie是否启用
var dt = new Date(); dt.setSeconds(dt.getSeconds() + 60); document.cookie = "cookietest=1; expires=" + dt.toGMTString(); var cookiesEnabled = document.cookie.indexOf("cookietest=") != -1; if (!cookiesEnabled) { //没有启用cookie }49. 如何让cookie过期
var date = new Date(); date.setTime(date.getTime() + (x * 60 * 1000)); $.cookie('example', 'foo', { expires: date });50. 如何使用一个可点击的链接来替换页面中任何的URL
$.fn.replaceUrl = function () { var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; this.each(function () { $(this).html( $(this).html().replace(regexp, '<a href="$1">$1</a>') ); }); return $(this); }//用法 $(‘p’).replaceUrl();
终于完了,排版也是个体力活.资源整理自网络,送给那些没有送藏的童鞋,如果你已经收藏过了,请不要扔砖头.

(多谢大家的反馈,错误的地方已经更正了,希望没有误导大家
)
PS:由于更正错误,导致排版乱掉,现在重新发布。http://www.cnblogs.com/edison1105/archive/2012/07/31/2617518.html