应几位台湾朋友的要求为ThinkPage天气预报插件增加了繁体中文支持,可以在天气预报代码生成向导中选择。英文版的参数由en修改为en-US,简体中文是zh-CN,繁体中文zh-TW,使用英文版的用户可以自行修改“l”参数为en-US。另外,修正了天气预报插件在其他几种皮肤下背景透明的bug。感谢大家对ThinkPage天气预报的支持,我会尽量及时对您的建议给予回应。

http://www.thinkpage.cn/weather/

2007.8.27 完成了城市列表的繁体转换。

博客里的广告留言越来越多,清理广告成了日常工作,这事也持续了挺长时间了,屡删不止。在这里我得对发广告的哥们说一下,您留了我也得删,您也就别留了,您省事我也省心。如果是想交换链接可以在“链接”里申请。另外,就算发广告您也发点有水准上层次的啊,没准我看着顺眼会刀下留人,别那么庸俗!

  策划了1个多月的产品终于完成了文档原型设计。我跟digitalghost策划的这个创新产品目前互联网上还没发现。这个产品不仅仅是个概念,而且具备明确的盈利模式,但我的目标并非盈利,而是专注于用户的需求和体验。由于涉及产品的机密信息,现在还不能透露太多,请大家拭目以待。

在网上看了一篇不错的JavaScript基础知识文章,感谢Realazy的辛苦翻译,后来才得知原来Realazy就是我同事的朋友,晕,世界真小。(PS:CSDN上的名人孟子e章竟然也是我同事的哥们,ft。。。)

看了这篇文章才发现越是基础的东西越能展示水平,这也就是为什么很多朋友向我索取原代码我都没有回复,并非不想分享,而是觉得自己的作品还不足以达到供别人学习的程度,还需努力。

引用:http://realazy.org/blog/2007/07/18/scope-in-javascript/

作用域(scope)是JavaScript语言的基石之一,在构建复杂程序时也可能是最令我头痛的东西。记不清多少次在函数之间传递控制后忘记 this关键字引用的究竟是哪个对象,甚至,我经常以各种不同的混乱方式来曲线救国,试图伪装成正常的代码,以我自己的理解方式来找到所需要访问的变量。

这篇文章将正面解决这个问题:简述上下文(context)和作用域的定义,分析可以让我们掌控上下文的两种方法,最后深入一种高效的方案,它能有效解决我所碰到的90%的问题。

我在哪儿?你又是谁
JavaScript 程序的每一个字节都是在这个或那个运行上下文(execution context)中执行的。你可以把这些上下文想象为代码的邻居,它们可以给每一行代码指明:从何处来,朋友和邻居又是谁。没错,这是很重要的信息,因为 JavaScript社会有相当严格的规则,规定谁可以跟谁交往。运行上下文则是有大门把守的社区而非其内开放的小门。

我们通常可以把这些社会边界称为作用域,并且有充足的重要性在每一位邻居的宪章里立法,而这个宪章就是我们要说的上下文的作用域链(scope chain)。在特定的邻里关系内,代码只能访问它的作用域链内的变量。与超出它邻里的变量比起来,代码更喜欢跟本地(local,即局部)的打交道。

具体地说,执行一个函数会创建一个不同的运行上下文,它会将局部作用域增加到它所定义的作用域链内。JavaScript通过作用域链的局部向全局攀升方式,在特定的上下文中解析标识符。这表示,本级变量会优先于作用域链内上一级拥有相同名字的变量。显而易见,当我的好友们一起谈论”Mike West”(本文原作者)时,他们说的就是我,而非bluegrass singer 或是Duke professor, 尽管(按理说)后两者著名多了。

让我们看些例子来探索这些含义:

<script type="text/javascript">
var ima_celebrity = "Everyone can see me! I'm famous!",
the_president = "I'm the decider!";

function pleasantville() {
var the_mayor = "I rule Pleasantville with an iron fist!",
ima_celebrity = "All my neighbors know who I am!";

function lonely_house() {
var agoraphobic = "I fear the day star!",
a_cat = "Meow.";
}
}
</script>

我们的全明星,ima_celebrity, 家喻户晓(所有人都认识她)。她在政治上积极活跃,敢于在一个相当频繁的基层上叫嚣总统(即the_president)。她会为碰到的每一个人签名和回答问题。就是说,她不会跟她的粉丝有私下的联系。她相当清楚粉丝们的存在 并有他们自己某种程度上的个人生活,但也可以肯定的是,她并不知道粉丝们在干嘛,甚至连粉丝的名字都不知道。

而在欢乐市(pleasantville)内,市长(the_mayor)是众所周知的。她经常在她的城镇内散步,跟她的选民聊天、握手并亲吻小孩。因为欢乐市(pleasantville)还算比较大且重要的邻居,市长在她办公室内放置一台红色电话,它是一条可以直通总统的7×24热线。她还可以看到市郊外山上的孤屋(lonely_house),但从不在意里面住着的是谁。

而孤屋(lonely_house)是一个自我的世界。旷恐患者时常在里面囔囔自语,玩纸牌和喂养一个小猫(a_cat)。他偶尔会给市长(the_mayor)打电话咨询一些本地的噪音管制,甚至在本地新闻看到ima_celebrity后会写些粉丝言语给她(当然,这是pleasantville内的ima_celebrity)。

this? 那是虾米?
每一个运行上下文除了建立一个作用域链外,还提供一个名为this的关键字。它的普遍用法是,this作为一个独特的功能,为邻里们提供一个可访问到它的途径。但总是依赖于这个行为并不可靠:取决于我们如何进入一个特定邻居的具体情况,this表示的完全可能是其他东西。事实上,我们如何进去邻居家本身,通常恰恰就是this所指。有四种情形值得特别注意:

呼叫对象的方法
在经典的面向对象编程中,我们需要识别和引用当前对象。this极好地扮演了这个角色,为我们的对象提供了自我查找的能力,并指向它们本身的属性。

<script type="text/javascript">
var deep_thought = {
the_answer: 42,
ask_question: function () {
return this.the_answer;
}
};

var the_meaning = deep_thought.ask_question();
</script>

这个例子建立了一个名为deep_thought的对象,设置其属性 the_answer为42,并创建了一个名为ask_question 的方法(method)。当deep_thought.ask_question()执行时, JavaScript为函数的呼叫建立了一个运行上下文,通过”.“运算符把this指向被引用的对象,在此是deep_thought这个对象。之后这个方法就可以通过this在镜子中找到它自身的属性,返回保存在 this.the_answer中的值:42。

构造函数
类似地,当定义一个作为构造器的使用new关键字的函数时,this可以用来引用刚创建的对象。让我们重写一个能反映这个情形的例子:

<script type="text/javascript">
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
return this.the_answer;
}
}

var deep_thought = new BigComputer(42);
var the_meaning = deep_thought.ask_question();
</script>

我们编写一个函数来创建BigComputer对象,而不是直白地创建 deep_thought对象,并通过new关键字实例化deep_thought为一个实例变量。当new BigComputer()被执行,后台透明地创建了一个崭新的对象。呼叫BigComputer后,它的this关键字被设置为指向新对象的引用。这个函数可以在this上设置属性和方法,最终它会在BigComputer执行后透明地返回。

尽管如此,需要注意的是,那个deep_thought.the_question()依然可以像从前一样执行。那这里发生了什么事?为何this在the_question内与BigComputer内会有所不同?简单地说,我们是通过new进入BigComputer的,所以this表示“新(new)的对象”。在另一方面,我们通过 deep_thought进入the_question,所以当我们执行该方法时,this表示 “deep_thought所引用的对象”。this并不像其他的变量一样从作用域链中读取,而是在上下文的基础上,在上下文中重置。

函数呼叫
假如没有任何相关对象的奇幻东西,我们只是呼叫一个普通的、常见的函数,在这种情形下this表示的又是什么呢?

<script type="text/javascript">
function test_this() {
return this;
}
var i_w
onder_what_this_is = test_this();
</script>

在这样的场合,我们并不通过new来提供上下文,也不会以某种对象形式在背后偷偷提供上下文。在此, this默认下尽可能引用最全局的东西:对于网页来说,这就是 window对象。

事件处理函数
比普通函数的呼叫更复杂的状况,先假设我们使用函数去处理的是一个onclick事件。当事件触发我们的函数运行,此处的this表示的是什么呢?不凑巧,这个问题不会有简单的答案。

如果我们写的是行内(inline)事件处理函数,this引用的是全局window对象:

<script type="text/javascript">
function click_handler() {
alert(this); // 弹出 window 对象
}
</script>

<button id='thebutton' onclick='click_handler()'>Click me!</button>

但是,如果我们通过JavaScript来添加事件处理函数,this引用的是生成该事件的DOM元素。(注意:此处的事件处理非常简洁和易于阅读,但其他的就别有洞天了。请使用真正的addEvent函数取而代之):

<script type="text/javascript">
function click_handler() {
alert(this); // 弹出按钮的DOM节点
}

function addhandler() {
document.getElementById('thebutton').onclick = click_handler;
}

window.onload = addhandler;
</script>

<button id='thebutton'>Click me!</button>

复杂情况
让我们来短暂地运行一下这个最后的例子。我们需要询问deep_thought一个问题,如果不是直接运行click_handler而是通过点击按钮的话,那会发生什么事情?解决此问题的代码貌似十分直接,我们可能会这样做:

<script type="text/javascript">
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
alert(this.the_answer);
}
}

function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');

the_button.onclick = deep_thought.ask_question;
}

window.onload = addhandler;
</script>

很完美吧?想象一下,我们点击按钮,deep_thought.ask_question被执行,我们也得到了“42”。但是为什么浏览器却给我们一个undefined? 我们错在何处?

其实问题显而易见:我们给ask_question传递一个引用,它作为一个事件处理函数来执行,与作为对象方法来运行的上下文并不一样。简而言之,ask_question中的 this关键字指向了产生事件的DOM元素,而不是在BigComputer的对象中。DOM元素并不存在一个the_answer属性,所以我们得到的是 undefined而不是”42″. setTimeout也有类似的行为,它在延迟函数执行的同时跑到了一个全局的上下文中去了。

这个问题会在程序的所有角落时不时突然冒出,如果不细致地追踪程序的每一个角落的话,还是一个非常难以排错的问题,尤其在你的对象有跟DOM元素或者window对象同名属性的时候。

使用.apply()和.call()掌控上下文
在点击按钮的时候,我们真正需要的是能够咨询deep_thought一个问题,更进一步说,我们真正需要的是,在应答事件和setTimeout的呼叫时,能够在自身的本原上下文中呼叫对象的方法。有两个鲜为人知的JavaScript方法,apply和call,在我们执行函数呼叫时,可以曲线救国帮我们达到目的,允许我们手工覆盖this的默认值。我们先来看call:

<script type="text/javascript">
var first_object = {
num: 42
};
var second_object = {
num: 24
};

function multiply(mult) {
return this.num * mult;
}

multiply.call(first_object, 5); // 返回 42 * 5
multiply.call(second_object, 5); // 返回 24 * 5
</script>

在这个例子中,我们首先定义了两个对象,first_object和second_object,它们分别有自己的num属性。然后定义了一个multiply函数,它只接受一个参数,并返回该参数与this所指对象的num属性的乘积。如果我们呼叫函数自身,返回的答案极大可能是undefined,因为全局window对象并没有一个num属性除非有明确的指定。我们需要一些途径来告诉multiply里面的this关键字应该引用什么。而multiply的call方法正是我们所需要的。

call的第一个参数定义了在业已执行的函数内this的所指对象。其余的参数则传入业已执行的函数内,如同函数的自身呼叫一般。所以,当执行multiply.call(first_object, 5)时,multiply被呼叫,5传入作为第一个参数,而this关键字被设置为first_object的引用。同样,当执行multiply.call(second_object, 5)时,5传入作为第一个参数,而this关键字被设置为second_object的引用。

apply以call一样的方式工作,但可以让你把参数包裹进一个数组再传递给呼叫函数,在程序性生成函数呼叫时尤为有用。使用apply重现上一段代码,其实区别并不大:

<script type="text/javascript">

multiply.apply(first_object, [5]); // 返回 42 * 5
multiply.apply(second_object, [5]); // 返回 24 * 5
</script>

apply和call本身都非常有用,并值得贮藏于你的工具箱内,但对于事件处理函数所改变的上下文问题,也只是送佛到西天的中途而已,剩下的还是得我们来解决。在搭建处理函数时,我们自然而然地认为,只需简单地通过使用call来改变this的含义即可:

function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');

the_button.onclick = deep_thought.ask_question.call(deep_thought);
}

代码之所以有问题的理由很简单:call立即执行了函数(译注:其实可以用一个匿名函数封装,例如the_button.onclick = function(){deep_thought.ask_question.call(deep_thought);},但比起即将讨论的bind来,依然不够优雅)。我们给onclcik处理函数一个函数执行后的结果而非函数的引用。所以我们需要利用另一个JavaScript特色,以解决这个问题。

.bind()之美
我并不是 Prototype JavaScript framework的忠实粉丝,但我对它的总体代码质量印象深刻。具体而言,它为Function对象增加一个简洁的补充,对我管理函数呼叫执行后的上下文产生了极大的正面影响:bind跟call一样执行相同的常见任务,改变函数执行的上下文。不同之处在于bind返回的是函数引用可以备用,而不是call的立即执行而产生的最终结果。

如果需要简化一下bind函数以抓住概念的重点,我们可以先把它插进前面讨论的乘积例子中去,看它究竟是如何工作的。这是一个相当优雅的解决方案:

<script type="text/javascript">
var first_object = {
num: 42
};
var second_object = {
num: 24
};

function multiply(mult) {
return this.num * mult;
}

Function.prototype.bind = function(obj) {
var method = this,
temp = function() {
return method.apply(obj, arguments);
};

return temp;
}

var first_multiply = multiply.bind(first_object);
first_multiply(5); // 返回 42 * 5

var second_multiply = multiply.bind(second_object);
second_multiply(5); // 返回 24 * 5
</script>

首先,我们定义了first_object, second_object和multiply函数,一如既往。细心处理这些后,我们继续为Function对象的prototype定义一个bind方法,这样的话,我们程序里的函数都有一个bind方法可用。当执行multiply.bind(first_object)时,JavaScript为bind方法创建一个运行上下文,把this置为multiply函数的引用,并把第一个参数obj置为first_object的引用。目前为止,一切皆顺。

这个解决方案的真正天才之处在于method的创建,置为this的引用所指(即multiply函数自身)。当下一行的匿名函数被创建,method通过它的作用域链访问,obj亦然(不要在此使用this, 因为新创建的函数执行后,this会被新的、局部的上下文覆盖)。这个this的别名让apply执行multiply函数成为可能,而传递obj则确保上下文的正确。用计算机科学的话说,temp是一个闭包(closure),它可以保证,需要在first_object的上下文中执行multiply,bind呼叫的最终返回可以用在任何的上下文中。

这才是前面说到的事件处理函数和setTimeout情形所真正需要的。以下代码完全解决了这些问题,绑定deep_thought.ask_question方法到deep_thought的上下文中,因此能在任何事件触发时都能正确运行:

function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');

the_button.onclick = deep_thought.ask_question.bind(deep_thought);
}

漂亮。

转自:http://benchwang.spaces.live.com/blog/cns!1621B5CAD6EB680B!149.entry

Adam McCrea 写了篇使用 JavaScript 进行元编程的文章: Metaprogamming JavaScript。
该文用一个例子来说明元编程。例子很简单,一个 Form 包中两个下拉列表 country 和 state,业务需求是 country 下拉列表中选中“United States”则要显示 state 列表,否则隐藏该下拉列表。处理该逻辑的代码如下:

Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
if ($F(“country”) == “United States”)
$(“state-field”).show();
else
$(“state-field”).hide();
});
});

这里用到的对象和方法是 Prototype 对 JavaScript 的扩展。Prototype 是旨在简化动态 Web 程序开发的 JavaScript Framework. 详见 http://www.prototypejs.org/。在上面代码中:Event.observe 在IE下相当于 attachEvent,用于注册事件处理函数。这是注册了 window.onload 和 country.onchange 事件处理程序。$() 是 document.getElementById() 的别名。$F() 会取出 form 中元素当前值。show() 和 hide() 是在 html 元素上增加的方法。通过 prototype 在 html 元素基础上扩展了很多方法。如果想运行上面代码,需要下载 prototype.js 并放置在 html 页面相同目录。完整代码如下:

<html>
<head>
<title>Metaprogramming JavaScript – Example 1</title>
<script src=”prototype.js”></script>
<script type=”text/javascript” charset=”utf-8″>
Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
if ($F(“country”) == “United States”)
$(“state-field”).show();
else
$(“state-field”).hide();
});

});
</script>
</head>

<body>
<form>
<p id=”country-field”>
<label for=”country”>Country</label>
<select id=”country”>
<option>United States</option>
<option>Canada</option>
<option>Somewhere Else</option>
</select>
</p>

<p id=”state-field”>
<label for=”state”>State</label>
<select id=”state”>
<option>Ohio</option>
<option>Michigan</option>
<option>Kentucky</option>
</select>
</p>
</form>
</body>
</html>

这些代码满足这样简单的需要没有什么问题。然而随着需求变化,代码会变得繁琐。假如增加如下需求:
增加 province 字段
当 country 为 “Canada” ,显示 province
当 country 为 “United States”,显示 state
当 state 是 “Ohio” 或 “Michigen”,显示 Brutus(很可爱的logo)
根据以上需求,代码修改为:

Event.observe(window, “load”, function() {
Event.observe($(“country”), “change”, function() {
var country = $F(“country”);
if (country == “United States”) {
$(“us-state-field”).show();
$(“province-field”).hide();
} else if (country == “Canada”) {
$(“province-field”).show();
$(“us-state-field”).hide();
} else {
$(“us-state-field”).hide();
$(“province-field”).hide();
}
});

Event.observe($(“us-state”), “change”, function() {
var state = $F(“us-state”);
if (state == “Ohio” || state == “Michigan”)
$(“brutus”).show();
else
$(“brutus”).hide();
});

});

新程序使用先前的事件处理和字段检查方式,尝试扩展程序以满足新的需求。然而这样有几个问题,第一个问题这个代码有两个 bug:1)页面初始载入时所有字段都显示出来了,2)隐藏 state 字段不会,不会自动隐藏 Brutus。后者可称为多米诺(domino)bug,因为如果有其他元素依赖于动态显示和隐藏的字段会导致级联影响。

第二个问题是代码的可读性。不容易立即看出这段代码想达到的目的,因而需要很仔细的检查是否遗漏或者不严格符合需求。当需求发生变化,很希望准确地知道代码什么地方需要修改,当作了修改后,需要有信心确信不会影响其它功能。元编程概念就是用于解决这个问题。

Pragmatic Programmer(实用主义程序员?) 把元编程描述为江代码中的细节抽出来放到元数据中。元数据通常为配置文件或者其他的数据源,当然元数据也可以是可执行代码,只要把“what”(是什么)和“how”(如何做)清晰分离开。该方法的思路是使用易于描述问题论域的词汇来编写代码。问题论域即要描述的“what”。这种类型语言称为领域专用语言(domain specific language),缩写为 DSL。在上面例子中,“what”是显示和隐藏 form 中字段的规则。目前的实现是把规则和规则的实现耦合在一起。随着需求变化和特性增加,代码会变得越来越不清晰。如果能够将这些规则抽出来,规则修改不再依赖于实现,会减少应对需求变化的苦恼。如果使用一种比 JavaScript 更接近问题论域的语言来描述规则,会将问题大幅简化。

编写 DSL 可以从考虑描述业务领域使用的词汇入手。这些词汇有时会因为描述人角色不同而不同。例如程序员、用户、业务分析员就会各不相同。在作出上述假设前,可以考虑一个问题:为什么这些不同角色要使用不同词汇呢?不同词汇在沟通中导致信息失真。通过找到在各组人之间通用的词汇,可以消除信息传递,也就可避免信息失真。这种公共的词汇通常可以在需求说明书中找到:

show us-state when country is “United States”
show province when country is “Canada”
show Brutus when state is “Ohio” or “Michian”
DSL 应该尽可能与上述描述一致。

有可能直接使用上述描述。上述描述并不是可执行的 JavaScript 代码,但可以放在文本文件中,使用 JavaScript 读出来进行解析。这种方法虽然可行,但可能过于复杂。如果使用纯文本作为 DSL,它需要和英语同样的灵活性。但我们希望把它作为数据,它需要遵循一些严格的规则。如果 DSL 本身就是可执行代码,可以将问题简化。不足的一面是非程序开发人员可能不会使用这种 DSL。实际上让非开发人员写DSL也是不现实的目标。如果程序元写好了,业务人员能够看懂或者能够修改,就很不错了。怎样处理例子中的需求,把它转成可执行代码?这可能是最难的一步了,具有很强的主管性。在 JavaScript 中,有两个建议:把方法串起来组成类似于句子的结构,灵活的使用方法名。沿着这个思路,可将上面的业务规则描述如下:

show(“us-state-field”).when(“country”).is(“United States”);

/>show(“province-field”).when(“country”).is(“Canada”);
show(“brutus”).when(“us-state”).is(“Ohio,Michigan”);

方法名“when”和“is”本身表意不完整,但在 DSL 上下文中,这些方法串起来,能够表意很好。可以看出 show() 返回一个对象,该对象具有方法 when();when() 返回一个对象,该对象具有方法 is()。下面给出能够描述第一条规则的简单实现:

function show(fieldToDisplay){
return {
when : function(field){
return {
is: function(value){
if($F(field) == value){
$(fieldToDisplay).show();
}
else{
$(fieldToDisplay).hide();
}
}
}
}
};
};

完整代码如下:

<html>
<head>
<title>Metaprogramming JavaScript – Example 1</title>
<script src=”prototype.js”></script>
<script type=”text/javascript” charset=”utf-8″>
function show(fieldToDisplay){
return {
when : function(field){
return {
is: function(value){
if($F(field) == value){
$(fieldToDisplay).show();
}
else{
$(fieldToDisplay).hide();
}
}
}
}
};
};

Event.observe(window, “load”, function() {

Event.observe($(“country”), “change”, function() {
show(“us-state-field”).when(“country”).is(“United States”);
});

});

</script>
</head>

<body>
<form>
<p id=”country-field”>
<label for=”country”>Country</label>
<select id=”country”>
<option>United States</option>
<option>Canada</option>
<option>Somewhere Else</option>
</select>
</p>

<p id=”us-state-field”>
<label for=”state”>State</label>
<select id=”us-state”>
<option>Ohio</option>
<option>Michigan</option>
<option>Kentucky</option>
</select>
</p>
</form>
</body>
</html>

以上只是一个简单示意,在后续文章中会给出完整的实现。

京ICP备05053527号
经过26次查询历时3.735秒终于生成了此页面
Powered by WordPress & Designed by Felix © 2008