发布时间:March 10, 2010
分类:JavaScript
经常有朋友邮件过来咨询技术问题,本人不才经常不是忘记回复就是回复的比较迟。原因是一来本人比较懒(或者可以托词为忙),二来是怕“误人子弟”。
当回复这位仁兄的邮件时,不觉得就发现回复的字数越来越长,于是就将其整理为篇 Blog。我的意图是不仅仅帮忙解决问题,而且能提供个解决类似问题的思路。
邮件原文
来自: Captain
时间: 2010-02-28 20:41
话题: 新手虚心求教
你好,我是刚刚入门学习Javascript的新手。我想向你请教一下我在学习过程中所
遇到的一个问题。恕我愚昧,但是我一直无法理解,恳请赐教。
我在学习中碰到过一条这样的声明: var !myVariable != variableValue, 因为在
以前的学习中知道双重否定等于可定,那这则声明是否可以理解成 var myVariable =
variableValue ?
若非如此解释,恳求正解。冒昧打扰请多多包涵。我的回复
不好意思,收到你的来信到现在才回复。第一眼看见您提供的代码,个人觉得它不合乎语法规则
var !myVariable != variableValue
根据 ECMA 相关的文档,var 关键字的定义在 12.2 章节(我看的是第五版),它的语法定义是:
var VariableDeclarationList ;
而 VariableDeclarationList 包括
VariableDeclarationList , VariableDeclaration
上述两个定义又包含多种情况,说起来有些复杂,所以我画了张草图(可能不准确,以文档为准):

所以,上面的表达式不符合 var 语句的定义要求,因此 Javascript 解析器会报语法错误。
如果是非赋值定义语句,也就是
!myVariable != variableValue
那么就是个条件判断语句,其中有两个条件判断操作符“非(!)”以及“不等于(!=)”,那么我们就要了解它们的结合优先级。
根据相关的参考文档,我们可以得知“非”操作符的优先级比“不等于”要高,因此语句先计算 !myVariable 这块,然后再计算“不等于”后的值。
至于能否改成其他等同的逻辑情况,这里还有个问题就是值取“非”的时候会有多种情况,这里有个讨论:
明城:
问: !a != b 是否等于 b == !!a
崇厚:
这个还要看数据类型吧?
比较运算符的优先级是最低的
渔隐:
照这样推理就是a == b 了
崇厚:
a = ''
b = 0
console.log(!a != b)
console.log(b == !!a)
true true
明城:
关键还有个“非”操作符,所以 B 始终是和布尔值对比的
崇厚:
我试出来三个结果
t t
t f
f f
痴灵:
var a = 0;
console.log(!a);console.log(typeof(!a),typeof(a));
true boolean number
类型发生了变化了
渔隐:
用!== 和===吧
明城:
不纠结了,以后记得逻辑表达式里千万不要加一元操作符
或者加括号
沉鱼:
有个比较有效的做法,不太记得操作符的优先级时,我都
加(),这样虽然比较挫,不过保险
崇厚:
不管记得不记得优先级,表达式比较复杂时最好都加括号
这样看起来清晰一些
举一反三,以后如果再碰到类似的问题,我们可以:
- 查文档。ECMAScript 规范、MDC 中的文档、甚至 Google 都是我们的好帮手
- 如果还是无法理解,可以“偷懒”先看运行结果,然后逆向推断
- 团队的力量胜过一个人闭门造车
-- EOF --
发布时间:February 4, 2010
分类:JavaScript
这是工作记录,慢慢的不知不觉就形成篇文章了,就发在这里吧。
-- Split --
acookie 就是段统计代码,它的原理就是发送个 GET 请求到统计服务器,并附上本地用户的浏览器类型、分辨率等其他信息。
问题
有人报告 acookie 的代码出现 XSS,原理是利用某 GET 参数 bypass 未过滤完全的字符。这里是原先的代码摘录:
(function() {
function akrand(num) {
return Math.floor(Math.random() * num) + 1
}
var P = location.pathname;
if ((parent === self) || P.indexOf('{...}') != - 1 || P.indexOf('{...}') != - 1) {
var R = escape(document.referrer);
var id = "" + akrand(9999999) + akrand(9999999);
var title = escape(document.title);
document.write('<img src="http://foo/1.gif?acookie_load_id=' + id + '&title=' + title + '&pre=' + R
+ '¶m0={...}¶m1={...}¶m2={...}" width="0" height="0" style="display:none;" />');
}
})();这段代码“年事已高”每次的修修补补都是治标不治本,于是干脆考虑重写这段统计代码。
目标
在开始重写之前,为将要写的代码列了几条目标:
- 流量方面的考虑,代码要尽可能的简短
- 尽可能的优化性能
- “绿色”,不干扰页面的其他脚本
- 脚本尽可能得做到安全
思考
- 使用原先的 document.write 会在页面中加入 DOM,直接使用 new Image 更好
- 原先的判断条件很冗余,完全可以考虑个函数搞定
- 安全方面虽然可以写个简短的过滤函数,但这样代码又会很长
解决
下面是反复修改后的最终代码:
(function(){
var M = function(n) {
return Math.floor(Math.random() * n) + 1;
},
I = function() {
for (var i = 0, P = location.pathname, args = arguments, len = args.length; i < len; i++) {
if (P && P.indexOf(args[i]) !== -1) {
return 1;
}
}
return 0;
},
D = document, R = escape(D.referrer), S = screen, T = escape(D.title);
if (parent === self || I('{...}') || I('{...}')) {
try {
return new Image().src = [
"http://foo/1.gif?cache=" + M(9999999),
'&pre='+ R +'&scr='+ S.width +'x'+ S.height + '&title=' + T,
'¶m0=' + '{...}',
'¶m1=' + '{...}',
'¶m2=' + '{...}'
].join('');
} catch (e) {}
}
})();本来想使用 ~function() {}; 这样的闭包,结果发现效率方面还是原先的理想(当然不会差很多),加之可能以后阅读的同事会有困扰,还是采用原先的吧。
在这个案例中,其实返回 0 和 1 同比返回 false 和 true 是同样的道理,处于节省代码量考虑,直接使用整型值。
安全方面出于简单原则考虑,没有考虑使用转义函数,而是采用数组拼贴的方式,这样就算有注入也会造成语法报错而不能指定此段脚本,前面加 return 也是这样考虑。
后记
考虑以后的代码可维护性很重要,这个例子中代码虽然简单,当然还会有更极端的写法,不过处于以后同事的合作考虑,尽量不要写得过于的“专业”。
采用 document.write 的考虑是想直接使用脚本在页面上输出,因此在本案例中不是很适用,同时才用这一方法在安全的角度上考虑需要过滤的字符太多,因此如果了解需求(比如仅仅是生成个带参数的 URL)还是避免使用它
XSS 和 CSRF 等虽说后台处理是治本的,但如果使用不当前后台考虑不周全就算加入了对应的过滤函数,也有可能造成注入。也从另个角度考虑,有时候完善的代码也能从一定程度上加大注入的难度,尤其是 Javascript 等这种“每个人都可以看见得脚本”。
-- EOF --
发布时间:January 3, 2010
分类:JavaScript
这是 小马 在交流会中的分享,可能有些朋友还不曾了解,同时也为了自己温故而知新,就整理下。
多种方式
可以想像得到,有很多方法立即执行匿名函数,简单的整理就有下面三种的典型方式(还有其他方式的,欢迎告知):
方式一
(function() {
// …
})();方式二
void function() {
// …
}();方式三
~function() {
// …
}();方式一 和 方式二 大家可能都看到过,这里主要说明下 方式三。在说明 方式三 之前,我们现回顾下运算符“~”(位取反)在 EMCAScript 中的定义(第五版的第 72 页),简单的翻译下:
- 按运算符结合语句
- 将旧值转换为 32 位整型
- 执行运算符后的语句
- 转换之行结果为 32 位整形并返回
从上面可以了解,其实位运算符都能立即返回后面表达式的值。其实其他位运算符都可以达到这样的目的,例如
!function() {
// …
}();等都可以达到我们的目的。所以其实用“~”也并无其他的原因,仅仅是代码“看着好看”而已 :^)
效率
与其说三种方式执行匿名函数的效率,到不如直接分析个运算符的执行效率。那么我们对比再来看看“()”以及“void”在 EMCA 规范中的定义
组运算符(第五版 66 页)
- 返回表达式的执行结果
void(第五版 70 页)
- 按运算符结合语句
- 执行
- 返回 undefined
由于组运算符还需要执行语句并返回语句块返回的值,对比 void 会多个获取语句块的操作(虽然消耗不了多少的性能),因此在这种情况下 void 的性能要优于组运算符。
对比两者,那么 方式三 的性能对比很明显要低于前两者。总结起来就是光从语法的角度上分析,在上面三者匿名函数的方式中 方式二 优于 方式一,方式三 的效率最低。
总结
思考下三者之间的优异
- 方式一的写法很常见而且很保险,所以并不会有人因为你使用这种方式而责怪你
- 但使用方式一的同学,可能经常会犯忘记匹配括号的“毛病”(特别是语句块很长时,就经常搞错)
- 使用位运算符执行匿名函数很新潮,用来装
逼酷不错
- 很多 IDE (如 IDEA)以及语法高亮工具不支持 方式三 的写法
- void 效率最好,但总感觉相比其他两种实现方式非常的臃肿(仅仅多几个字符?)
那么
- 综合代码量以及效率等情况考虑,用 方式一 没错的
- 在需要额外节省代码的极端情况,用 方式三
- 考虑效率优先,那么使用 方式二
这里要说明下,其实三种方式效率之间的差异非常小。因此单纯拿效率来考虑采用那种方式,几乎是站不住脚的。
具体采用何种方案,需要根据实际情况来考虑。比如我自己,经常会使用方式三,是因为 1、方便(加一个字符即可) 2、在函数长的情况下匹配括号会很头晕 3、用起来很酷,但 方式三 会经常让看我代码的组员感到困扰。
如果在些类库等框架性质的基础代码,用 方式一 最保险同时大家都容易看懂,是最保险的选择。
附
看见 阮一峰 的 Blog 上有篇文章说避免使用 void 运算符。其实在 Javascript 中, void 的使用方面还是比较常见的。
例如,我们会强制让某些调用返回 undefined 以阻止浏览器等默认行为(我们经常看到的就比如很多 Javascript Bookmark 前面就经常会加上 void 运算符)。
所以,还是“物尽其用”吧。
-- EOF --
- 1
- 2
- 3
- 4
- ...
- 24
- »