retrieve the image's original size after have set it to a specific value

My colleague encountered such a problem this morning.

He had to display a set of images which were retrieved from a co-partner's search engine's results. But the engine didn't supply the images sizes together with the results. So he had to decide what sizes should he use for displaying these images.

Before these images onloaded in the user's browser, their sizes were unknown and might vary a lot, which it was not what we expected. 'Cause the layout of the page might be in a mess for these undecided images sizes. So my colleague decide to resize these images sizes by registering an onload event handler for each image. But it has defects. The page will flash once when these images have loaded and the onload event handlers have triggered.

So I think there's might be two solutions.

One, show a default image which the size is fixed at first and hide the onloading images, when imags loaded, replace these defaults images with the onloaded images(for these images now have sizes, and we can calculate new proper sizes for displaying on retaining the original scale of the width and height).

Two, set the images size to fixed values before they loaded. When these images have loaded completely, the images size won't change. But the scale of the width and height might be not correct. So calculate the sizes with both fixed width/height and the original width/height of the image for a new proper one to display.

They decide to take the second solution for some reason. But my colleage find it was hard to retrieve the original sizes of those images after we specified a fixed width/height. So how we deal with that? Actually, after some "exploring", I found the answer it's simple. Remove the fixed sizes (or say the width/height attributes) we've set, and then retrieve them one more time. And we got the actual size of these images.

For instance, we may use something like the codes below:

img.removeAttribute('width');
img.removeAttribute('height');

or

img.style.width = '';
img.style.height = '';

Then we can get the original sizes of image instead the ones we set it before.

Which one to use depends on what method you've chosen for setting the images sizes.

Forgive my poor English representing . I don't know clearly what I'm saying either. -___-#

Posted in 杂记 | Leave a comment

padding&margin on inline elements

Margins and padding on inline elements work a little differently from block elements - if you add a margin on all sides of an inline element, you'll only see space added to the left and right. You can add padding to the top and bottom of an inline element, but the padding doesn't affect the spacing of the other inline elements around it, so the padding will overlap the other inline elements.

from Head First HTML with CSS & XTHML page 466 chapter 11

Posted in 杂记 | Leave a comment

window.open with referer header, how?

被产品经理告知运营数据统计有问题。 原因是运营统计的js代码里面, 在ie下取不到由window.open函数打开页面的document.referrer。 试验了一下, firefox、 opera、 safari(均为最新稳定版本)都可以取到, ie sucks...

我的第一反应是应该用window.opener去取父窗口页面信息。 所以统计代码改为在取document.referrer为空时,尝试判断window.opener是否存在, 然后取window.opener.location.href。以为没有问题了, 于是发布了个测试版统计代码到自己的项目上。

然后发现问题。 在各浏览器下都经常会发生跨域访问问题。 在ie下会显示没有权限, firebug也会相应的报错。trace了一下, 发现两个页面的document.domain的确不同, 但还不是必现-__-#, 貌似是部门某个公用的服务在一定条件下会设置业务的子域。 所以有时候一个页面是xx.com, 另一个页面是aa.xx.com(举例)。但统计代码是在页面最下面的, 所以还不能在执行时再把domain改回到上一级域。 从修改时间成本等角度考虑, 于是产品经理说把页面里面通过注册onclik事件来window.open新页面的地方替换为<a>元素。 但这样会影响整个页面的展现, 而且有些地方也不可行。 于是想办法改一下onclick事件。

最开始想到的是在页面append一个<a>元素, 然后调用其click方法。 但是在手册和MDC中发现至少gecko内核的浏览器不支持<a>元素的click方法。 准确的说应该是除了<input>元素中某些类型, 如button、 radio等之外, 其他元素都不支持调用element.click方法。 所以想到用<form>和<input>来代替<a>, 用<form>中的action属性指定要打开页面的url, 用target属性指定是否打开新窗口, 然后通过method属性为GET方式提交表单。 当然<input>也就多余了, 直接调用<form>的submit方法就可以了, 这样就肯定可以取到document.referrer了。 但是又发现问题了。 用GET method提交之后, action属性中指定的url的cgi参数丢了。打开的新页面的url只到问号处, 后面的参数没有了。 而用POST方式, action属性中指定的url是完整的传递到新页面的, 但是POST肯定行不通的,一刷新页面就麻烦了。 觉得GET方式提交表单, 目标窗口的url参数应该是从<input>元素中提取的, 所以直接写入到action是不起作用的。

最后想了个简单的方法, 就是直接判断元素是否支持click方法, 如果支持, 就通过添加或修改元素的href属性, 然后调用click方法打开新页面;如果不支持,则直接调用window.open方法打开新页面。 因为ie通过window.open打开新页面取不到页面的domument.referrer, 但是恰好支持元素的click方法。 其他不支持元素click方法的浏览器用window.open也可以取到referrer。当然opera即支持元素的click方法, 又可以通过window.open方法取到打开页面的referrer。

当然如果打开新页面需要指定窗口大小等属性, 该方法就不适用了。 不知道还有什么好方法不。或者只能通过解决document.domain问题的方法了。

Posted in 杂记 | Leave a comment

javascript: null+"0" = ?

周五下班前, 其他组同事找我说上次给他们写的javascript脚本有个bug。 那个页面是在ie工具栏里的, 所以不能用firefox调, 很不方便。 跟了一下, 最终发现问题所在。

取得一个变量,应该是一个固定长度的字符串, 但有可能长度不够, 也有可能为null。 长度不够的话要在后面循环补“0”。 所以很直观的把这个变量v循环做v+="0"的操作。 于是问题就出来了, null+"0"等于什么? 之前没考虑过这个问题, 认为肯定是"0"。 结果偶然在后面charAt(0)的操作时返回了个"n", 于是就导致了这个bug。 发现null+"0"居然等于"null0", 有点儿出乎我的意料。 试了一下, undefined变量或其本身, 如果+"0"的话, 等于"undefined0"。 试了一下几种浏览器, 结果是一样的。 看来javascript会把null和undefined转化成字符串"null"和"undefined"。 这种强制转化的方式还是让我没有想到。 把加数位置调换一下, 也是字符连接操作的结果。 试了一下0+null等于0, 0+undefined等于NaN。 看来类似的边界问题还得注意一下, 不能太想当然了, 呵呵。

p.s. 好奇, 顺便试了一下在php里, null+"0" 或者"0"+null, 结果都是一个int型的0。

Posted in 杂记 | Tagged | Leave a comment

Goals Before Spring Festival

1. 读完《UNIX环境高级编程》和《UNIX网络编程》, 参与到后台开发中。 成为一名前端工程师&&后端工程师。

2. 体重减到75kg以下(including)。 肌肉发达一些, 肥肉尽量消除, 恢复弹跳(>=70cm)。

3. 学车(至少考完交规)。 估计到春节前总会有段不忙的时间。 起码产品经理已经说过无数次了, 不知能兑现否。

Posted in 杂记 | Tagged | 1 Comment

Write Before My Birthday

    It's the last day of the 22nd year of my age.

    This year means a lot to me. No more a student in college. Worked in my first company for almost a year. Got a little bit more mature. And the most important, I got my love. I hope and believe it will last forever.

    Life is hard and always full of uncertainty and difficulty. We will prepare for it, face it and challenge it to make it worthwhile.

    Thanks all my friends for your trust, supports, helps and companies all along. And the blessings i'll recieve the incoming day :)

Posted in 心情 | 6 Comments

kinda get lost

工作差不多已经9个月了。 感觉反而越来越迷茫了。

这大部分时间里是在学习和做一些前端的事情。 毕业前, 前端的知识几乎为零。而感觉前端的东西很多很杂, 虽然门槛相对较低一些。 但我觉得任何一个方向想做得很深入、 想做好都不是一件容易的事情。 感觉现在对前端有了一定整体的把握了。 虽然离前端牛人还有很大差距, 但觉得自己学东西还算是比较深入, 起码不是仅停留在在表面。 现在应付产品的前端需求应该是没有什么问题, 当然可能也是没有什么特别有挑战的需求,我们组有什么前端的问题一般也都会先问问我。 我觉得我现在在CSS方面可能差得要多一些, 但美工那边水平实在是有限, 所以我要是自己去研究的话就会有事倍功半的感觉了。 JavaScript, 有时会有一些想法, 但是工作总是安排的很紧张。 打算把几个主流js框架仔细研究对比一下,并逐渐积累自己代码, 但一直没时间和精力落实到行动。

之所以感到迷茫, 原本是打算在前端深入下去, 但是这边前端实在没有强人, 连交流的环境都没有。 我知道自己水平还是很有限的, 但我实在不知道这边在前端方面有谁比我强很多, 甚至是比我强一些。

前一段时间在做一些有关数据库的调整, 也算是刚刚开始接触到数据库这方面的东西。 老大rong还推荐我看一下《Databases Demystified》。刚入职的时候, 有时部门讲座或组内技术讨论讲一些有关架构的东西时, 总觉得很抽象很无趣, 但现在慢慢觉得很有意思。

之前在看一本PHP的书, 感觉了解一些新的东西。 但在实际中也用不大上。 需求的实现最多也就是很小范围的修改一下框架, 写写代码, 只能算是维持一下编码能力吧。

之后一直在犹豫, 是看看有关CSS、JavaScript的前端技术的书呢, 还是巩固一下自己的基础, 看看算法或有关程序设计的书呢, 或是深入看看数据库或关于框架的东西呢。 最后还是觉得先看看APUE。 自己大概一年半都没写过C代码了,正好借着这个机会复习一下。 而且自己对于线程、 通信等很多问题还是有不少空白, 之后打算看一下网络编程, 然后想自己写个server啥的。 总觉得现在总是用别人写的东西, 有些开源的东西还是很优秀的, 但是一些自己人写的东西, 我觉得要是我写, 应该比他们写的更好。 所以也不甘心自己只负责前端的工作。 虽然上面会说诸如前端做好了也很了不起、 也很有发展云云, 但以目前公司、 部门的前端水平, 以及他们对前端的掌握和认识, 我觉得还是满敷衍的。 而且我觉得有些东西无论怎样, 还是很有必要学好的。 毕竟自己是学计算机的, 而不是做IT蓝领的。

Posted in 心情 | 6 Comments

for the month just passed

上个月一直挺忙的, 很多事情也没时间记录一下, 只好现在回忆一下了。

11号我们组去腐佳节又重阳败, 在北大西门那边吃的烤翅。 酒是不能少的, 不过由于之前的游戏大家都已经非常熟了, 被罚酒的次数明显下降, 都担心酒喝不完。 于是我想到大四时酒桌上不许说“你我他”的玩法,只要提到这三个字中的任何一个就得罚酒一杯, 一经提议即被采纳。 效果很不错, 一箱酒瞬间就不够了。 const每次都是听到别人说到某个字,然后用手比划半天, 最后来了句“我听到你刚才说...”, 然后自罚两杯。 XO的酒还没喝就又连续被罚3杯。 colin每次被罚之后还得补一句“我X”。 总之大家玩得很开心。直到我们组老大和我们说他要离职了。 瞬间鸦雀无声了。 估计是大家都觉得比较突然吧。 老大人非常好,技术也很强。 我觉得这对公司尤其是我们组, 是非常大的损失。 我们都非常舍不得老大离开。 还是祝福老大的创业之路一帆风顺:)之后去清华打保龄, 我是第一次玩保龄, 感觉挺有意思的。

24号是部门组织的篮球友谊赛。 技术对产品。 比赛开始了, 才发现自己的体力真是远不如前了。 对方的后卫vivien简直体力太好了, 满场飞奔, 防守也是非常积极。 对方的高大中锋, 我赛前夸下海口, 说防他简直太轻松了。 结果那天他突然发飙了, 弄得我很没面子-__-# 后来我突破上篮, 他过来封盖, 我把球打进之后, 他落地时踩到我, 然后脚崴了,还去了医院... 之后比分一度相当接近, 但我们没抓住机会,最后由于对方组织得很好, 而我们球很难传到前场, 被打垮了。 赛后作为对方主力的部门老大带我们一起去吃烤翅 (发现这东东的确很好吃啊)。我到家基本上快夜里一点了。

这个月工作一直很忙。 发现总和UI协调不好。 他们总是在做视觉体验, 而做出来的页面, 简直没有啥可用性。 让他们返工, 即使是比较小的部分, 也会由于他们比较忙而往后延很多天。 结果大大压缩了我的开发时间。 弄得我这个月很多不必要的辛苦。 以前周末从来没加过班, 结果这个月就两次。 我真的是怕产品不能按时提测、 上线。 而总感觉他们就没点儿责任感。 我要不是把产品当作自己的产品, 我也不会经常加班到晚上八东篱把酒黄昏后九点, 甚至周末也出来。 为什么不能把UI也按产品和其他人分到一组呢。

这个阶段的工作很前端, js和相关的代码写了一两千行了, php写的很少, 可能不比写的shell脚本多多少-__-# 下一阶段整个数据库要重新调整、 划分,php代码要大面积的重构。 正好是个补数据库知识和熟练php的机会。 数据库的设计我主要是参与讨论。 最后基本上还是peterwang定的。 发现有个人带着还是很好的, 虽然项目90%的工作还是我一个人做, 但在很多方面, 老员工的经验的确能让自己少走很多弯路。 当然也是因为peter、 colin等人都很好, 大家在一起都很爱讨论问题,技术氛围非常好。 peter很学术, 丝毫没有项目经理的架子。 peter和colin对于vim的使用也很在行,最近经常向他俩请教, 发现vim越来越顺手了。 有这样一个氛围也的确是一个很好学习的机会。

才想起来23号的时候, 晓雷和柴营来北京玩。 先去了中央电视塔下面的海洋馆,感觉和上次在烟台看的那个差得很远, 自己不是很想去, 主要是陪他俩玩了。 然后去看了看鸟莫道不消魂巢、 水有暗香盈袖立方。 虽然还没开放, 但还是挑了几个不错的位置照了几张像。 满达同学也赶来了, 大家在一起还是很亲切的:)

来公司已经8个月了, 有时觉得自己进步挺快, 但更多的时候觉得自己差得很远。 精力大部分是在前端。而发现公司(起码是所在部门)这方面真是没有强人, 更别提牛人了。  如果全靠自己去学, 而和工作脱离, 感觉对于学习和工作都是比较低效的。 这也一直使我的发展方向产生些许不确定性。 只好先把自己知识上漏洞比较大的地方补好, 再找一两个方面深入下去了。

Posted in 杂记 | Leave a comment

How JavaScript Timers Work [译]

出处: http://ejohn.org/blog/how-javascript-timers-work/
注: 本人英文水平和专业水平均有限, 翻译文章也是第一次, 纯属个人兴趣。 不求翻译准确无误, 只是想和大家分享其中的内容。 欢迎讨论和指正。

从一个基础的层面来讲, 理解JavaScript中的timers(定时器, 两种称呼在下文均有可能使用) 如何工作是十分重要的。 由于timers所处的单线程机制, 通常使他们的行为和我们直观想像的不同。 先来看一下以下三个可以让我们构造和操作timers的函数。

  • var id = setTimeout(fn, delay); - 初始化一个单独的timer, 用来在一定时间间隔之后调用特定的函数。 这个函数返回一个唯一的标识ID, 用来随后取消这个timer。
  • var id = setInterval(fn, delay); - 类似于setTimeout函数, 区别是每隔一个周期(时间间隔)都会调用同一个函数, 直到被取消。
  • clearInterval(id);, clearTimeout(id); - 接收一个标识ID(前面所提到的函数返回的), 停止timer回调函数的发生。

为了理解timers内部如何工作, 有一个重要的概念需要探明: timer的发生并不被保证。 由于所有JavaScript程序在浏览器中执行单线程异步事件(asynchronous events)(比如鼠标点击和timers)当且仅当执行过程中有执行机会时执行(即需要按顺序执行, 等待空闲)。 我们可以通过下图去表明这一观点:

这个图表里有很多信息值得去仔细体会, 一旦完全理解了, 你将对JavaScript如何异步执行工作有一个更好的认识。 这是一个一维的图表, 垂直的表示时间, 按毫秒计算。 蓝色的方块代表JavaScript正在被执行的部分。 比如一个JavaScript执行方块的执行时间大概是18毫秒, 鼠标点击(Mouse Click Callback)的执行方块大概是11毫秒, 等等。

由于JavaScript只能在同一时间执行一个段代码(由他单线的特性所决定) , 这些执行代码块中的每个都会“阻塞”其他的异步事件的进行。 这意味着当一个异步事件发生(比如鼠标点击, 定时器触发, 或异步请求)时, 他会排到执行队列后面(这个队列如何排列在各浏览器中的行为是不同的, 所以这样考虑是一个简化)。

从第一个JavaScript执行代码块开始分析, 有两个定时器被初始化: 一个10毫秒的setTimeout和一个10毫秒间隔的setInterval。 根据定时器何时何地被初始化, 他实际上会在第一个执行方块完成之前被触发。 注意, 触发后不会马上执行(由于单线程的原因) 。 而被延后的函数将排在执行队列之后, 并且等到下一个可能的执行时刻执行。

另外, 在第一个执行块中, 我们看到一个鼠标点击发生了。 一个JavaScript回调绑定在这个异步事件了(我们永远不会知道用户何时会执行这样一个操作, 因此他被考虑成异步的), 但不能立刻被执行, 因此, 像开始的定时器一样, 排在执行队列后面等待执行。

在第一个初始化的执行块结束后, 浏览器立刻询问: 谁是下一个等待被执行的。 这时, 鼠标点击处理函数和定时器回调函数都在等待执行。 于是浏览器选择了鼠标点击事件的回调函数并立即执行。 定时器将等到下一个可能的时间去执行。

注意, 在鼠标点击处理函数执行时, 第一次interval回调函数触发了。 和定时器一起, 他的处理函数排入队列等待随后的执行。 然而, 注意当这个interval第二次触发时(当setTimeout的处理函数正在执行时), 他的处理函数被抛弃掉了。 如果把所有的interval回调函数都排在执行队列后面,当一大段代码执行完之后, 结果便会是很多interval的处理函数一个接着一个没有间隙的执行, 直到全部完成。因此, 浏览器倾向于简单的当没有更多interval的处理函数排队时再将下一个处理函数排到队尾。

我们可以看到, 当第三个interval触发时, 之前的interval函数正在执行。 这也证实了一点, interval并不关注当前执行的是什么, 而是直接排到执行队列后面。 这就意味着两次interval回调函数之间的时间间隔有可能被牺牲(缩短)。

最后, 在第二个interval回调函数执行结束之后, 我们可以看到没有任何其他等待JavaScript引擎去执行的了。 这意味着浏览器现在等待这一个新的异步事件发生。 我们在50毫秒时等来了interval的又一次触发。 这时没有任何其他事件阻塞他了, 所以立即就执行了。

让我们通过一个实例来更好的证明setTimeout和setInterval之间的区别。

  setTimeout(function(){
    /* Some long block of code... */
    setTimeout(arguments.callee, 10);
  }, 10);  setInterval(function(){
    /* Some long block of code... */
  }, 10);

这两段代码乍一看功能上是相同的, 但其实并不然。 最明显的,setTimeout代码的执行最少要在前一次回调执行10毫秒之后执行(只会比这个多, 不会少)。 然而setInterval将尝试每10毫秒执行一次, 无论上次回调函数何时执行完毕。

我们在这里了解到了很多东西, 让我们一起回顾概括一下:

  • JavaScript引擎只有一个单一的线程, 强制异步事件排队等待执行。
  • setTimeout和setInterval在执行异步代码时从根本上是不同的。
  • 如果一个定时器被阻塞而不能立即执行, 他会延迟到下一个可能的执行点(通常会比期望的时间间隔长)
  • 如果Interval的处理函数的执行时间足够长(比指定的间隔时间长), 他会背靠背的执行而没有间隔。

上述这些都是非常重要的需要了解的知识。 了解JavaScript引擎如何工作, 特别的当大量的异步事件发生时, 才能为构建优质的应用程序代码打好基础。

This is an excerpt from my work-in-progress book: Secrets of the JavaScript Ninja. To be released Fall 2008. (此段是笔者原文)

Posted in 技术 | Tagged , , | 5 Comments

Function.prototype.curry in Prototype JavaScript

前些天读了一下Prototype JavaScript Framework的源码, 由于后来老大让我在上周的技术交流会讲一下有关的内容, 所以读的有点儿匆忙, 有很多地方没有完全读懂。 而且是在读完代码然后又读了一遍API文档之后, 才缕清思路, 包括各模块间的继承关系。 不过很多实现的细节还是有待深掘。

今天集中时间清理了一下未读的RSS, 主要是技术文章, 平时怕没时间细看, 就攒到周末了。 结果卡在John Resig大哥的一篇名为《Partial Application in JavaScript》的文章上了。 其实是自己对JavaScript语言的一些特性生疏了, 而这篇文章的例子虽然很简短, 但充分运用这些在JavaScript中算是比较高级而且很绕的特性, 非常巧妙。 虽然不到10行代码让我读了2个小时才读懂, 但期间获得的收获是非常大的。 算是让我拾起并且比较透彻的明白这些特性。 《JavaScript 权威指南 第五版》上说, 这部分内容弄懂之后就算是advanced JavaScript programmer了。 我觉得的确是如此, 而大多数程序员只是简单去运用JavaScript的一些功能, 并不会深入研究这些特性。 我希望将来能够为开源事业贡献自己的一份力量, 所以一定要打好基础:)

言归正传。 我并不打算去翻译John Resig的这篇文章, 虽然这样可能会更简单些。 我还是希望能结合自己学习这部分知识时的感受来和大家分享。

先看这样一段代码吧, 借鉴了prototype javascript framework(以下简称prototypejs)的源码, 但是源码上由于有对其他部分的函数依赖关系, 所以稍微改了一下。

Function.prototype.curry = function() {
        if (!arguments.length) return this;
        var __method = this;
        var __args = Array.prototype.slice.call(arguments);
        return function() {
                return __method.apply(this, __args.concat(
                        Array.prototype.slice.call(arguments)));
        };
};

这个curry函数是prototypejs对Function的扩展。 这样每创建一个新的函数, 无论是以new Function()的形式, 还是匿名与否的形式, 该函数都会继承了这个curry的方法。 再来说一下这个函数的用途。 curry这个单词是咖喱的意思。 当时也没细研究,觉得这个词和这个函数没有什么直接的关系, 当然开始也没明白这个函数是做什么用的。 其实这个curry在这里是添加佐料的意思, 而这个函数正是用来给一个函数预添加参数用的, 这样一想, 这个名字还是很形象的。

举个例子说一下吧 , 比如有一个函数f, 而这个函数接收若干个参数, 比如4个, 其中前两个参数在处理一类问题的总是固定的, 处理另外一类问题又是另一组相对固定的值。 所以如果我打算处理A类问题时, 我希望前两个参数不用每次都去填, 处理B类问题也是如此。 这时curry就可以起到这个作用了。 我可以为处理A类问题定义一个函数fa, 如var fa = f.curry(arga1, arga2), 这样参数arg1和arg2就算是预填进函数f了。 这样在处理A类问题时, 我们可以直接条用函数fa, 比如fa(arg3, arg4), 这时我们只需指定后两个参数arg3和arg4即可。 同样, 处理B类问题, var fb = f.curry(argb1, argb2), 然后调用fb(arg3, arg4), 即指定后两个参数即可。 换句话说, 我们分别为函数fa和fb指定了前两个参数的默认值。 JavaScript言语本身是不支持函数参数的默认值的, 所以这个curry函数非常有用。

知道函数的用途了, 我们再来看看上面的代码。 第一行就不用说了, JavaScript是一种prototype-based inheritance的语言。 第二行if (!arguments.length), 判断curry的参数是否为空, 如果为空, 那么curry函数就失去了预填参数的目的, 所以直接返回this了。 这里的this指向的是调用curry函数的那个函数。 比如f.curry(arg1, arg2), 在curry函数中的this关键字就指向f这个函数。 可以在curry函数中alert(this), 然后f.curry()试一下。 因为this关键字是指向call object的, 就是调用他的对象。 我们是对Function进行的扩展, 所以可以理解为每次创建一个新的函数时, 都new了一次Function, 当然实际上是调用了Function.prototype.constructor。 而创建的结果就是得到了一个function(注意function和Function的区别)。所以this指向就是这个调用curry函数的函数。

第三行将调用curry函数的函数赋值给__method变量。 第四行将curry函数的参数赋值给__args。 注意一下这个arguments, 他其实不是一个数组, 而是一个Array-like的对象。 所以我们要调用数组函数slice, 把他转化成数组。 判断一个变量是否是数组, 用typeof关键字是不够的, 因为数组也是对象, 所以用typeof取得的数组类型也是object。 可以判断这个变量的constructor是否为Array, 如var arr = []; alert(arr.constructor == Array);, 显示为true。 我们用call方法把arguments当作slice函数的this关键字传了进去, 然后得到了一个curry参数的数组。

之后的return返回了一个函数, 这也正是curry函数要做的事情, 返回一个已经预填了参数的新函数, 保持原函数功能不变。

这个返回的函数只有一条语句return __method.apply(this, __args.concat(Array.prototype.slice.call(arguments))); 这里用到了JavaScript的一个高级特性, 闭包(closure)。 闭包是个很有意思的东西, 很多JavaScript进阶技巧都是通过他来实现的。 比如实现对象属性的私有化。 通过这个例子来看, 这个返回的函数中用到了两个变量__method和__args, 而这两个变量是在这个函数的外层作用域, 也就是curry函数的call object作用域中的。 结合这个例子, 闭包是指即使这个返回的函数已经脱离了外层作用域(这里是curry函数的call object的作用域), 而所用到的外层作用域的变量值仍然会被这个返回的函数所保留。 所以如果是通过语句var fa = f.curry(arg1, arg2)所得到的返回函数,是var fa = function() {return f.apply(this, [arg1,arg2].concat(Array.prototype.slice.call(arguments)));}。这样我们在调用函数fa时, 会把传进fa的参数压进和arg1,ar2所在的同一数组, 然后作为参数去调用f函数, 这也正是我们所想要的。

这段代码虽然非常简短, 但是关联到的知识点很多, 而且都是深入学习JavaScript语言非常重要的东西。 现在流行的JavaScript框架都大量运用了上述提到的知识点。 我觉得花了两个小时研究学习这些知识点之后, 再去看prototypejs框架的源代码, 有很多地方就很容易理解了。

《Partial Application in JavaScript》一文后面提到了如何完善一个partial application in JavaScript, 相信大家掌握了上述知识点再去看下面的例子就很轻松了, 我把代码贴过来,有兴趣可以研究一下。

Function.prototype.partial = function(){
        var fn = this, args = Array.prototype.slice.call(arguments);
        return function(){
        var arg = 0;
        for ( var i = 0; i < args.length && arg < arguments.length; i++ )
                if ( args[i] === undefined )
                        args[i] = arguments[arg++];
        return fn.apply(this, args);
    };
};
var delay = setTimeout.partial(undefined, 10);
delay(function(){ alert( "A call to this function will be temporarily delayed." ); });
var bindClick = document.body.addEventListener .partial("click", undefined, false);
bindClick(function(){ alert( "Click event bound via curried function." ); });
Posted in 技术 | Tagged , , , | 2 Comments