半年前,因为VPS未续费导致所有数据丢失,直至今日终于重新恢复了所有的文章数据(虽然丢失了全部的评论),并且借此机会对所有文章进行了一次重新审视,修改了部分问题,并将所有示例迁移到jsfiddle和jsperf上,总算造一段落。
新的博客完全独立建设,不使用任何第三方的CMS系统,后端使用ASP.NET MVC实现,数据库使用MySQL,通过Mono部署于Ubuntu Server之上,前端使用nginx作为静态服务器。
也正因为完全独立构建,不受任何系统出于安全、简便等奇怪理由而附加的限制,这个博客系统也成了自己练手的娱乐场。就比如本篇要介绍的OPOA化实践。
概念
OPOA,全称One Page One Application,中文可以称之为单页应用。
顾名思义,在OPOA下,一个页面组成一个应用,不再以传统的超链接跳转导航的方式,而是通过javascript以XMLHttpRequest加载数据,通过DOM操作展现数据。
作为一个单页应用,其优势主要有:
- 多个页面拥有相同的结构时,一些相同的内容(如侧边栏、LOGO等)不需要重复加载,节省流量(及一定的数据库查询)。
- 没有浏览器跳转地址导致的短暂空白页面状态,提升用户体验。
- 可以增加过渡效果(如渐隐、渐显等),进一步提升体验。
听闻最近由于老赵在其Wind.js关于eval这一函数使用上与BYVoid进行了一系列的争论,甚至为此写了一篇博客来证明eval对性能的影响可以忽略这一观点。
而不幸由于在这一次讨论中,起于Ericpoon_智对eval性能影响的疑问,也不知不觉参与了一些讨论,随后_Franky提出要有数据上的事实来说明这一问题,于是在好久没有新的博客出产的时候,决定对这一问题进行一下非常浅薄的挖掘,提供一些客观的数据。
首先在此文中,需要声明的是:
- 个人倾向于不要计较
eval产生的影响。
- 此文会从
eval有负面影响这一角度进行表述和举例,与老赵的博客属于完全相反的观点,但并不是一种对其的驳斥或者反对,仅仅是希望可以提供一些客观的理论依据。
- 本文只谈V8引擎,由于这一问题是不同引擎的表现会大不相同,不可能完全覆盖,因此数据不可作为权威参考。
- 本人不懂C或C++,看不懂V8,因此完全是黑盒的推断,各结论也不具权威意义,完全可能是臆测,当然数据保证真实。
以下是本次测试使用的Chrome版本详细信息:
- Google Chrome: 21.0.1180.79 (Official Build 151411) m
- OS: Windows
- WebKit: 537.1 (@124502)
- JavaScript: V8 3.11.10.18
- Flash: 11.3.31.227
- User Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1
这是近期一次内部分享的PPT,从函数的相关概念上,主要内容有:
- 变量的概念。
- 闭包的表象。
- 核心概念,包括可执行代码、执行环境、词法环境、变量环境、环境数据、绑定对象等。
- 函数的相关过程,包括创建函数、进入函数、定义绑定初始化、变量查找等。
- 闭包对垃圾回收的影响的探究。
PPT权当对新事物的一种普及性展示,不会有深入的内容,也不会有详细的例子,如果有任何疑问,欢迎随时交流。
如需观看或下载,请点击此处。
本文的诞生,源自近期打算做的一个关于javascript中的闭包的专题,由于需要解析闭包对垃圾回收的影响,特此针对不同的javascript引擎,做了相关的测试。
为了能从本文中得到需要的知识,看本文前,请明确自己知道闭包的概念,并对垃圾回收的常用算法有一定的了解。
问题的提出
假设有如下的代码:
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
console.log('inner');
};
}
var inner = outer();
在这一段代码中,outer函数和inner函数间会形成一个闭包,致使inner函数能够访问到largeObject,但是显然inner并没有访问largeObject,那么在闭包中的largeObject对象是否能被回收呢?
如果引入更复杂的情况:
function outer() {
var largeObject = LargeObject.fromSize('100MB');
var anotherLargeObject = LargeObject.fromSize('100MB');
return function() {
largeObject.work();
console.log('inner');
};
}
var inner = outer();
首先一个显然的概念是largeObject肯定不能被回收,因为inner确实地需要使用它。但是anotherLargeObject又能不能被回收呢?它将跟随largeObject一起始终存在,还是和largeObject分离,独立地被回收呢?
继续大逆不道系列……
在上一篇中,提出了一个另类的MVC模型,与经典MVC模型有一些不同,那么自然需要描述这样的另类模型有什么优势,又能在怎么样的场景中使用。
逻辑划分
正如上一篇所说,这种模式下,最大的优势莫过于逻辑的清晰划分。在该模式的作用下,每一个Action都只要处理真正与自己有关的逻辑及数据,而不需要关心一些“通用”的内容,因为这些通用内容也成了独立的Action。
例如,继续引用上一篇中的页面设计,根据经典的MVC模式,我们不得不在一个Action中准备所有数据:
public ActionResult ViewPost(int id) {
ViewBag.Friends = FriendsRepository.ByUser(CurrentUser);
ViewBag.RecentVisitors = VisitorsRepository.ByPost(id, TimeSpan.FromMinutes(30));
Post post = PostRepository.ById(id);
return View(post);
}
即便你按上一篇提到的方案使用Action Filter,依旧没有办法彻底地将这些数据从当前Action产生的数据模型中消除,当模块越来越多时,各种“通用”数据一起扔在ViewBag中,造成臃肿、冲突,没有清晰的文档更加大了维护的难度。
而如果使用修改后的MVC模型,每一个Action都只照顾自己相关的逻辑:
这是一篇大逆不道的文章,其作用就是供大家娱乐以及批斗,因为此文所提及的思想,试图改变现有的著名模式MVC的结构,因此如果认为MVC优秀甚至完美的话,还请直接忽略此文,以免影响心情。
本文将提出一种类似MVC但又不完全是现有的经典MVC的模式,该模式仅基于HTTP的Web系统中对经典的MVC模式进行改造,其特点是将View前置,通过View的切分来切分逻辑,形成多次M-V-C交互,最终生成响应。
经典MVC模式
对于经典的MVC模式,虽然从表面上看完全是个“不需要解释”的问题,但是每个人的理解又不尽相同。在我的理解中,MVC模式可以用下面这张图来表达:

基于HTTP的Web系统里,在经典的MVC模式中,一个请求的处理过程大致分为:
- Controller处理原始请求,根据请求的数据与系统的配置,寻找到真正处理该请求的逻辑,称之为Action。
- Action处理请求提交的数据,与Model进行交互,获取需要反馈的数据,并将这些数据按View的要求组装后交给View。
- View根据Action递交的数据,组装为用户可识别的视图模板,并通过响应传递到客户端进行渲染。
遭遇问题
这个模式一直工作得非常好,我也完全没有理由反驳他存在的意义和优势,直到有一天我发现我的页面有了些奇怪的需求:
时隔N久,继续生产,年底实在忙……
在上一篇发表之后,有网友指出这个沙箱过于复杂,事实上一些更简单的代码也能完成同样的效果,例如:
var proxy = {
document: new DocumentProxy()
};
function(window) {
// 开发者提交的代码
}.call(proxy, proxy);
通过call和函数参数来劫持this及window这两个指向全局对象的变量,以达到屏蔽全局对象的目的。
但是这个方式虽然简单易懂,却有一个非常重要的环节无法照顾到,即全局环境下LexicalEnvironment和VariableEnvironment和ThisBinding均是全局对象,这一特性意味着在全局环境下执行的代码将同时满足以下4个条件:
- 通过
var x = 3;定义的变量,可以使用console.log(x);输出。
- 通过
this.x = 3;定义的属性,可以使用console.log(this.x);输出。
- 通过
this.x = 3;定义的属性,可以使用console.log(x);输出。
- 通过
var x = 3;定义的变量,可以使用console.log(this.x);输出。
其中第1点和第2点是任何环境下的代码都可以满足的,因此通过call和函数参数来支持全局对象没有问题。但是第3和第4点正是全局环境的特征,并不能简单地通过单层的函数代码来控制。而通过一个with语句则可以得到满足第3点,而第4点则需要一些tricky的手段来实现了,本文将会重点从标准出发来阐述这2点的推理过程。
上一章中,经过对标准的查询、翻阅、解析,终于在绕了一个大圈之后,明白了全局对象的特征,并明确了模拟一个全局对象需要的内容,还为此编写了简单的测试用例。本文将从第1个测试用例着手,放眼与全局作用域的VariableEnvironment、ThisBinding以及window别名这三者的一致性,来推导出一个沙箱的原型。
所谓纰漏
在开始这一章以前,首先不得在这里道个歉,原因很简单,我对标准进行了误读,且在没有测试的情况下就开始了这一系列的博客。事实上,以我的思路,是无法制作出一个理想中的沙箱的,虽然可以控制住第三方代码的作用区域,但是无法做到完全的透明,其主要问题出在这里:
var x = 3;
console.log(this.x); // 打印出3
事实上,仅仅依靠Javascript本身,是没有办法在非全局环境下使上面这段代码得到正确的结果的。
因此,在这里,不得不先把标准给改上一改,放弃绝对透明性这一要求,事实上也确实很少有开发者会通过var声明变量并使用this.x或window.x来访问,不得以之下只能放弃这个需求,将var声明和this.x声明隔离开来。
事实上(开始狡辩),对标准的误读也是学习标准的一种过程,不会犯错的人也很难有太大的进步。我思考良久,最终的决定是不删除这一系列的博文,而是使之继续下去,在修改测试用例的情况下,继续来制作沙箱。当然这个难度要简单不少了。
这一系列已然变成了一个“边研究边写”的“研究笔记”,随着这一系列的进行,必然会出现越来越多的问题,事实上这个沙箱也不见得有可用性。还请以“标准对研究进行指导”这样的角度来看待这个问题吧,得出一个“无角”这样的结论,姑且也算一个结论……
理解VariableEnvironment和ThisBinding
依旧沿用上一章的思路,打开ECMAScript 5的HTML版本,通过CTRL+F组合键打开搜索框,查询VariableEnvironment,可以看到搜索的结果并不多,同样搜索ThisBinding,则发现多数内容与VariableBinding的搜索结果重叠,说明这两者经常放在一块描述。
上一章中对需求进行了分析,并对实现方案有了一定的探讨,最终得出从劫持全局对象这一环节入手的结论,而本文将讨论如何劫持这个全局对象。
全局对象,多数会进行Javascript编程的工程师都知道,在浏览器的执行环境中,叫做window,虽然事实上window和全局对象并不严格意义上是一个东西,不过这对我们的实现并不会造成什么障碍。
剖析全局对象
既然要去做劫持全局对象这样不人道的事,自然先要知道全局对象是个什么东西,有什么样的特点,以便针对目标,一刀致命。这个时候,标准就会成为我们最好的参考,首先打开ECMAScript 5的HTML版本,此后几乎所有的标准引用都会从这里产生。
所谓擒贼先擒王,经典理论告诉我们,不可以把整个洋洋洒洒数万字的标准给全看了,必须以快、狠、准的手段找到我们需要的东西。如果是纸质的书籍,对于一个不熟悉文档结构的人而言自然是无处下手,但好在现在是数字化的时代,我们有一个名为“搜索”的强大功能。
在浏览器中按下CTRL+F的组合键,随后输入Global,看看一共有多少内容。从目录来看,一共有7处内容,确实不多:
- 10.2.3 The Global Environment
- 10.4. Entering Global Code
- 15.1 The Global Object
- 15.1.1 Value Properties of the Global Object
- 15.1.2 Function Properties of the Global Object
- 15.1.4 Constructor Properties of the Global Object
- 15.1.5 Other Properties of the Global Object
而这8处内容又可以分为2部分,其中1-3分别介绍了全局对象本身的特点,而4-7则专注于介绍全局对象上的内置属性(即本地对象)的相关内容。仅仅以劫持全局对象为目的的我们,自然可以先不管全局对象上的其它属性,只针对全局对象自身入手,因此将目光放置在前3者之上。
目的
这将是一个系列的文章,大致由4篇博文组成,内容应该会是这样的:
- 目的、背景及分析
- 劫持全局对象
- 劫持全局对象属性
- 存在的问题及漏洞
起草这一系列博文的原因有两点,第一自然是因为最近突然有这样的需求,而群里正好也在某个深夜(技术宅基本深夜活动)讨论到了一个类似的话题,给了我一些灵感,所以决定将近期“研究”的成果展示一下。
而第二个目的,也是我想将这么一个简单的东西(其中最终就10几行代码)用整整5个博文的篇幅来说的原因,是因为近期有不少朋友向我强烈地表达了标准的存在意义极其微弱,我能把代码写好就足够的想法,而且现在更是催生了一种叫做jQuery工程师的职位,大有星星之火的样子。
对于这样的态度,作为一个只会理论不懂实践的2B工程师,个人自然是坚持地抱以反击态度的,因此,这一系列的博文,将会从“构建一个沙箱”这样的场景出发,将对于这一话题的研究过程展现于众,期间将频繁涉及到对标准的参阅,以展现标准如何引导问题的解决这一事实。
在阅读这一系列的博文前,还请确认几件事:
- 你懂英文,因为我不是翻译专家,没能力也没篇幅将标准相关的内容都翻译一下。对于标准,我会将相关的原文引用于此,但肯定是英文的。
- 你得抱着标准无用论来看待问题,如果你也觉得标准很好用,那还是看最后的成品代码吧,因为其中的过程于你已经失去了大部分的意义。
最后,还是先声明一件事,其实我研究这个问题并没有这么麻烦,因为本系列博文中引用标准的内容几乎全在我的脑子里。我只是将这个思考、查找、得出结论的过程放大、放慢,进行分解,一点一点地通过文字表达出来而已。至于类似“根本不知道标准又怎么会往这方面想”的问题如果出现在你的大脑中,那么恭喜你似乎已经意识到了标准学习的些许价值。