Web
慎用 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 属性来传递参数 :)
在 AIR 中突破同源策略访问 iframe 中的内容
Oct 26th
本篇所讲关于 AIR 的内容是基本 HTML+JS,而不是 Flex 或其他。
在 AIR 中,如果当前页面有一个 iframe,并且我们需要获取这个 iframe 的内容,但是如果 iframe 里包含的是远程页面,在默认情况下,浏览器策略并不允许这种情况,这就是同源策略的限制。
在 Titanium 这个类似于 AIR 的平台中,默认情况下是可以直接从应用的页面中去读取 iframe 中的内容而没有任何限制,但是在 AIR 中不可以。在网上搜索了一番,再看了好几遍 AIR 的文档之后,终于找到解决问题的方法了。
在 AIR 中,iframe 标签拥有额外的几个属性:sandboxRoot、documentRoot、allowCrossDomainXHR 及 ondominitialize。这里为了解决读取 iframe 内容所用到的属性就是 sandboxRoot 及 documentRoot。
通过 sandboxRoot 及 documentRoot,可以将本地的页面映射为远程页面,并在沙盒中运行,另外,在沙盒中运行的页面就会拥有映射域名的域。例如,我们可以将本地的 proxy.html 映射为 http://example.com/local/proxy.html,这样,在实际运行时,proxy.html 中如果用 document.domain 获取页面所在的域,就会得到 example.com,这个时候如果在 proxy.html 中添加一个 iframe,这个 iframe 指向我们实际需要获取内容的地址,因为域相同,就可以直接获取 iframe 的内容了。
例如,我们要在 AIR 中获取 http://example.com/member/login.html 的内容,或者操作这个页面的元素,就可以先用一个 proxy.html,用来实现将本地文件运行在沙盒中,再用一个 login.html 来对实际网页进行操作。
<!– proxy.html –>
<iframe src=”http://example.com/local/login.html” id=”frame” width=”100%”
sandboxRoot=”http://example.com/local/”
documentRoot=”app:/” name=”frame” height=”100%”></iframe>
<!– login.html –>
<iframe id=’f' frameborder=”0″ scrolling=”no”
src=”http://example.com/member/login.html”
width=”100%” height=”100%”></iframe>
<script type=”text/javascript”>
alert(document.domain);
var f = document.getElementById(‘f’);
f.onload = function() {
alert(f.contentWindow.document.documentElement.innerHTML);
};
</script>
在 proxy.html 中,虽然 iframe 的 src 属性是 http://example.com/locale/login.html,但是由于这个 iframe 具有 sandboxRoot 属性,所以实际请求会被重定向到相对于 sandboxRoot,并且使用 documentRoot 进行定位的实际地址 app://login.html,但是这个时候 login.html 中具有 document.domain=”example.com” 这个属性,所有可以直接使用 iframe.contentWindow 来访问 iframe 中的内容。
虽然解决方法有些麻烦,但终归可以实现突破同源策略的限制来读取 iframe 的内容了。
当然,如果你有更好的方法,请不吝赐教:)
使用 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 是没有问题的。
不过,使用奇技淫巧容易伤身体,慎用:)
HTML5 Canvas 起步(3) – 颜色与渐变
Jun 7th
上一篇介绍了 HTML5 中 Canvas 的路径,这篇将要介绍一下 Canvas 中的颜色及渐变。
Canvas 中的基本颜色系统
在 Canvas 中,颜色主要用途就是在绘制路径时,用来指定填充颜色和边框颜色。
Canvas 中的颜色参数值有两种格式:
1. 如果透明度为 1.0,也就是不透明,颜色值的格式就与一般使用一样,为:#AABBCC,其中 AA、BB、CC 分别为 Red、Green、Blue 分量。
2. 如果透明度不为 1.0,也就是带透明,颜色值格式可以使用 rgba(r, g, b, a),其中 r、g、b、a 分别为 Red、Green、Blue 分量和透明度。透明度的值为 0 至 1.0 之间的一个数值。
3. 颜色值还可以为已知的颜色名称,例如 red、blue、green 等。
总的说来,Canvas 中颜色值的格式与 CSS 中一致,因此颜色值没有特别需要注意的地方。
<div id="_mcePaste"><canvas id="canvas" width="600" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); // fill a rectangle whit sepecified fill style function fillRect(color) { ctx.fillStyle = color; ctx.fillRect(0, 0, 150, 100); ctx.font = "10pt Arial"; ctx.fillStyle = "white"; ctx.fillText(color, 6, 20); ctx.translate(160, 0); } ctx.translate(0, 50); // fill rect with color name fillRect("green"); // fill rect with 0xAABBCC fillRect("#AABBCC"); // fill rect with rgba(50, 100, 150, 0.5) ctx.translate(0, -25); fillRect("rgba(50,100,150,0.5)"); // fill another rect with rgba(150, 100, 50, 0.5) ctx.translate(-110, 50); fillRect("rgba(150, 100, 50, 0.5)"); </script></div>
注意,以上代码需要使用 Firefox 3.5 来查看,在 Firefox 3.0.x 中,Canvas 的 Context 对象不支持 fillText 方法,而我安装的 Chrome 2.0.174.0 对 translate 方法的实现有误。
2. Canvas 中的渐变
WHATWG 的 Canvas 规范中规划了两种渐变模式,一种是线性渐变,另一种是径向渐变。如果需要在 Canvas 中使用渐变,首先要根据你所要创建的渐变模式来调用 Context 的相应方法来创建一个渐变对象,这个对象就是用来控制渐变的效果。
HTML5 Canvas 起步(2) – 路径
May 5th
上一篇介绍了 HTML5 中 Canvas 的基本概念,这篇将要介绍一下 Canvas 中的基本图形。
图形的基础 - 路径
在 Canvas 中,所有基本图形都是以路径为基础的,也就是说,我们在调用 2dContext 的 lineTo、rect 等方法时,其实就是往已经的 context 路径集合中再添加一些路径点,在最后使用 fill 或 stroke 方法进行绘制时,都是依据这些路径点来进行填充或画线。
在每次开始绘制路径前,都应该使用 context.beginPath() 方法来告诉 Context 对象开始绘制一个新的路径,否则接下来绘制的路径会与之前绘制的路径叠加,在填充或画边框时就会出现问题。在绘制完成路径后,可以直接使用 context.closePath() 方法来关闭路径,或者手动关闭路径。另外,如果在填充时路径没有关闭,那么 Context 会自动调用 closePath 方法将路径关闭。
基本路径方法
1. beginPath, closePath
这两个方法在前面已经介绍过,分别用来通知 Context 开始一个新的路径和关闭当前的路径。
在 Canvas 中使用路径时,应该要保持一个良好的习惯,每次开始绘制路径前都要调用一次 beginPath 方法,否则画出来的效果难看不说,还会严重影响性能。
在下面这张图中,左边的图形在每次绘制矩形前都调用了一次 beginPath 来清除之前的路径并重新开始绘制新的路径,而后面的图形则就只在绘制所有图形前调用了一次 beginPath 来清除路径,因此,虽然这里是使用的边框色是 #666,但是右边的图形颜色比左边的深一些,因为每次使用 stroke 绘制边框时,会把之前的路径再次绘制一遍,叠加起来颜色就比原来深一些。
<div id="_mcePaste"><canvas id="canvas" width="500" height="500"></canvas>
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#666";
function useBeginPath() {
for (var i = 0; i < 5; ++i) {
ctx.beginPath();
ctx.rect(10 + i*20, 10 + i*20, 210 - i*40, 210 - i*40);
ctx.stroke();
}
}
function notUseBeginPath() {
ctx.beginPath();
for (var i = 0; i < 5; ++i) {
ctx.rect(240 + i*20, 10 + i*20, 210 - i*40, 210 - i*40);
ctx.stroke();
}
}
useBeginPath();
notUseBeginPath();
</script></div>在 Context 中路径数较少时,如果不考虑显示效果,性能上还可以接受,但是如果 Context 中的路径数很多时,在开始绘制新路径前不使用 beginPath 的话,因为每次绘制都要将之前的路径重新绘制一遍,这时性能会以指数下降。
因此,除非有特殊需要,每次开始绘制路径前都要调用 beginPath 来开始新路径。 More >
HTML5 Canvas 起步(1) – 基本概念
Apr 20th
什么是Canvas
<canvas> 是一个新的 HTML 元素,这个元素在 HTML5 中被定义。这个元素通常可以被用来在 HTML 页面中通过 JavaScript 进行绘制图形、合成图像等等操作,也可以用来做一些动画。当然,目前 HTML5 规范还在草稿阶段,正式发布也许要等到2010年,不过现在已经有不少浏览器已经支持了部分 HTML5 规范。目前支持 canvas 元素的浏览器有 Firefox 3+、Safari 4、Chrome 2.0+ 等,因此,在运行本页中的例子时,请确保你使用的是上述浏览器之一。
尽管在 Mozilla 已经有不少关于 Canvas 的教程,我还是决定把自己的学习过程记录下来。如果觉得我写的不够明白,那么你可以在参考资料中找到 Mozilla 网站上 Canvas 教程的链接。
另外,可以在这里找到一些有趣的 Canvas 示例。
开始使用 Canvas
使用 Canvas 很简单,与使用其他 HTML 元素一样,只需要在页面中添加一个 <canvas> 标签即可:
<canvas id="screen" width="400" height="400"></canvas>当然,这样只是简单的创建了一个 Canvas 对象而已,并没有对它进行任何操作,这个时候的 canvas 元素看上去与 div 元素是没什么区别的,在页面上什么都看不出来:)
另外,canvas 元素的大小可以通过 width 与 height 属性来指定,这与 img 元素有点相似。
Canvas 的核心:Context
前面说到可以通过 JavaScript 来操作 Canvas 对象来进行绘制图形、合成图像等操作,这些操作并不是通过 Canvas 对象本身来进行的,而是通过 Canvas 对象的一个方法 getContext 获取 Canvas 操作上下文来进行。也就是说,在后面我们使用 Canvas 对象的过程中,都是与 Canvas 对象的 Context 打交道,而 Canvas 对象本身可以用来获取 Canvas 对象的大小等信息。
要获取 Canvas 对象的 Context 很简单,直接调用 canvas 元素的 getContext 方法即可,在调用的时候需要传递一个 Context 类型参数,目前可以用的并且是唯一可以用的类型值就是 2d:
<script type="text/javascript"><!--mce:0--></script>
Firefox 3.0.x 的尴尬
Firefox 3.0.x 虽然支持了 canvas 元素,但是并没有完全按照规范来实现,规范中的 fillText、measureText 两个方法在 Firefox 3.0.x 中被几个 Firefox 特有的方法代替,因此在 Firefox 3.0.x 中使用 Canvas 时需要先 fix 这个几个方法在不同浏览器中的差别。
“云端的编辑器”,10 步安装 Bespin Development Server(Python版)
Apr 8th
Mozilla Labs 在今年情人节那天发布了一个叫 Bespin 的编辑器,这是一个基于网络的可扩展文本编辑器,按照现在流行的说法,就是“云编辑”了。并且,这个编辑器是开源的。
Bespin 是基于 Canvas 的,因此目前它只支持少数浏览器,其中包括 Firefox 3+、Safari 4 以及 Chrome 2 开发版,因此,如果要使用 Bespin 的话,你必须使用这几款浏览器中的一种。
要体验 Bespin,可以直接在 bespin.mozilla.com 注册一个帐号并且登录,不过目前 bespin.mozilla.com 所用的代码并不是最新版本的,线上使用的代码有许多不完善之处。因此,除了使用 Mozilla Labs 官方的 Bespin 站点,我们也可以从 Mozilla Labs 下载 Bespin 源代码,并在本地搭建 Bespin 服务,从而可以体验 Mozilla Labs 最新的开发成果。
注意,这篇文章并不介绍怎么去使用 Bespin,而是介绍怎么样去配置一个可以在本地运行的 Bespin 服务器,因此,如果你需要了解怎么去使用这个编辑器,可以参阅 Mozilla Labs 上的文档,或者等我再写一篇使用 Bespin 的文章:)
准备工作
首先需要说明的是,这里介绍的配置 Bespin 本地服务器的环境是 Windows Vista(or WinXP) + Python。另外,如果是在 Vista 中安装 Bespin Server,你使用管理员权限来运行 cmd。
第 1 步:在这里把 Bespin 的源代码下载下来,然后解压到任意目录,我这里解压到的是“D:\Source\Bespin\bespin-8b89188c5066”。
第 2 步:这篇文章介绍的是 Python 版 Bespin 服务器的配置,因此还需要安装 Python 解释器,可以在这里下载到 Python 2.5.4,使用默认设置安装即可。
第 3 步:在 bespin 源代码目录中新建一个目录 Scripts,这个目录与 backend 和 frontend 两个目录是平级的。然后到 Python 的安装目录下,将安装目录下的 msvcr71.dll、python.exe、python25.dll、pythonw.exe 四个文件拷贝到前面建立的 Script 目录中。
第 4 步:将 Python 安装目录中的 libs 目录拷贝到 bespin 源代码目录中。
第 5 步:Bespin Python 服务器所用的有些组件是使用 C 写的,在 Windows 上要编译与 Python 兼容的 C 扩展,需要使用 MinGW,下载以下这些压缩包,解压到 D:\Tools\MinGW,这里解压的位置可以自己选,但是后面要用到,所以请记住你解压的路径:
http://nchc.dl.sourceforge.net/sourceforge/mingw/binutils-2.19.1-mingw32-bin.tar.gz
http://nchc.dl.sourceforge.net/sourceforge/mingw/gcc-g++-3.4.5-20060117-3.tar.gz
http://nchc.dl.sourceforge.net/sourceforge/mingw/gcc-core-3.4.5-20060117-3.tar.gz
http://nchc.dl.sourceforge.net/sourceforge/mingw/w32api-3.13-mingw32-dev.tar.gz
http://nchc.dl.sourceforge.net/sourceforge/mingw/mingwrt-3.15.2-mingw32-dev.tar.gz
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]



