Posts tagged JavaScript
慎用 script 节点的 src 属性来传递参数
Nov 13th
在有些使用 javascript 来渲染数据的时候,为了能动态获取不同的数据,并且保持 javascript 代码的可扩展性,会将 javascript 代码中获取数据的部分需要的参数提取出来,做为参数放在 script 节点的外部。
一般来说,传递参数到 javascript 文件内部的方法有两种,一种是将参数写在一个 script 节点中,写成全局变量的方式的传递给紧接着这个 script 节点的外部 javascript 中,Google Analytics 就是使用这样的方式:
<script type="text/javascript"> var p1 = "v1", p2 = "v2"; </script> <script type="text/javascript" src="foo.js"></script>
另外一种是将参数直接写在 script 节点的 src 属性中,相当于一个页面的查询字符串一样:
<script type="text/javascript" src="foo.js?p1=v1&p2=v2"></script>不过,使用 script 节点的 src 属性来传递参数需要注意一个很重要的问题,那就是动态变化的 src 属性会导致缓存失效。
现在,为了网站性能的需要,一般都会将 javascript 文件放在独立的服务器上,并设置一个较长的过期时间,这样客户端只会在第一次访问网站时需要去下载这个 javascript 文件。但是,如果使用 src 来传递参数,就可能会使这种缓存策略失效。特别是 src 中存在动态参数的情况,例如统计脚本中如果有一个 ip 参数,那么访客每次连上线时,可能 ip 都会不同,就会导致 javascript 缓存失效了。
解决这个问题的方法也很简单,简单地的将 src 属性中的参数放到 script 节点的一个自定义属性中就可以了,例如 data-args,而 src 属性只需要保留一个时间戳就可以了。因为使用 src 属性来传递参数本来就需要定位 script 节点,所以改由 data-args 自定义属性来传递参数并不会增加额外的代码。只不过页面会通不过 w3c 的验证罢了 :)
<script type="text/javascript" src="foo.js" data-args="p1=v1&p2=v2"></script>
再次提醒,慎用 script 节点的 src 属性来传递参数 :)
使用 arguments.caller 实现自动回调
Oct 25th
这是一篇介绍类似于 js hack 的文章,只是说明了一种可行的途径,并且可能会增加代码复杂程度,具体项目中是否可以使用还请自辨:)
在前端开发过程中,有许多业务流程可能是需要用户进行登录的,并且登录过程是放在弹出层中,这样就可以不用刷新页面,增强用户体验。在登录时,用户的操作就会被打断,为了进一步增强用户体验,我们可能需要在登录完成后自动继续进行用户在登录前想进行的操作。
假设有这样一个场景,用户需要发表一个留言,但是发表留言是需要登录的,而发表留言的输入框是一直显示的,这也就要求在用户点击了发表按钮时对用户登录状态进行验证,传统的做法是将在用户登录状态检查封装在一个函数之中,这个函数接收一个回调参数,如果登录验证通过,则执行回调函数。这样的逻辑可以用以下代码表示:
function doAction() {
checkLogin(function() {
// 处理业务逻辑
});
}
function checkLogin(callback) {
if (isLogin) {
callback();
} else {
showLogin(callback);
}
}
function showLogin(callback) {
document.getElementById(“login-btn”).onclick = function() {
isLogin = true;
callback();
};
}
总觉得这样的方式会将业务逻辑放到一个匿名函数中,而不是放在了按钮的事件响应函数中,感觉上不是那么好。这对整个事件响应函数的改动比较大,主要的业务逻辑都放在了登录验证函数的回调中。而希望可以是下面这样:
function doAction() {
// 直接在函数开始进行登录验证,将业务逻辑独立出来
if (!doLogin()) {
return;
}
// 处理业务逻辑
}
function doLogin() {
if (isLogin) {
return true;
} else {
var callback = function() {}; // 自动组装 callback
showLogin(callback);
return false;
}
}
function showLogin(callback) {
document.getElementById(“login-btn”).onclick = function() {
isLogin = true;
callback();
};
}
这里的关键在于怎么“自动组装 callback”,很幸运的是,JavaScript 的 Function 提供了一个属性 caller 可以用来获取调用当前函数的函数是什么。并且还可以用 caller 的 arguments 属性来获取 caller 执行时的参数是什么,这也就使我们自动组装 callback 并恢复 caller 的原参数成为可能。
<div id=”result”></div><input type=”button” value=”Sumit” onclick=”sendPost()” id=”post” /><input type=”button” value=”Clear login” onclick=”isLogin=false” id=”reset” /><div id=’login’ style=”border:1px solid #000;padding:10px;margin:10px;display:none”><strong>Login</strong><br /><input type=”button” id=’loginbtn’ value=”Login” /></div><script type=”text/javascript”>// 是否登录var isLogin = false;// 检查登录状态var checkLogin = function(cfg) {if (isLogin) {return true;}cfg = cfg || {};var callback = null;// 自动执行回调if (cfg.autoCallback && arguments.callee.caller) {try {var caller = arguments.callee.caller,args = caller.arguments || [],scope = cfg.callbackScope || {},newArgs = [];for (var i = 0; i < args.length; ++i) {newArgs.push(args[i]);}callback = caller ? function() {try {caller && caller.apply(scope, newArgs);} catch (e) {//alert(e.message);}} : null;} catch (e) {callback = null;}}showLogin(callback);return false;};function sendPost() {if (!checkLogin({autoCallback:true})) {return;}document.getElementById(‘result’).innerHTML += ‘Hello<br />’;}function showLogin(callback) {document.getElementById(‘login’).style.display = ‘;document.getElementById(‘loginbtn’).onclick = function() {document.getElementById(‘login’).style.display = ‘none’;isLogin = true;callback && callback();}}</script>
其实弄出这么个方法来实现登录自动回调也是为了方便写代码,不用每次去将一堆业务逻辑放到 callback 中,直接在函数开始回一个 if (!checkLogin({autoCallback:true})) 就行。
caller 属性在 MDC 中被标记为“非标准”的,但是在主流浏览器中,都是支持这个属性的,也就是说,在浏览器兼容性上使用 caller 是没有问题的。
不过,使用奇技淫巧容易伤身体,慎用:)
YUI学习笔记(4)
Jan 10th
YUI学习笔记(4)
by xujiwei (http://www.xujiwei.com/)
YAHOO.util.Subscriber 与 YAHOO.util.CustomEvent。
1. YAHOO.util.Subscriber (event.js)
这 应该算是设计模式中的观察者模式了,Subscriber 订阅一个事件,在 Publisher 触发那个事件后,会逐个通知 Subscriber。
对 于一般开发者来说,并不需要去关心 Subscriber 的实现,因为 Subscriber 主要是 CustomEvent 用来分发动作以及删 除 Subscriber 的。
Subscriber 类只定义了 3 个属性:fn、obj 以及 override,3 个方 法:getScope、contains、toString,其中 fn 为订阅者的回调函数,obj 为要传递给回调函数的一个额外参 数,override 如果是布尔型的 true 值,那么表示使用 obj 属性为回调函数执行时的上下文,或者直接使用一个对象来作为回调函数执行的 上下文。
Subscriber 的 3 个方法中比较有用的是 getScope 和 contains,toString 只是简单的 将 Subscriber 对象转换成一个字符串。getScope 会根据 Subscriber 对象的 override 属性来获取回调函数执行 的上下文,contains 用来判断 Subscriber 对象与指定的回调函数和 obj 是否一致。
2. YAHOO.util.CustomEvent (event.js)
CustomEvent 的 作用相当在观察者模式中发布者的身份,可以通过它来实现一个自己的事件发布者。
CustomEvent 构造函数的定义如下:
CustomEvent = function(type, oScope, silent, signature)
在 创建 CustomEvent 对象时,几个参数的用途如下:
type 是自定义事件的名称,在使用回调函数的参数格式 为 YAHOO.util.Event.LIST 时,回调函数的第一个参数就是 CustomEvent 对象的名称;
oScope 是 执行回调函数时的上下文对象,也就是在回调函数中可以用 this 来引用这个对象;
silent 参数是用指示是否 在 YUI 为 debug 版本时禁用调试信息;
signature 用来指示回调函数参数的格式,可以为 YAHOO.util.Event.FLAT 或 YAHOO.util.Event.LIST, 默认是 YAHOO.util.Event.LIST。
在使用 CustomEvent 之前,先要了解一下 CustomEvent 中 回调函数参数的格式,CustomEvent 的回调函数可以有两种格式,一种为 YAHOO.util.Event.LIST,这种格式的回调函数具有 三个参数,分别是事件名称、参数数组和附加对象参数;另外一种回调函数参数格式为 YAHOO.util.Event.FLAT,这个时候回调函数只有两 个参数,一个为 CustomEvent 对象调用 fire 方法时的第一个参数,另外一个是订阅时的额外对象参数。
在创 建 CustomEvent 对象时,CustomEvent 构造函数还会首先创建一个内部的自定义事件,用来处理该自定义事件被订阅的事件,这 在 EventProvider 中用到,这里暂且不提。
CustomEvent 对象使用一个名为 subscribers 的数组来保 存所有订阅者的列表,并且通过维护这个列表来维护该自定义事件的订阅者。
CustomEvent 对象提供了 subscribe、 unsubscribe、unsubscribeAll、fire 这几个方法来处理自定义事件的订阅、退订以及触发等动作,而这几个就是观察者模式中的 主要动作了。
subscribe 的签名为 subscribe: function(fn, obj, override), 三个参数分别对应了 Subscriber 类构造函数的三个参数,分别对应了回调函数、额外对象参数以及是否使用额外对象参数作为执行上下文。 subscribe 只是简单的判断参数 fn 是否有定义,然后会触发自定义事件订阅事件,最后使用这三个参数创建一个 Subscriber 对象添 加到 CustomEvent 对象的 subscribers 属性中。
unsubscribe 方法用来取消事件的订阅,它的函数签名 为 unsubscribe: function(fn, obj),两个参数分别是回调函数和额外对象参数,如果使用无参数调 用 unsubscribe 方法,那么会直接调用 unsubscribeAll 来删除所有订阅者,否则会逐一判断 subscribers 中的每 个对象,通过使用 Subscriber 对象的 contains 方法来判断给定的 fn 和 obj 与其是否一致,如果一致,就使用一个私有方 法 _delete 来删除这个 Subscriber。
unsubscribeAll 方法没有参数,它只是简单的直接删除自定义事件的 所有订阅者,最后直接给 subscribers 赋值一个空数组来避免有可能出现漏删订阅者的情况。
内部方法 _delete 的参数 是 Subscriber 对象在 subscribers 数组中的索引,它会先删除 Subscriber 对象的 fn 和 obj 属性,最后使 用 splice 方法将 Subscriber 对象从数组中删除。
var s = this.subscribers[index];
if (s) {
delete s.fn;
delete s.obj;
}
this.subscribers.splice(index, 1);
使用 delete 删除 Subscriber 对象的 fn 和 obj 属性是为了去除 回调函数及额外对象参数的引用,以免引起不必要的内 存泄露。
CustomEvent 对象最重要的方法就是 fire 了,就是通过这个方法来通知所有了订阅者这个自定义事件被触发了。 fire 方法先使用 Array 的 slice 方法将调用 fire 方法时的参数转化成数组,这样就可以在调用 Subscriber 的回调函 数时可以传递参数给它们。
在遍历 subscribers 中的 Subscriber 前,fire 方法先使用 了 subscribers 的 slice 方法来创建一个 subscribers 的副本,这样避免在执行 fire 的过程中 有 Subscirber 取消订阅了这个自定义事件会导致错误。
在执行 Subscriber 的回调函数前,先使 用 Subscriber 对象的 getScope 方法来获取执行回调函数时的上下文对象,再根据 CustomEvent 对象 的 signature 属性来决定怎么去调用 Subscriber 的回调函数。
如 果 signature 为 YAHOO.util.CustomEvent.FLAT,那么就把调用 fire 方法时的第一个参数做为回调函数的第一 个参数,再把 Subscriber 对象的 obj 属性做为第二个参数:
s.fn.call(scope, param, s.obj)
如 果 signature 为 YAHOO.util.CustomEvent.LIST,那么就除了把整个 fire 方法的参数列表传递给回调函数外, 还要传递当前 CustomEvent 的名称给回调函数:
s.fn.call(scope, this.type, args, s.obj)
Subscriber 的 回调函数如果在执行过程中出现了错误,那么 CustomEvent 的 lastError 属性就是指向错误对象的引用,另外,如 果 YAOO.util.Event.throwErrors 为 true,那么会把这个错误再次抛出。
另外,Subscriber 对 象也可以控制事件通知是否继续,如果 Subscriber 对象的回调函数执行后的返回一个 false,那么在 fire 方法中就会停止通知剩下 的 Subscriber 对象,通常情况下,先订阅自定义事件的 Subscriber 可以阻止后订阅的 Subscriber 接收到通知。
使 用 YUI 的自定义事件(CustomEvent)可以很方便地实现观察者模式,更好地组织 JavaScript 程序的结构。
YUI学习笔记(3)
Jan 9th
YUI学习笔记(3)
by xujiwei (http://www.xujiwei.com/)
YAHOO.lang.later,YAHOO.lang.trim,YAHOO.lang.isXXX 以及 YAHOO.lang.hasOwnProperty。
1. YAHOO.lang.later(yahoo/yahoo.js)
later 方法用来延迟执行方法,是对 setInterval 和 setTimeout 的封装,并且可以传递参数到延迟执行的函数或者使用参数数组批量执行指定的函数。
later 方法的签名为:
later: function(when, o, fn, data, periodic)
when 是用来指定在多长时间后执行指定的函数,以毫秒计算;
o 是上下文对象,即在要执行的函数里使用 this 是会引用这个 o 对象;
fn 就是要延迟执行的函数了,可以传递一个函数引用,也可以传递一个字符串,later 方法会在 o 对象中查找对应名称的属性来做为要执行的方法;
data 就是传递给延迟执行的函数的参数了,可以为一个参数,或者是一个参数数组,那么如果我们要传递的参数本身就是一个数组的话,就要自己先把这个参数数组包装成一个数组;
peridoic 参数是一个布尔值,用来表示延迟执行的函数是否需要周期执行而不是只执行一次。
later 方法执行后会返回一个对象,包含了一个名 interval 的属性用来表示函数是以 setInterval 来执行的还是以 setTimeout 来执行的,以及一个方法 cancel 用来取消执行被延迟执行的函数。嗯,不过只有在 peridoic 为 true 时这个 cancel 方法比较有用,毕竟如果 peridoic 为 false 时函数执行一次就不会再执行了,cancel 也没有什么意义。
{
interval: periodic,
cancel: function() {
if (this.interval) {
clearInterval(r);
} else {
clearTimeout(r);
}
}
};
2. YAHOO.lang.trim (yahoo/yahoo.js)
trim 方法用来去除字符串两边的空白字符,其实也就用了一个正则来匹配字符串两端的空白字符并替换成空白字符串。
不过在 YAHOO.lang.trim 中,它使用了一个 try … catch 来在调用的参数不为字符串时直接返回原来的对象。
3. 对象类型判断 YAHOO.lang.isXXX (yahoo/yahoo.js)
YAHOO.lang 中包含了一堆用于判断对象是否为某个类型的方法,例如 isObject、isString、isNumber 等。
YAHOO.lang.isArray 用来判断一个对象是否为数组,YUI 中并不是使用的 obj instanceof Array 或者 obj.constructor == Array,而是判断指定的对象是否有两个数组应该具有的经典属性和方法 length 和 splice,这是因为如果要判断的对象是属于另外一个 frame 中时,除非你能获得另外一个 frame 中 Array 定义的费用,否则 instanceof 和 constructor == Array 都是返回 false 的,我们可以使用以下代码来测试一下:
test.html
<iframe id=”frame” src=”iframe.html”></iframe>
<script type=”text/javascript”>
<!–
var f = document.getElementById(‘frame’);
setTimeout(function() {
alert(f.contentWindow.arr.constructor == Array);
}, 1000);
//–>
</script>
iframe.html
<script type=”text/javascript”>
<!–
var arr = [1, 2, 3];
//–>
</script>
而如果把 Array 换成 arr 所在 frame 中 Array 的引用,结果就是为 true 了:
test.html
<iframe id=”frame” src=”iframe.html”></iframe>
<script type=”text/javascript”>
<!–
var f = document.getElementById(‘frame’);
setTimeout(function() {
// 这里将 Array 换成 f.contentWindow.Array
alert(f.contentWindow.arr.constructor == f.contentWindow.Array);
}, 1000);
//–>
</script>
但是因为 isArray 是一个通用的方法,在使用时不可能要求调用者同时也传递一个目标 frame 的引用过来,所以只好通过判断两个经典的属性来代替了。
YUI 的注释中提到是 Safari 不支持跨框架通过 instanceof 的检测,但是我测试的结果是几个主流浏览器都不支持跨框架的 instanceof 检测。
YAHOO.lang.isBoolean、YAHOO.lang.isFunction、YAHOO.lang.isString、YAHOO.lang.isUndefined 这 4 个方法是直接通过使用 typeof 来判断对象类型是否为 boolean、function、string 或 undefined 的。
YAHOO.lang.isNumber 也是通过 typeof 来判断的,不过附加了一个检测条件,使用 isFinite 来检测数字是否为有限的,即不是 NaN、负无穷或正无穷。
YAHOO.lang.isObject 通过 typeof 来判断对象类型是否 object 以及使用 isFunction 方法来检测对象是否为一个 Function,在 YAHOO.lang.isObject 方法的眼中,Object 与 Function 都是 Object。
YAHOO.lang.isNull 直接将判断对象与 null 进行严格相等比较。
YAHOO.lang.isValue 用来判断传递给它的参数是否为一个有值的对象,它是将 isObject、isString、isNumber 以及 isBoolean 四个方法对参数进行判断的结果进行或操作来作为结果的,对 null/undefined/NaN 这三个值进行 isValue 判断时就会返回 false,因为 0、false、’ 这三个值在分别在 isNumber、isBoolean、isString 时判断为 true,所有 isValue 的结果也为 true。
4. YAHOO.lang.hasOwnProperty (yahoo/yahoo.js)
嗯,这个方法也是对 Object.prototype.hasOwnProperty 的封装,前提是当前上下文的 Object.prototype 有 hasOwnProperty 方法,如果 Object.prototype.hasOwnProperty 不存在,那么 YAHOO.lang.hasOwnProperty 就实现了自己的 hasOwnProperty 方法,它是通过判断对象的属性与对象 prototype 的同名属性是否为同一个对象的判断的。如果对象的属性不是从原型继承过来的,那么两个属性值就有可能不一致:
o.constructor.prototype[prop] !== o[prop]
不过这里也有个问题,在 YAHOO.lang.hasOwnProperty 的注释中也提到了,就是 YUI 自己实现的 hasOwnProperty 是无法分辨在属性值一样时,这个属性是从原型继承过来的还是对象自身的:
var YAHOO = {
lang : {
isUndefined : function(o) {
return typeof o === ”undefined”;
},
hasOwnProperty : function(o, prop) {
return !this.isUndefined(o[prop]) &&
o.constructor.prototype[prop] !== o[prop];
}
}
}
var A = function() {};
A.prototype.foo = ’foo’;
var a = new A();
a.foo = ’foo’;
alert(a.hasOwnProperty(‘foo’)); // true
alert(YAHOO.lang.hasOwnProperty(a, ’foo’)); // 这里返回 false,事实上应该是 true
但是现在 Object.prototype 中没有 hasOwnProperty 的浏览器应该基本没有了,所以这个问题也不是很大。
5. YAHOO.lang.augmentProto 以及 YAHOO.lang.extend 的别名 (yahoo/yahoo.js)
在 yahoo.js 中,将 YAHOO.lang.augment 以及 YAHOO.augment 指向了 YAHOO.lang.augmentProto,YAHOO.extend 指向了 YAHOO.lang.extend,可以一定程度上减少代码的编写。
YUI学习笔记(2)
Jan 8th
YUI学习笔记(2)
by xujiwei (http://www.xujiwei.com/)
YAHOO.lang.dump 与 YAHOO.lang.substitute。
1. YAHOO.lang.dump(yahoo.js)
dump 方法用来将一个对象转储为一个字符串,并且可以指定转储的深度。
在 dump 过程中,对于基础类型例如 Number、String、Boolean,是直接返回字符串的,对 HTMLElement 对象是返回 HTMLElement 本身,也就是不做处理,对于函数 Function 则是返回字符串“f(){…}”。
对于数组,dump 返回的格式就如我们定义时一样“[item1, item2 item3, ...]”,对于对象 Object,则是使用键值对的形式“key => value”,与 PHP 里面的数组定义方式相似。
例如一个对象定义如下:
var obj = {
num: 1,
str: ”string”,
bool: true,
date: new Date(),
obj: {
obj_num: 1,
obj_str: ”obj_string”
},
foo: function() {
}
}
dump 之后的字符串如下:
{num => 1, str => string, bool => true, date => Wed Jan 7 15:57:52 UTC+0800 2009,obj => {obj_num => 1, obj_str => obj_string}, foo => f(){…}}
字符串没有被引号引起来,这个方法只适合用来展示对象的结构,与 JSON 序列化差得有点远了。
2. YAHOO.lang.substitute(yahoo.js)
substitute 实现的功能与 C# 中的 String.Format 方法类似,用来格式化一个字符串,但是它是一个字符串来代表一个占位符,而不像 C# 中是使用数字,或许这个用法和 python 中的字符串格式化更像。
substitute 调用的格式为:substitute(formatString, valueObject [,formatCallback]),其中 formatString 是作为格式化字符串的字符串,其中包含着一些格式为 {key} 这样的占位符,valueObject 是包含要取代占位符的值的对象,它的结构为 { key1 : value1, key2 : value2 },最后的 formatCallback 参数则是额外的格式化处理函数,用来在格式化字符串进行一些额外的处理。
substitute 格式化字符串的占位符格式为“{key [meta]}”,其中 meta 是可选的,用来表示额外的格式化属性,而要替换的数据则是 valueObject 中名称为占位符 key 的属性,例如,使用 { name : ”xujiwei” } 做为数据,那么“{name}”将被替换为“xujiwei”。
基本数据类型例如 String、Number 在 substitute 中是直接替换的,但是如果要替换的值为一个数组,那么 subtitute 会先使用 YAHOO.lang.dump 方法将对象转储为一个字符串再进行替换,深度为 10,而在替换的值为一个对象时,则会先检测 meta 数据中是否包含了 dump 关键字,或者这个对象的 toString 方法是否与 Object 对象原型的 toString 方法一样,如果是的话就使用 YAHOO.lang.dump 方法来将对象转储为字符串进行替换,否则就调用对象的 toString 方法得到字符串进行格式化。
如果替换的数据类型不是 Object、Array、String、Number 中的一种的话,那么这个占位符就不进行替换。
一个覆盖了所有类型数据的例子如下:
YAHOO.lang.substitute(
“String : {key1}\nNumber : {key2}\nObject : {key3}\nArray : {key4}”,
{
key1 : ”xujiwei”, // String
key2 : 123456, // Number
key3 : {
firstName : ”Jiwei”,
lastName : ”Xu”
}, // Object
key4 : [1, 2, 3] // Array
});
它的输出如下:
String : xujiwei
Number : 123456
Object : {firstName => Jiwei, lastName => Xu}
Array : [1, 2, 3]
YUI学习笔记(1)
Jan 7th
YUI学习笔记(1)
by xujiwei (http://www.xujiwei.com/)
今天开始学习 YUI,加强一下对 JavaScript 的理解。
1. 命名空间 YAHOO.namespace(yahoo.js)
YUI 中使用了命名空间的概念,在 JS 中使用命名空间是为了模块以及代码组织清晰的需要,通过使用命名空间可以将功能相似或同一模块中的函数、变量等放到同一个命名空间下。
其实 JS 中的命名空间就是一个嵌套的对象而已,即子命名空间相当于父命名空间中的一个属性,它本身也是一个对象,这样子命名空间也可以有自己的子命名空间。
在 YUI 中,命名空间的格式与 C# 中类似,是以点号分隔的字符串,可以使用 YAHOO 对象的静态方法 namespace 来创建命名空间,需要注意的是,以 namespace 方法创建命名空间时,所有的对象都是附加在 YAHOO 这个对象上的,如果调用 namespace 方法创建一个“com.xujiwei.ajax”这样的命名空间,其中 ajax 的完整路径将是 YAHOO.com.xujiwei.ajax,而不是 com.xujiwei.ajax,也就是说 namespace 不会产生新的顶层对象,一切以 YAHOO 对象为基础。
namespace 方法接受一个或多个字符串来生成命名空间,但是它只返回最后一个参数所代表的命名空间的最后一个域的对象,例如使用 namespace(“com.xujiwei.js”, ”com.xujiwei.ajax”) 时它的返回值是代表 js 这个模块的的对象:
YAHOO.namespace(“com.xujiwei.js”, ”com.xujiwei.ajax”).get = function() {
alert(“get method of com.xujiwei.ajax”);
}
YAHOO.com.xujiwei.ajax.get();
2. YAHOO.lang.extend(yahoo.js)
YAHOO.lang.extend 负责扩展一个现在的函数对象,相当于面向对象中的继承,并且可以附加一个参数重写继承的方法,它的函数声明是:
extend: function(subc, superc, overrides)
其中 subc 为要扩展的函数对象,superc 是要继续的函数对象,overrides 中包含着要重写父类的方法。
YAHOO.lang.extend 是通过原型链的方式来扩展对象,即创建一个父类的实例做为子类的原型对象,在 extend 方法中,首先构造了一个空函数,将空函数的 prototype 指向父类的 prototype,以免在父类的构造函数需要参数时创建父类实例时会出错,然后创建这个空函数的实例赋值给子类的 prototype 对象,并且给子类添加一个 superclass 属性指定它的父类,另外还指定了子类 prototype 的 constructor 属性为子类构造函数,以免创建子类实例时会丢失 constructor 属性,这个问题我在《慎用 somefunction.prototype》有提到。
var F = function() {};
F.prototype = superc.prototype;
subc.prototype = new F();
subc.prototype.constructor = subc;
subc.superclass = superc.prototype;
接着处理的是要重写的函数,首先遍历 overrides 参数,并且使用 hasOwnProperty 方法判断属性是否为 overrides 自身的属性,而不是从 Object 对象的 prototype 继承过来的,如果是 overrides 自身的属性,那么就覆盖子类 prototype 的相同成员。
在处理 overrides 的最后,还修复了 IE 中不枚举与内部函数同名的函数的 bug,即例如 overrides 中包含了 toString 或 valueOf 的定义,但是这两个函数也是 Object 对象所具有的,在 IE 中使用 for(p in overrides) 就不会得到这两个函数,因此需要修复,这是 YAHOO.lang 中 _IEEnumFix 函数所做的工作。
if (overrides) {
for (var i in overrides) {
if (L.hasOwnProperty(overrides, i)) {
subc.prototype[ i ]=overrides[ i ];
}
}
L._IEEnumFix(subc.prototype, overrides);
}
_IEEnumFix 只是简单的判断当前浏览器是否为 IE,如果为 IE 则从一个已经定义的列表中获取在 IE 中枚举不到的函数名列表,逐一判断是否为源对象自身的属性并且与 Object 原型中的同名函数不一样,如果是则添加到目标对象。如果浏览器不是 IE 则 _IEEnumFix 直接就是一个空函数,直接不处理。
_IEEnumFix: (YAHOO.env.ua.ie) ? function(r, s) {
for (var i=0;i<ADD.length;i=i+1) {
var fname=ADD[ i ],f=s[fname];
if (L.isFunction(f) && f!=Object.prototype[fname]) {
r[fname]=f;
}
}
} : function(){},
3. YAHOO.lang.augmentObject(yahoo.js)
augmentObject 方法用来引入目标对象的属性,并且处理重写,通常用于合并配置对象与默认配置对象。
augmentObject 有三种调用方法,分别是:
augmentObject(result, source),一般情况下用些方法,从 source 引入 result 中没有的属性;
augmentObject(result, source, override),override 设置是否覆盖,如果是 true 的话,那么 source 中所有属性会覆盖 result 中已有的属性;
augmentObject(result, source, ovrride_name, …),指定要覆盖属性的列表,source 参数后为要覆盖属性名称的列表,例如覆盖 result 中的 toString 和 valueOf,那么可以调用 augmentObject(result, source, ”toString”, ”valueOf”)来实现。
合并对象的最后同样调用了 _IEEnumFix 方法来修正 IE 中不枚举与内部属性同名属性的 bug。
augmentProto 与 augmentObject 功能基本相同,只不过后者是处理对象,而前者是处理函数的 prototype,它是通过使用两个参数的 prototype 来构造一个参数列表,传递给 augmentObject 对象来实现 augmentProto 的功能。
var a=[r.prototype,s.prototype];
for (var i=2;i<arguments.length;i=i+1) {
a.push(arguments[ i ]);
}
L.augmentObject.apply(this, a);
因为 augmentProto 是调用 augmentObject 来实现的,因此它的调用格式也是 augmentObject 一样,具体可以看看上面 augmentObject 的三种调用方法。
另外,YAHOO.lang.merge 方法也是通过调用 augmentObject 方法来实现对象的合并。
[JavaScript] 慎用 somefunction.prototype
Oct 15th
在写 JavaScript 脚本的时候,为了创建一个类,如果不使用框架,一般情况我们都会使用 prototype 来给要创建的类增加公有方法,例如:
- // code from xujiwei.cn
- function Person(name) {
- this.Name = name;
- }
- Person.prototype.SayHello = function() {
- alert(‘Hello, ’ + this.Name);
- }
- Person.prototype.SayBye = function() {
- alert(‘Goodbye, ’ + this.Name);
- }
不过,有的时候,为了书写以及维护的方便,我们会把公有方法的声明写到一个对象里,然后赋值给 Person.prototype,例如:
- // code from xujiwei.cn
- function Person(name) {
- this.Name = name;
- }
- Person.prototype = {
- SayHello: function() {
- alert(‘Hello, ’ + this.Name);
- },
- SayBye: function() {
- alert(‘Goodbye, ’ + this.Name);
- }
- }
使用这种方式,在这个类具有大量公有方法的时候,就不需要维护许多的 Person 标识符,如果某一天这个类的名字需要改变,那么要改的地方只有两个,一个是 function 的声明,一个是 prototype 前面的标识符,如果是使用前一种方式的话,那么有多少个公有方法,就需要维护 N+1 个标识符了,虽然可以使用查找替换,但是从稳定上来说,查找替换可能会引起一些错误,这增加了维护的成本。
这种方式虽然给我们的维护增加了便利,但也引发了另外一个隐藏的问题,就是类的 constructor 属性丢失的问题。
- // code from xujiwei.cn
- function Person1(name) {
- this.Name = name;
- }
- Person1.prototype.SayHello = function() {
- alert(‘Hello, ’ + this.Name);
- }
- Person1.prototype.SayBye = function() {
- alert(‘Goodbye, ’ + this.Name);
- }
- // code from xujiwei.cn
- function Person2(name) {
- this.Name = name;
- }
- Person2.prototype = {
- SayHello: function() {
- alert(‘Hello, ’ + this.Name);
- },
- SayBye: function() {
- alert(‘Goodbye, ’ + this.Name);
- }
- }
- alert(new Person1(‘Bill’).constructor);
- alert(new Person2(‘Steve’).constructor);
运行上面的测试代码我们可以发现,Person1 的 constructor 属性为 Person1 类的构造函数,但是 Person2 的 constructor 属性却是 Object,那么在需要使用 constructor 属性来判断对象类型的时候,就会出现问题。
因此,在写 JavaScript 类的时候,如果不需要使用 constructor 属性来获取对象的类型,那么个人比较倾向于使用第二种写法,但是如果需要使用 constructor 属性以实现自己的反射机制或 GetType 函数等等,那么就要使用第一种写法。
当然,如果在实现自己反射机制或 GetType 函数时并不依赖 constructor 属性,那么两种写法都是可以的了,例如额外维护一个成员变量,用于标识自身的类型等。也可以使用一些现成的JS框架,有一些框架已经实现了JS中类的实现等,例如 js.class,这就看个人需要进行取舍了。
JavaScript 中为 Date 类实现 DateAdd 方法
Mar 7th
JavaScript 中的并没有提供像 VBScript 里的 DateAdd 方法用于日期的操作,像加一年,减一个月什么的,这在服务端经常用到,比如设置 Cookie 的到期时间为现在时间的后一年,那么就需要使用这个方法了。
虽然 JavaScript 中没有 DateAdd 方法,但是 Date 类却有设置年月日时分秒的方法,比如 setFullYear、setMonth 之类的,而且,这些个方法的参数是可以为负的,在设置之后, Date 类会自行进行调整,每个月是30天还是31天,年份是不是闰年都不用我们来管了,只管设置值就是。
有了这个特性之后,我们就可以很方便的来为 Date 类添加 add 方法了。之所以不添加一个 DateAdd 方法而是给 Date 类添加一个 add 方法是因为我觉得那样更加方便,当然你也可以将 Date 类的 add 方法转换成为一个全局函数 DateAdd。
为了对每一个 Date 类的实例都有效,这里用到了 prototype 对象。
在 VBScript 里的 DateAdd 方法是用一个字符串来控制所加的量是年还是月还是其他的,所以在这里我也模仿 VBScript 里的 DateAdd 方法,使用一个字符串来控制所加量对应的部分,比如 y 代表年,m 代表月。
最后得到的代码如下:
- Date.prototype.add = function(part, value) {
- value *= 1;
- if(isNaN(value)) {
- value = 0;
- }
- switch(part) {
- case ”y”:
- this.setUTCFullYear(this.getUTCFullYear() + value);
- break;
- case ”m”:
- this.setUTCMonth(this.getUTCMonth() + value);
- break;
- case ”d”:
- this.setUTCDate(this.getUTCDate() + value);
- break;
- case ”h”:
- this.setUTCHours(this.getUTCHours() + value);
- break;
- case ”n”:
- this.setUTCMinutes(this.getUTCMinutes() + value);
- break;
- case ”s”:
- this.setUTCSeconds(this.getUTCSeconds() + value);
- break;
- default:
- }
- }
代码里的 getUTCFullYear 等等也可以换成通常用的 getFullYear,因为是相对调整,所以用哪一个是没有区别的。
希望此文对你有所帮助。
xujiwei
Firefox与IE兼容性:getAttribute的返回值类型
May 15th
在改AJAXRequest的过程中,碰到了个问题,应该算是Firefox和IE之间的兼容性问题。
提交表单时,往往需要先对表单进行验证,而这个验证的过程一般是放在form标签的onsubmit属性中。
onsubmit一般是由浏览器在form的submit动作发生时自动触发,但是如果表单由我们自己来提交,比如在AJAX应用中,就是由我们自己写程序将表单转换成请求字符串,再通过XMLHttpRequest发送到服务器,那么如果在此同时不丢掉表单验证的话,就需要我们自己来获取onsubmit属性,并去处理它。
在获取属性时,为了保证兼容性,我用getAttribute来获取标签的属性值,但是发获取了onsubmit属性之后,发现在Firefox和IE中使用getAttribute(“onsubmit”)所返回的返回值类型是不同的。
测试代码如下:
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]在Firefox中使用getAttribute(“onsubmit”)返回值的是一个字符串,而在IE中的返回值类型则是function,也就是一个函数,因此如果在IE中处理onsubmit,我们可以直接调用这个函数:
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]但是,在Firefox中,使用getAttribute(“onsubmit”)返回的是一个字符串,因此就不能直接这样使用了,而应该将字符串转换成函数再去调用:
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]如果把上面这段代码在IE中运行,那么会发现无论是否在输入框中输入值,都会显示“Error”。
因此,如果要解决这个问题,可以在使用getAttribute获取onsubmit属性值之后,判断返回值类型是否为字符串,如果是字符串就使用new Function将它转换成函数:
[Ctrl+A 全部选择 提示:你可先修改部分代码,再按运行]这样,就解决了使用getAttribute(“onsubmit”)返回值类型不一样的问题,对于其他回调函数如onclick也可以这样处理。当然,如果大家有什么更好的解决方案也可以提出来分享一下:)
[JavaScript] setTimeout和setInterval的浏览器兼容性
Feb 15th
无意中测试AJAXRequest浏览器兼容性的时候,发现AJAXRequest.update方法在某些情况下在IE里有问题,经过测试找到是setTimeout和setInterval的问题。
问题出现在当调用AJAXRequest.update方法时,如果带了更新间隔及更新次数,那么在IE下面就会出现问题,具体表现为带了更新间隔时是函数工作,带上更新次数时函数无法在更新指定次数后停止执行。
测试几个例子之后找到了问题所在,在IE里,setTimeout和setInterval是不支持参数传递的。
演示地址:http://www.xujiwei.cn/demo/usetimer/
在Netscape的JavaScript参考中找到setTimeout的语法如下:
setTimeout
Evaluates an expression or calls a function once after a specified number of milliseconds elapses.
语法
setTimeout(expression, msec)
setTimeout(function, msec, arg1, …, argN)
参数
expression A string containing a JavaScript expression.
msec A numeric value or numeric string, in millisecond units.
function Any function.
arg1, …, argN (Optional) The arguments, if any, passed to function.
第二种使用方法就是定义了一个定时器,在执行时function时,将把调用setTimeout时定义的参数传递给function,但在IE中,并不支持这种方式的调用,也就是在执行function的时候,函数并没有接收到这些参数。如下面的例子:
- <script type=”text/javascript”>
- function show(str) {
- alert(“my site: ”+str);
- }
- setTimeout(show,100,”www.xujiwei.cn”);
- </script>
在Firefox和Opera里,浏览器都能正确的弹出提示框显示字符串“my site: www.xujiwei.cn”,而在IE里,显示的则是“my site: undefined”,说明函数show并没有接收到参数str,所以显示出来就是一个未定义变量。
当然,如果在函数内部使用的变量是全局变量时,就不必要考虑这些问题,如:
- <script type=”text/javascript”>
- function show() {
- // url是全局变量,函数正确执行
- alert(“my site: ”+url);
- }
- var url=”www.xujiwei.cn”;
- setTimeout(show,100);
- </script>
这段代码在IE和Firefox里都能正常工作,显示出“my site: www.xujiwei.cn”。
在变量是全局变量的情况下,可以使用语句段的方式来调用setTimeout,即使用第一种语法:
- <script type=”text/javascript”>
- function show(str) {
- // url是全局变量,函数正确执行
- alert(“my site: ”+str);
- }
- var url=”www.xujiwei.cn”;
- setTimeout(“show(url);”,100);
- </script>
因为变量url是全局变量,因此定时器执行所定义的语句段“show(url);”能正确传递参数,但是如果url不是全局变量,而是一个局部变量时,执行结果就会出错了:
- <script type=”text/javascript”>
- function show(str) {
- // url是全局变量,函数正确执行
- alert(“my site: ”+str);
- }
- function test() {
- var url=”www.xujiwei.cn”;
- setTimeout(“show(url);”,100);
- }
- test();
- </script>
此时就会出错了,在函数test执行时,会提示url未定义,在执行定义的语句段“show(url);”时,上下文已经脱离了函数test,而url是在函数test内部定义的,所以在执行函数test的时候,变量url已经释放了。
如果要在setTimeout里面使用局部变量,并且解决在IE里的setTimeout不支持参数传递的问题,可以使用匿名函数,即在调用setTimeout时定义一个匿名函数,在这个函数内部进行原来需要进行的操作。
- <script type=”text/javascript”>
- function show(str) {
- // url是全局变量,函数正确执行
- alert(“my site: ”+str);
- }
- function test() {
- var url=”www.xujiwei.cn”;
- setTimeout(function(){show(url);},100);
- }
- test();
- </script>
在上面的例子中,调用setTimeout时定义了一个匿名函数,它的函数体是“show(url);”,因为已经定义了函数,所以在定时器调用这个函数时,变量url还是有引用的,因些函数可以正确执行,显示出字符串“my site: www.xujiwei.cn”。
总的来说,使用setTimeout或者setInterval时需要注意以下几点:
1. 定义定时器时如果是使用的表达示,那么其中的变量应该是全局变量,或者是一个直接的值,而不能是局部变量。
2. 定义定时器时如果是定义的调用函数,那么应该只写函数名,而不能加括号,如果加了就是定义返回值了。
3. 在IE里使用定时器时不能传递参数。
4. 如果要在IE里使用定时器时传递参数,可以使用匿名函数,在函数体中调用原来该调用的函数。