- 浏览: 950828 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
sscsacdsadcsd:
mike8625 写道react还要自己的一些标签 还得编译 ...
对于React体系的一点想法 -
mike8625:
说的都是给大公司听的,国内很多还是小公司,做个小项目, 说实话 ...
关于国内前端和JS技术发展的乱想 -
mike8625:
react还要自己的一些标签 还得编译 编译吧浏览器端说还慢 ...
对于React体系的一点想法 -
u012814086:
下意识想到了Golang
JavaScript语句后应该加分号么? -
xueduanyang:
我是水羊,年轻的时候觉得只要有好斧子就能做成好产品,各种产品都 ...
关于国内前端和JS技术发展的乱想
在上一篇帖子中,我讨论了Peter提出的Lazy Function Definition Pattern,我指出了这个pattern并不能带来性能的提升,而所使用的closure也有可能造成内存泄漏。
当然内存泄露在任何closure中都可能存在。因此仅仅从不必要的使用closure加大了内存泄漏的风险这一点来说,说服力可能并不强。而且,在之后的回复中,FCKEditor的FredCK给出了一个不使用closure的改进方案。
但是我仍然觉得哪里不妥。
经过一些思考,我终于意识到关键的坏味道,其实就在于对函数变量的重新赋值。
我们知道纯FP是不带有副作用的,因此不可能允许函数名称与实际代码的重新绑定。所以正如我之前的回复所指出的,这个Lazy Definition模式,其实一点也不functional。
那么让我们看看,副作用有什么危害吧!
首先是Peter的样例代码:
假设一位倒霉的开发者John以前有自己写过一个类似getScrollY的函数,但是名字叫做getPageYOffset,用在他自己的页面代码中。现在他知道了Peter的getScrollY,他听说Peter这个版本兼容性更好、性能更高,于是决定采用Peter的版本代替自己的版本。同时他又不想对原有代码做过多改动。于是很自然的,他打算在html head部分引入Peter的脚本,然后后面加上一句:
getPageYOffset = getScrollY;
John特地写了一个testcase,测试这样做的效果。当然,testcase肯定能通过。
但是,大家都知道这样做的结果了,getPageYOffset指向的是原初的getScrollY,因此每次调用getPageYOffset都等于是第一次调用getScrollY。这样非但不会有假想中的节约一次条件判断,反而会而反复的对getScrollY变量重新赋值。性能肯定大大下降。
这还不算,还可能更糟。
考虑FredCK的版本:
对于这个版本,John一样做了testcase,调用了一次getPageYOffset,没问题,又通过了。
然而当John实际使用的时候,会发生什么?大家可以推导一下。记住,真实应用中,getPageYOffset可能被调用很多次。
所以,这就是我为什么不喜欢这个Pattern,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
没有看到,有备份吗?
当然内存泄露在任何closure中都可能存在。因此仅仅从不必要的使用closure加大了内存泄漏的风险这一点来说,说服力可能并不强。而且,在之后的回复中,FCKEditor的FredCK给出了一个不使用closure的改进方案。
但是我仍然觉得哪里不妥。
经过一些思考,我终于意识到关键的坏味道,其实就在于对函数变量的重新赋值。
我们知道纯FP是不带有副作用的,因此不可能允许函数名称与实际代码的重新绑定。所以正如我之前的回复所指出的,这个Lazy Definition模式,其实一点也不functional。
那么让我们看看,副作用有什么危害吧!
首先是Peter的样例代码:
var getScrollY = function() { if (typeof window.pageYOffset == 'number') { getScrollY = function() { return window.pageYOffset; }; } else if ((typeof document.compatMode == 'string') && (document.compatMode.indexOf('CSS') >= 0) && (document.documentElement) && (typeof document.documentElement.scrollTop == 'number')) { getScrollY = function() { return document.documentElement.scrollTop; }; } else if ((document.body) && (typeof document.body.scrollTop == 'number')) { getScrollY = function() { return document.body.scrollTop; } } else { getScrollY = function() { return NaN; }; } return getScrollY(); }
假设一位倒霉的开发者John以前有自己写过一个类似getScrollY的函数,但是名字叫做getPageYOffset,用在他自己的页面代码中。现在他知道了Peter的getScrollY,他听说Peter这个版本兼容性更好、性能更高,于是决定采用Peter的版本代替自己的版本。同时他又不想对原有代码做过多改动。于是很自然的,他打算在html head部分引入Peter的脚本,然后后面加上一句:
getPageYOffset = getScrollY;
John特地写了一个testcase,测试这样做的效果。当然,testcase肯定能通过。
但是,大家都知道这样做的结果了,getPageYOffset指向的是原初的getScrollY,因此每次调用getPageYOffset都等于是第一次调用getScrollY。这样非但不会有假想中的节约一次条件判断,反而会而反复的对getScrollY变量重新赋值。性能肯定大大下降。
这还不算,还可能更糟。
考虑FredCK的版本:
var getScrollY = function() { if (typeof window.pageYOffset == 'number') return (getScrollY = getScrollY.case1)(); var compatMode = document.compatMode; var documentElement = document.documentElement; if ((typeof compatMode == 'string') && (compatMode.indexOf('CSS') >= 0) && (documentElement) && (typeof documentElement.scrollTop == 'number')) return (getScrollY = getScrollY.case2)(); var body = document.body ; if ((body) && (typeof body.scrollTop == 'number')) return (getScrollY = getScrollY.case3)(); return (getScrollY = getScrollY.case4)(); }; getScrollY.case1 = function() { return window.pageYOffset; }; getScrollY.case2 = function() { return documentElement.scrollTop; }; getScrollY.case3 = function() { return body.scrollTop; }; getScrollY.case4 = function() { return NaN; };
对于这个版本,John一样做了testcase,调用了一次getPageYOffset,没问题,又通过了。
然而当John实际使用的时候,会发生什么?大家可以推导一下。记住,真实应用中,getPageYOffset可能被调用很多次。
所以,这就是我为什么不喜欢这个Pattern,因为开发者有了这样的负担:必须了解这个函数会否悄悄的变化,理解这种副作用可能带来的问题。
评论
8 楼
kaize
2011-09-27
Hax你好,我是周六W3ctech的一名听众:)
关于这个lazy loading Func我有些不同的意见:
其实lazy loading func“概念本身”的确是对函数的执行进行了优化,比如在<Pro Javscript for web developer 2nd Edition>中18。Advanced Tech一章就用到了这个pattern来创建XHR。
另:<pro javascript design patterns>第8章也有类似实现
综上,个人认为,lazy loading func这种pattern本身对于性能的优化是可以肯定的,而对其的使用,是有选择性的。而且lazy loading func Pattern恰恰体现了js的灵活性。
关于这个lazy loading Func我有些不同的意见:
其实lazy loading func“概念本身”的确是对函数的执行进行了优化,比如在<Pro Javscript for web developer 2nd Edition>中18。Advanced Tech一章就用到了这个pattern来创建XHR。
function createXHR(){ if (typeof XMLHttpRequest != “undefined”){ createXHR = function(){a return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != “undefined”){ createXHR = function(){ if (typeof arguments.callee.activeXString != “string”){ var versions = [“MSXML2.XMLHttp.6.0”, “MSXML2.XMLHttp.3.0”, “MSXML2.XMLHttp”]; for (var i=0,len=versions.length; i < len; i++){ try { var xhr = new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; return xhr; } catch (ex){ //skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { createXHR = function(){ throw new Error(“No XHR object available.”); }; } return createXHR(); }
另:<pro javascript design patterns>第8章也有类似实现
综上,个人认为,lazy loading func这种pattern本身对于性能的优化是可以肯定的,而对其的使用,是有选择性的。而且lazy loading func Pattern恰恰体现了js的灵活性。
7 楼
netfishx
2007-08-19
hax 写道
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。
没有看到,有备份吗?
6 楼
hax
2007-08-19
Peter同志好像把我在他blog上最后一个comments删除了,令我有点失望啊。
5 楼
netfishx
2007-08-18
看了好几天了,说下我个人的感觉:peter的例子举的不好,没有体现出lazy的作用。但在特定场景下,这种模式是有用处的。至于边界条件确实应该说明出来,lazy模式不是放之四海而皆准的。
所谓functional更多的只是起一个吸引眼球的作用,这个模式根本不沾边,不必理睬。
所谓functional更多的只是起一个吸引眼球的作用,这个模式根本不沾边,不必理睬。
4 楼
hax
2007-08-18
所以这是考验lib开发者的。
我们有几种方式来解决问题。
第一个是给多一点选择,例如jquery,为了和其他类库的$共存,而提供了另外的api。
第二个是遵循社区的约定。例如private成员用下划线作为前缀。
第三个是使用某种良好的设施。例如jsi和pies,能帮助不同类库和平共处。
第四个是避免对用户的不必要约束,特别是对于比较trivial的事情,不能强加给用户约束。这是所谓unobstrusive javascript。
下面回过头看看这个问题。
对于function的不变性,绝大多数用户会有不自觉的隐含认可。因为function调用的意义是明确的,不变的。(有明确说我这个函数就是经常变化的例子么?)基于此,用户不会预期函数对象本身的变化。
注意,用户可以接受初始化这样一件事情。这是很正常的。但是用户为什么要接受,第一次调用与以后调用的差别呢!所以问题就在这里。调用函数是一件trivial的事情,不应该强迫用户了解调用之间的差别,这与用户的目标毫无关系。况且你就是告诉用户第一次调用和以后调用的差别,也不代表他们能理解这种副作用对于他们代码的影响。因此出现我说的那种情况的话,用户就会陷入迷茫。
你可以争辩说我所做的这个例子的假设会不会发生,有多大概率发生。
Ok,本身这就是一种权衡。我的看法是,附加这样一种对用户的假设所付出的代价,却没有得到什么明显的好处。所以我认为这是一种antipattern。
我们有几种方式来解决问题。
第一个是给多一点选择,例如jquery,为了和其他类库的$共存,而提供了另外的api。
第二个是遵循社区的约定。例如private成员用下划线作为前缀。
第三个是使用某种良好的设施。例如jsi和pies,能帮助不同类库和平共处。
第四个是避免对用户的不必要约束,特别是对于比较trivial的事情,不能强加给用户约束。这是所谓unobstrusive javascript。
下面回过头看看这个问题。
对于function的不变性,绝大多数用户会有不自觉的隐含认可。因为function调用的意义是明确的,不变的。(有明确说我这个函数就是经常变化的例子么?)基于此,用户不会预期函数对象本身的变化。
注意,用户可以接受初始化这样一件事情。这是很正常的。但是用户为什么要接受,第一次调用与以后调用的差别呢!所以问题就在这里。调用函数是一件trivial的事情,不应该强迫用户了解调用之间的差别,这与用户的目标毫无关系。况且你就是告诉用户第一次调用和以后调用的差别,也不代表他们能理解这种副作用对于他们代码的影响。因此出现我说的那种情况的话,用户就会陷入迷茫。
你可以争辩说我所做的这个例子的假设会不会发生,有多大概率发生。
Ok,本身这就是一种权衡。我的看法是,附加这样一种对用户的假设所付出的代价,却没有得到什么明显的好处。所以我认为这是一种antipattern。
3 楼
radar
2007-08-18
我明白你的意思。
但是假设和规范 是两码事。
例如:var是javascript的关键字,你不能乱用。ok这是规范。
$对prototype.js 这是规范
这些是明确要 语言,类库使用者遵守的规定。
假如你选择mootools,你的类必须按照它提供的方式创建。你还不能随便乱增加方法。... ...
如果选择jQuery,你的dom不能有 $event属性。
等等,其实这是个矛盾,你提到的边界很难把握,人与人之间这个点也是不同的。
但是假设和规范 是两码事。
例如:var是javascript的关键字,你不能乱用。ok这是规范。
$对prototype.js 这是规范
这些是明确要 语言,类库使用者遵守的规定。
假如你选择mootools,你的类必须按照它提供的方式创建。你还不能随便乱增加方法。... ...
如果选择jQuery,你的dom不能有 $event属性。
等等,其实这是个矛盾,你提到的边界很难把握,人与人之间这个点也是不同的。
2 楼
hax
2007-08-18
To radar:
你明白程序员的职责么?代码质量的很重要的一条,就是程序员对于边界情况的处理。
你明白类库开发者的职责么?类库开发者很重要的一条,就是不要对类库的用户作出过多的限制。除非这种限制是在nontrivial的事情上。
因此不是我的假设太多,而是这个antipatterns本身的假设太多。
你明白程序员的职责么?代码质量的很重要的一条,就是程序员对于边界情况的处理。
你明白类库开发者的职责么?类库开发者很重要的一条,就是不要对类库的用户作出过多的限制。除非这种限制是在nontrivial的事情上。
因此不是我的假设太多,而是这个antipatterns本身的假设太多。
1 楼
radar
2007-08-18
你的假设太多。
发表评论
-
论ES6模块系统的静态解析
2013-03-14 04:56 15683本文是Dave Herman的《Stati ... -
如何创建一个JavaScript裸对象
2012-08-27 02:11 7909所谓裸对象,即 naked object ,是指没有原型(sp ... -
JavaScript语句后应该加分号么?
2012-06-19 03:10 14157这是一个老生常谈的问 ... -
shim是应该抛异常还是应该fail silently?
2011-08-11 17:26 5476玉伯发布了es5-safe模块 ... -
7月30日的广州演讲视频和Slides
2011-08-01 23:38 31117月30日在W3CTech广州站活动上的演讲,题目是:ECMA ... -
如何判断一个函数的意图是被用作构造器,也就是可视为“类”
2011-07-21 13:55 2890前提是不要求做什么特殊标记。只是最大可能的猜测函数的作用大概是 ... -
关于国内前端和JS技术发展的乱想
2011-07-19 18:53 33024玉伯在我的一条微博后面写了一些(和主题不是很相关但)非常值得思 ... -
Module与Trait的比较
2011-08-12 12:50 3888最近我多次提及module和trait。 粗看,我们可以发现 ... -
如何将let结构(block scope)转换到当前的JavaScript代码
2011-07-12 17:24 2945本文是对如何将let结构转换到ES3代码的补充。 首先,原文 ... -
JavaScript的未来方向之观察
2011-07-12 02:53 8312最近每次去杭州,都有 ... -
我为什么力挺NodeJS
2011-07-04 00:27 0之前在参加CNodeJS社区在 ... -
JS之父再谈JS历史(续完)
2010-12-31 04:20 3370又到年底,我觉得是时候还债了。自开blog来,我出了不少“太监 ... -
我为什么是DC黑续,兼答Tin
2010-04-27 14:29 0我同意安全是一个重要问题。我不同意的是把所谓安全放到凌驾于其他 ... -
我为什么是DC黑─Why I disagree with Douglas Crockford
2010-04-26 17:51 10920参加完了QCon北京大会, ... -
写对正则:一行代码,速度差50倍
2009-05-12 03:43 59532009-05-11 A lesson of ... -
JavaScript的EOS(分号)问题
2009-05-08 16:24 5725在http://bbs.51js.com/viewthre ... -
JavaScript五大关键字
2009-05-06 17:53 4708近期做语法高亮项目的副产品,是统计了一下几个主流JS工具包中各 ... -
curry和partial的差别
2009-03-28 00:15 366751js上asfman翻译了http://ejohn.org/ ... -
Eval is Evil , With evil too
2009-03-27 18:39 0with的问题: 2009-3-27 17:12:40 ... -
IE全局变量的Dissociative Identity Disorder(人格分裂症)
2009-03-16 02:47 14670最近,小麦提出了一个疑惑: 小麦 写道最后介绍一个我也搞不明白 ...
相关推荐
用jump cpu指令改写函数的头几个字节,该指令会转移到替换函数的内存地址,(替换函数必修和被hook的函数标记完全相同),当被hook的函数被调用时,jump指令实际上将转移到替换函数上执行,实现hook的目的。...
js写的ajax核心构造和改写alert函数.
使用Thunk技术改写窗口类回调函数为窗口对象成员函数。 具有介绍看 http://chzup.blog.163.com/blog/static/10771319201133014637408/
第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。, 如果您是开发...
第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。, 如果您是开发...
无副作用。 2。高阶函数。 3。延迟计算 而最最有意义的(至少我认为如此),是基于高阶函数的函数组合能力。一些人把这叫做glue。 简短地说,什么让函数式编程如此强大?是用简单的函数组合出复杂函数的能力。 我...
strcmp 函数重定义, 改写函数, 字符串逆序输出单词
在将MATLAB程序改写成C语言的过程中,用到了如标签所示的fft、ifft等函数,不局限在上面几个函数,还有一些相关的函数在里面
Delphi源码免杀之(自删除)函数改写过卡巴高启发
该函数通过改写沙维老师CS_OMP算法,将其改写成函数的形式,便于直接调用
详细介绍了各种由传递函数转状态空间的各种方法,各位可以根据需要自由选取
matlab中smooth函数,改写成C/C++语句
第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。 如果您是开发...
第5~12章是提高部分,讲解了正则表达式、分析函数、树形查询及汇总函数的用法。这部分知识常用于对一些复杂需求的实现及优化改写。最后两章介绍日常的优化改写案例。这部分是前面所学知识的扩展应用。, 如果您是开发...
面向对象程序设计C++详细实验报告 含代码,结果 实验要求: (1) 将数据成员改为私有的; (2) 将输入和输出的功能改为由成员函数实现; (3) 在类体内声明成员函数,而在类外定义成员函数。
将函数cvvHist1D改写成用折线方式绘制直方图。方法是将线段方式中每条线段的上端点连接起来。
中国科学院大学 电子学院 机器学习与应用作业gmms改写