Windows 下 Phantomjs 及 chromedriver 安装

很多时候,我们在Node模块测试的时候需要用到 phantomjs 和 chromedriver 等工具模块。但是npm的镜像内部指定的是github的下载路径(chromedriver 是 google的地址,根本没办法下载了),总是在安装时下载phantomjs-***-windows.zip 失败。

我尝试用 指定taobao 的npm 镜像的方式,用镜像地址安装。

然而我window下安装的时候,还是会发现下载失败。我看了下phantomjs 模块的install.js 源码。

发现他是可以指定压缩包的cdn地址的:

chromedriver 同理:

 

 

另外提供一个傻瓜方案供参考,可以直接下载好压缩包后解压安装。

Phantomjs :

首先到taobao的镜像地址:https://npm.taobao.org/mirrors/phantomjs

下载好指定版本的安装包后,进到系统的:C:\Users\{管理员账号}\AppData\Local\Temp 目录下找到 phantomjs 把我们下载好的安装包:phantomjs-2.1.1-windows.zip  拷贝进去。

接下来,进入安装目录下的node_modulejs/phantomjs 目录直接运行:

如果正常的话,系统检测到安装包会直接解压然后执行后续的配置。

 

从零开始学RactiveJS 系列(一)

这个系列,主要是把ractive 的一些常规用法,以及编码规范来做一个系统的讲解。

希望通过本系列可以让对ractive 感兴趣的同学。有一个初步的认识。

一、前言

RactiveJS是一个模板驱动的UI库,融入了很多Backbone的元素。但又与Backbone不同,它有自己的Two-way binding引擎。
而Backbone 却需要插件支持。
Two-way binding (双向绑定) 也就是 大家常说的 MVVM 或者 MV* 什么的。
这个,它不是跟 angularjs 类似了?

二、Get started

官方给了个 “60 second setup” 教程。 简短的一段代码描述了RactiveJS 引擎的运行。
ps:目前最新稳定版是 0.7 ,后面的内容都是基于这个版本的

我们可以用官方提供的CDN来引入基础库文件

然后跟大多数框架类似,我们需要一个模板。RactiveJS支持,字符串,或者以script text 类型的容器。

最后就是初始化代码。至此,一个基本的RactiveJS Demo 就完成了。

  • el 可以是一个选择器,或者Element元素。 指示将要运行引擎的容器。
  • template 可以是一个选择器,一段文本。或RactiveJS (Ractive.parse())预编译的模板对象。
  • data  可以理解为,需要绑定的数据源。 在data里面指定的变量,可以随意的在template里面使用。

 

RactiveJS 内置了基本的选择器模块,Template引擎,封装的Event,Node等对象。

所以如果没有特别的需求,我们几乎可以不用jQuery 这类框架了。


 

点到为止。

 

后面几个阶段,我们就RactiveJS的配置项,内置API及用法做详细说明。

Ractive.js – components

好长一段时间都在研究一些奇怪的前端框架,为的是找到适合自己的一套前端解决方案。

直到遇到ractive.js , 这是一款国外的JS大牛(Rich-Harris)写的。

因为有研究过angularjs。知道了“脏值检测”,才真正对这系列的框架感兴趣,也就是大家都叫的MVVM,Two-way binding等。

 

这个文章不讲怎么写Ractivejs的基础代码,而是来说说components 组件的写法。

Shadow DOM API 我想做过JS的都有了解过。

template,css,js  当我们把这些以往死了都要拆开的东西又放到一堆。就有了Shadow DOM Components 的意思了。

Ractivejs有提供的rvc.js工具 就很好的实现了这种想法。另外值得一提的是,google做这个算是做到了极致(请移步:http://www.polymer-project.org)

基于Ractivejs + rvc.js 结合下 Shadow DOM 的思想。就有了

这段代码看似很简单。但是很好的归纳了HTML组件的几个要素。在Shadow DOM 不怎么流行,或说受限于浏览器版本的时候。

我们可以用这种方式很好的来组织我们的组件代码。

 

使用同样让人很愉悦

或者这样

看到这些代码,大家的顾忌肯定是,这不就是一种规范嘛,肯定是需要预编译的。浏览器怕是吃不销。

然而退一步说,我们要的就是一种规范,也许我就是想让我一个写HTML代码的人,能用这种规则来写组件。

比起写成片的JS代码,看着要直接的多。

剩下的事情,让Ractivejs 配合 Nodejs 做个及时编译。感觉效果还是挺美的。

 

点到为止,看完你可能对这几个字眼感兴趣。

http://www.ractivejs.org/

https://github.com/ractivejs/rvc

https://www.polymer-project.org/

 

So… 下次再聊Ractivejs 的入门吧。

时间分割函数

工作需要,写了个时间分割的 小函数。

传入上班时间段 , 午休时间段和分割区间 即可 得出所有从开始到结束的时间分割区间。

使用方法:

 

JSDOC3 学习笔记

参考链接
http://www.36ria.com/5101
http://blog.csdn.net/wts/article/details/19117837
http://www.blogjava.net/JAVA-HE/archive/2008/11/25/242477.html?opt=admin
http://www.cnblogs.com/hxling/archive/2012/11/27/2791067.html

安装及配置
官方网站:https://github.com/jsdoc3/jsdoc/

建议不要直接通过“Download ZIP”功能直接下载,而是到Release中下载稳定版本,比如3.2.2

下载后,直接解压到某个目录,比如d:\jsdoc

然后设置windows环境变量,在path中增加d:\jsdoc,这样我们在那个目录下,都可以执行jsdoc命令了。当然还有个前提,就是windows环境变量中已经把jdk配置好了。

假设要对d:\js\myjs.js生成jsdoc文档,则:

1、打开dos窗口

2、在dos窗口中执行命令:

view plaincopy在CODE上查看代码片派生到我的代码片

回车后,会在d:\js目录下生成一个out文件夹,里面就是html形式的doc了。

如果要对js目录中所有js文件生成文档,则执行:

view plaincopy在CODE上查看代码片派生到我的代码片

如果要对js目录中的个别文件生成文档,则执行:

view plaincopy在CODE上查看代码片派生到我的代码片

如果要指定生成文档的目录,则可以在命令行后继续增加参数: -d d:\out
这个时候,jsdoc是使用的默认模板生成的jsdoc,效果不是很理想,我们需要找到好一点的模板。

执行jsdoc –help,回车,我们会看到该命令支持的所有参数。

jsdoc的完整官方文档:http://usejsdoc.org/

执行jsdoc –debug,回车,我们会看到一个Java窗口程序,好像是Rhino的,不懂,这个窗口跟调试有关。

执行命令生成doc 文档

 

gulp下使用,jsdoc插件(gulp-jsdoc

grunt 下面有jsdoc 插件,最近弄gulp 也顺便搜了下。发现gulp版本的 jsdoc 也有了。

github地址:https://github.com/jsBoot/gulp-jsdoc

具体使用方法也比较傻瓜。
首先,安装

用法

js里 mousewheel 那些事

之前也有知道鼠标的滚轮事件,但是今天有以前的同事问到我,让我给你个demo。
我尝试用jQuery 来写了下,发现居然在firefox 下居然没效果。

呵呵 🙂

搜了下资料,原来是firefox 下面 mousewheel 不支持这个;
firefox 下面要用 DOMMouseScroll 代替。更多差异请见表。

以上输出差异见下面(IE7, IE10, Chrome, 以及FireFox,鼠标向下滚动, win7)(可点击此页面单独查看表格内容):引用

属性名\浏览器 FireFox Chrome IE10 IE7
recordset ×没有该属性 ×没有该属性 ×没有该属性 null
type DOMMouseScroll mousewheel mousewheel mousewheel
fromElement ×没有该属性 null null null
toElement ×没有该属性 [object HTMLDivElement] null null
altLeft ×没有该属性 ×没有该属性 ×没有该属性 false
keyCode ×没有该属性 0 ×没有该属性 0
repeat ×没有该属性 ×没有该属性 ×没有该属性 false
reason ×没有该属性 ×没有该属性 ×没有该属性 0
data ×没有该属性 ×没有该属性 ×没有该属性 空字符串
behaviorCookie ×没有该属性 ×没有该属性 ×没有该属性 0
source ×没有该属性 ×没有该属性 ×没有该属性 null
contentOverflow ×没有该属性 ×没有该属性 ×没有该属性 false
behaviorPart ×没有该属性 ×没有该属性 ×没有该属性 0
url ×没有该属性 ×没有该属性 ×没有该属性 空字符串
dataTransfer ×没有该属性 null ×没有该属性 null
ctrlKey false false false false
shiftLeft ×没有该属性 ×没有该属性 ×没有该属性 false
dataFld ×没有该属性 ×没有该属性 ×没有该属性 空字符串
returnValue ×没有该属性 true ×没有该属性 undefined
qualifier ×没有该属性 ×没有该属性 ×没有该属性 空字符串
wheelDelta ×没有该属性 -120 -120 -120
bookmarks ×没有该属性 ×没有该属性 ×没有该属性 null
actionURL ×没有该属性 ×没有该属性 ×没有该属性 空字符串
button 0 0 0 0
srcFilter ×没有该属性 ×没有该属性 ×没有该属性 null
nextPage ×没有该属性 ×没有该属性 ×没有该属性 空字符串
cancelBubble false false false false
x ×没有该属性 799 876 839
y ×没有该属性 283 322 325
buttonID ×没有该属性 ×没有该属性 ×没有该属性 0
srcElement ×没有该属性 [object HTMLDivElement] [object HTMLDivElement] [object]
screenX 934 799 876 841
screenY 453 344 377 382
srcUrn ×没有该属性 ×没有该属性 ×没有该属性 空字符串
origin ×没有该属性 ×没有该属性 ×没有该属性 空字符串
boundElements ×没有该属性 ×没有该属性 ×没有该属性 [object]
clientX 1168 799 876 841
clientY 456 283 322 327
propertyName ×没有该属性 ×没有该属性 ×没有该属性 空字符串
shiftKey false false false false
ctrlLeft ×没有该属性 ×没有该属性 ×没有该属性 false
offsetX ×没有该属性 791 868 829
offsetY ×没有该属性 275 314 310
altKey false false false false
initMouseWheelEvent ×没有该属性 ×没有该属性 function initMouseWheelEvent() { [native code] } ×没有该属性
layerX 1168 799 876 ×没有该属性
layerY 456 283 322 ×没有该属性
which 1 1 1 ×没有该属性
buttons 0 ×没有该属性 0 ×没有该属性
metaKey false false false ×没有该属性
pageX 1168 799 876 ×没有该属性
pageY 456 283 322 ×没有该属性
relatedTarget null null null ×没有该属性
getModifierState function getModifierState() { [native code] } ×没有该属性 function getModifierState() { [native code] } ×没有该属性
initMouseEvent function initMouseEvent() { [native code] } function initMouseEvent() { [native code] } function initMouseEvent() { [native code] } ×没有该属性
detail 3 0 0 ×没有该属性
view [object Window] [object Window] [object Window] ×没有该属性
initUIEvent function initUIEvent() { [native code] } function initUIEvent() { [native code] } function initUIEvent() { [native code] } ×没有该属性
bubbles true true true ×没有该属性
cancelable true true true ×没有该属性
currentTarget [object HTMLBodyElement] [object HTMLBodyElement] [object HTMLBodyElement] ×没有该属性
defaultPrevented false false false ×没有该属性
eventPhase 3 3 3 ×没有该属性
isTrusted true ×没有该属性 true ×没有该属性
target [object HTMLDivElement] [object HTMLDivElement] [object HTMLDivElement] ×没有该属性
timeStamp 14296937 1366106275177 1366106216522 ×没有该属性
initEvent function initEvent() { [native code] } function initEvent() { [native code] } function initEvent() { [native code] } ×没有该属性
preventDefault function preventDefault() { [native code] } function preventDefault() { [native code] } function preventDefault() { [native code] } ×没有该属性
stopImmediate
Propagation
function stopImmediate
Propagation() { [native code] }
function stopImmediate
Propagation() { [native code] }
function stopImmediate
Propagation() { [native code] }
×没有该属性
stopPropagation function stopPropagation() { [native code] } function stopPropagation() { [native code] } function stopPropagation() { [native code] } ×没有该属性
AT_TARGET 2 2 2 ×没有该属性
BUBBLING_PHASE 3 3 3 ×没有该属性
CAPTURING_PHASE 1 1 1 ×没有该属性
webkitDirection
InvertedFromDevice
×没有该属性 false ×没有该属性 ×没有该属性
wheelDeltaY ×没有该属性 -120 ×没有该属性 ×没有该属性
wheelDeltaX ×没有该属性 0 ×没有该属性 ×没有该属性
webkitMovementY ×没有该属性 0 ×没有该属性 ×没有该属性
webkitMovementX ×没有该属性 0 ×没有该属性 ×没有该属性
charCode ×没有该属性 0 ×没有该属性 ×没有该属性
clipboardData ×没有该属性 undefined ×没有该属性 ×没有该属性
initWebKitWheelEvent ×没有该属性 function initWebKitWheelEvent() { [native code] } ×没有该属性 ×没有该属性
NONE 0 0 ×没有该属性 ×没有该属性
MOUSEDOWN 1 1 ×没有该属性 ×没有该属性
MOUSEUP 2 2 ×没有该属性 ×没有该属性
MOUSEOVER 4 4 ×没有该属性 ×没有该属性
MOUSEOUT 8 8 ×没有该属性 ×没有该属性
MOUSEMOVE 16 16 ×没有该属性 ×没有该属性
MOUSEDRAG 32 32 ×没有该属性 ×没有该属性
CLICK 64 64 ×没有该属性 ×没有该属性
DBLCLICK 128 128 ×没有该属性 ×没有该属性
KEYDOWN 256 256 ×没有该属性 ×没有该属性
KEYUP 512 512 ×没有该属性 ×没有该属性
KEYPRESS 1024 1024 ×没有该属性 ×没有该属性
DRAGDROP 2048 2048 ×没有该属性 ×没有该属性
FOCUS 4096 4096 ×没有该属性 ×没有该属性
BLUR 8192 8192 ×没有该属性 ×没有该属性
SELECT 16384 16384 ×没有该属性 ×没有该属性
CHANGE 32768 32768 ×没有该属性 ×没有该属性
rangeParent [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性
rangeOffset 0 ×没有该属性 ×没有该属性 ×没有该属性
isChar false ×没有该属性 ×没有该属性 ×没有该属性
mozMovementX 1168 ×没有该属性 ×没有该属性 ×没有该属性
mozMovementY 576 ×没有该属性 ×没有该属性 ×没有该属性
mozPressure 0 ×没有该属性 ×没有该属性 ×没有该属性
mozInputSource 1 ×没有该属性 ×没有该属性 ×没有该属性
initNSMouseEvent function initNSMouseEvent() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性
axis 2 ×没有该属性 ×没有该属性 ×没有该属性
initMouseScrollEvent function initMouseScrollEvent() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性
originalTarget [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性
explicitOriginalTarget [object HTMLDivElement] ×没有该属性 ×没有该属性 ×没有该属性
preventBubble function preventBubble() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性
preventCapture function preventCapture() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性
getPreventDefault function getPreventDefault() { [native code] } ×没有该属性 ×没有该属性 ×没有该属性
RESET 65536 ×没有该属性 ×没有该属性 ×没有该属性
SUBMIT 131072 ×没有该属性 ×没有该属性 ×没有该属性
SCROLL 262144 ×没有该属性 ×没有该属性 ×没有该属性
LOAD 524288 ×没有该属性 ×没有该属性 ×没有该属性
UNLOAD 1048576 ×没有该属性 ×没有该属性 ×没有该属性
XFER_DONE 2097152 ×没有该属性 ×没有该属性 ×没有该属性
ABORT 4194304 ×没有该属性 ×没有该属性 ×没有该属性
ERROR 8388608 ×没有该属性 ×没有该属性 ×没有该属性
LOCATE 16777216 ×没有该属性 ×没有该属性 ×没有该属性
MOVE 33554432 ×没有该属性 ×没有该属性 ×没有该属性
RESIZE 67108864 ×没有该属性 ×没有该属性 ×没有该属性
FORWARD 134217728 ×没有该属性 ×没有该属性 ×没有该属性
HELP 268435456 ×没有该属性 ×没有该属性 ×没有该属性
BACK 536870912 ×没有该属性 ×没有该属性 ×没有该属性
TEXT 1073741824 ×没有该属性 ×没有该属性 ×没有该属性
ALT_MASK 1 ×没有该属性 ×没有该属性 ×没有该属性
CONTROL_MASK 2 ×没有该属性 ×没有该属性 ×没有该属性
SHIFT_MASK 4 ×没有该属性 ×没有该属性 ×没有该属性
META_MASK 8 ×没有该属性 ×没有该属性 ×没有该属性
SCROLL_PAGE_UP -32768 ×没有该属性 ×没有该属性 ×没有该属性
SCROLL_PAGE_DOWN 32768 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_UNKNOWN 0 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_MOUSE 1 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_PEN 2 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_ERASER 3 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_CURSOR 4 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_TOUCH 5 ×没有该属性 ×没有该属性 ×没有该属性
MOZ_SOURCE_KEYBOARD 6 ×没有该属性 ×没有该属性 ×没有该属性
HORIZONTAL_AXIS 1 ×没有该属性 ×没有该属性 ×没有该属性
VERTICAL_AXIS 2 ×没有该属性 ×没有该属性 ×没有该属性

顺便推荐下jQuery mousewheel 插件https://github.com/brandonaaron/jquery-mousewheel

所以上面的代码要改进了

这样一来,就能兼容forefox了。

另外值得一提的是,我们要获取滚轮的delta。有说法是获取 event.wheelDelta 属性
但是我尝试了多次,在我的firefox下得到的都是 undefined 。后来看了下资料,发现wheelDelta不是标准的.
所以在很多情况下是获取不到 wheelDelta。
有篇讨论这个问题的帖子,可以看看http://stackoverflow.com/questions/8886281/event-wheeldelta-returns-undefined
引用Rob W的回复:

The event object in a jQuery event handler does not reflect the real event. wheelDelta is a non-standard event property IE and Opera, available through the originalEvent property of the jQuery event.

In jQuery 1.7+, the detail property is not available at the jQuery Event object. So, you should also use event.originalEvent.detail to for this property at the DOMMouseScroll event. This method is backwards-compatible with older jQuery versions.

event.originalEvent.wheelDelta

Demo: http://jsfiddle.net/eXQf3/22/
See also: http://api.jquery.com/category/events/event-object/

这里有个解决方案可以用:

后面还发现浏览器间对于 delta的值也是有所不同的。所以使用的时候,最好根据实际情况做处理。

jQuery Deferred对象API详解及DEMO

【转】:http://www.2fz1.com/?p=474

jQuery在1.5开始引入deferred(延迟),简单说,deferred对象就是jQuery的回调函数解决方案。

jQuery1.5中,Deferred对象提供一种方式来注册多个回调,添加到自已管理的回调队列中,调用适当的回调队列,并转达同步或异步函数的成功或失败状态。
deferred对象有三种执行状态:未完成(pending),已完成(resolved)和已失败(rejected)

API概览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
deferred object = {
    always(alwaysCallbacks [, alwaysCallbacks])
    //延迟对象不管成功或失败都最终会执行该方法
 
    done(doneCallbacks)
    //延迟对象成功完成后调用
 
    fail(failCallbacks)
    //延迟对象失败后调用
 
    isRejected()
    //确定延迟对象是否已失败
 
    isResolved()
    //确定延迟对象是否已成功
 
    notify( args )
    //用来触发一个自定义事件fireEvent
 
    notifyWith(context, [args])
    //跟notify一样,但可以指定上下文
 
    pipe([doneFilter] [, failFilter] [, progressFilter] )
    //jQuery的1.8,deferred.pipe()方法已经被淘汰。
    //用deferred.then()替代
 
    progress( progressCallbacks )
    //用来监控函数执行过程,进度处理程序,见:notify 方法
 
    reject([args])
    //使延迟对象的状态变为失败,对应的回调函数绑定方法为fail。
 
    rejectWith(context, [args])
    //使用方法与reject一样,但是可以指定上下文,使用可以参考 resolveWidth
 
    resolve([args])
    //使延迟对象的状态变为成功,对应的回调函数绑定方法为done。
 
    resolveWith(context, [args])
    //使用方法与resolve一样,但是可以指定上下文
 
    state()
    //查询延迟对象的状态,有三种:pending resolved rejected
 
    then(doneCallbacks, failCallbacks [, progressCallbacks])
    //一种缩写,用法与done,fail一样
 
    promise([target])
    //在原来的deferred对象上返回另一个deferred对象,
    //这个新的deferred对象屏蔽了改变状态的方法。
}

入门应用:

1
2
3
$.ajax("test.php")
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("出错啦!"); });

从1.5开始,ajax对象不再返回xhr对象,而是deferred对象。通过.done执行成功回调,通过.fail执行失败回调。
1、$.when
返回一个Deferred对象,允许你为多个事件指定一个回调函数。

1
2
3
$.when($.ajax("test.php"),$.ajax("test2.php"))
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("出错啦!"); });

这是一个与操作,只有test.php和test2.php都成功时才会执行done,否则执行fail;

2、resolve和reject的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var def = $.Deferred(); //创建deferred对象
var readConf = function(){
    fs.readFile('test.txt','utf-8', function(err, data) {
        if (err) {
           def.reject(); //改变deferred对象状态成失败
        } else {
           def.resolve(); //改变deferred对象状态成成功
        }
    });
    return def;  //返回
}
 
$.when(readConf())
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("出错啦!"); });

3、promise用法
例2中,定义的def为全局变量,在作用域范围内,可以通过def.reject()或def.resolve()来改变其本身的状态,这样影响def的安全性,promise在原来的deferred对象上返回另一个deferred对象,这个新的deferred对象屏蔽了改变状态的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var def = $.Deferred(); //创建deferred对象
var readConf = function(){
    fs.readFile('test.txt','utf-8', function(err, data) {
        if (err) {
           def.reject(); //改变deferred对象状态成失败
        } else {
           def.resolve(); //改变deferred对象状态成成功
        }
    });
    return def.promise();  //通过promise返回deferred对象,起到状态保护作用
}
 
$.when(readConf())
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("出错啦!"); });
 
def.reject(); //更改无效

4、notify和progress用法
两者一般结合使用,有点自定义事件的意思。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var def = $.Deferred(); //创建deferred对象
var readConf = function(){
    def.notify('start');
    fs.readFile('test.txt','utf-8', function(err, data) {
        if (err) {
            def.reject(); //改变deferred对象状态成失败
            def.notify('fail');
        } else {
            def.resolve(); //改变deferred对象状态成成功
            def.notify('success');
        }
    });
    return def;
}
 
$.when(readConf())
.progress(function(_event){
    console.log(_event); //打印start,fail 或 start,success
    //_event是def.notify参数中指定的自定义事件名,是一个字符串
    //此处可以跟据不同事件进行相应的逻辑处理
});

5、then用法

1
2
$.when(readConf())
.then(function(){ alert("成功了!"); },function(){ alert("出错啦!"); });

Cutting 头像上传,截图组件 JS端

最近公司项目有需求,所以把一个imgareaselect插件进行了二次封装。并且在基础上完善了部分功能。最后就成了一个上传组件。
部分功能还会继续完善,后续再更新。
这就不啰嗦了,直接放代码了
HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="select_imgb" id="crop-wrap">
    <form data-action="imgupuload.do" method="post" enctype="multipart/form-data" target="imgifr" data-dom="form">
        <input type="text" name="x" data-dom="x"/>
        <input type="text" name="y" data-dom="y"/>
        <input type="text" name="w" data-dom="w"/>
        <input type="text" name="h" data-dom="h"/>
        <input type="text" name="u" data-dom="u"/>
        <iframe class="upload-iframe" id="imgifr" name="imgifr" src="about:blank"></iframe>
        <div class="clearfix">
            <div class="upload-btn">
                <input class="file" data-dom="file" name="file" type="file"/>
                <a href="javascript:void(0);"><span><span>选择头像</span></span></a>
            </div>
        </div>
        <p class="mt_5">仅支持上传大小3MB以内,JPG、PNG、GIF、BMP格式的图片</p>
        <div class="crop-box clearfix">
            <div class="crop-box-left">
                <div class="crop-img-adapt" data-dom="adapt">
                    <img src="./crop/crop2.jpg" alt="" data-dom="crop"/>
                </div>
            </div>
            <div class="crop-box-right">
                <div class="crop-img-small">
                    <img src="./crop/crop2.jpg" alt="" data-dom="small"/>
                </div>
                头像预览
            </div>
        </div>
    </form>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/* crop ui */
.upload-btn {
    display: block;
    float: left;
    margin-right: 5px;
    overflow: hidden;
    width: 68px;
    height: 25px;
    position: relative;
}

.upload-btn input {
    position: absolute;
    height: 25px;
    width: 68px;
    opacity: 0;
    cursor: pointer;
}

.upload-iframe {
    display: none;
}

.crop-box {
    width: 360px;
}

.crop-img-small {
    overflow: hidden;
    padding: 0;
    width: 96px;
    height: 96px;
    text-align:center;
    border: 1px solid #cccccc;
}

.crop-box-left {
    float: left;
}

.crop-box-right {
    float: right;
}

.crop-img-adapt {
    width: 256px;
    height: 248px;
    border: 1px solid #cccccc;
}

.crop-img-adapt img{
    max-width: 100%;
    max-height: 100%;
}

.clearfix:after {
    content: "\0020";
    display: block;
    height: 0;
    clear: both
}

.clearfix {
    _zoom: 1
}

*+html .clearfix {
    overflow: hidden
}

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/**
 * 截图上传控件
 *
 * development by Ryan
 * mail: zengyi.zal@gmail.com
 *
 * useing jQuery JavaScript frame v1.6.1+
 */

;;;(function ($) {
    /**
     * 构造函数
     * @constructor
     */

    var Cutting = function () {
        this.initialize.apply(this, arguments);
    }
    /**
     * 扩展原型
     * @type {{constructor: Function, initialize: Function, create: Function, position: Function, select: Function, update: Function, setCrop: Function, preview: Function, imgReady: null}}
     */

    Cutting.prototype = {
        //构造函数
        constructor: Cutting,
        //初始化函數
        initialize: function (element, options) {
            var t = this;
            this.element = $(element);
            this.options = $.extend(true, {
                SIZE_MID: 96.0,
                maxWidth: 256.0,
                maxHeight: 248.0,
                drag: false,
                initcallback: function () {
                },
                dblcallback: function () {
                }
            }, options);

            this.maxWidth = this.options.maxWidth;
            this.maxHeight = this.options.maxHeight;
            this.SIZE_MID = this.options.SIZE_MID;
            this.dblclick = this.options.dblcallback;

            //初始化dom
            this.element.find("[data-dom]").each(function (i, o) {
                var dom = $(o), domName = dom.data("dom");
                domName && (t[domName] = dom);
            });
            this.action = this.form.data("action") || "";
            this.form.attr("action", this.action);
            this.file.change(function (e) {
                t.select(this);
            });
            this.options.drag && this.create(this.options.initcallback);
        },
        /**
         * 自适应图片,并创建控件
         * @param img
         * @param afterAdapt
         */

        create: function (img, afterAdapt) {
            var t = this;
            if (typeof img === "function") {
                afterAdapt = img, img = null;
            }
            img = img || t.crop[0];
            //预先设置图片不可见,以防出现闪烁的情况。
            img.style.visibility = 'hidden';
            var maxHeight = this.maxHeight, maxWidth = this.maxWidth;
            var aspect = maxWidth / maxHeight;

            //利用imgReady提前获取图片大小自适应大小
            t.imgReady(img.src, function () {
                var w = this.width, h = this.height, adaptW, adaptH;
                var picAspect = w / h;

                if (w <= maxWidth && h <= maxHeight) {
                    adaptW = w;
                    adaptH = h;
                } else {
                    if (picAspect > aspect) {
                        adaptW = w > maxWidth ? maxWidth : w;
                        adaptH = adaptW / w * h;
                    } else {
                        adaptH = h > maxHeight ? maxHeight : h;
                        adaptW = adaptH / h * w;
                    }
                }
                img.width = adaptW;
                img.height = adaptH;
                img.style.visibility = 'visible';
            }, function () {
                t.position();
                if (t.cropApi) {
                    t.update(img);
                } else {
                    t.setCrop(img, img.width, img.height, t.options.initcallback);
                }
                typeof afterAdapt === 'function' && afterAdapt.call(img, img.width, img.height);
            }, function () {
                window.console && console.log("图片加载失败!");
            });
        },
        /**
         *  设置图片位置
         */

        position: function () {
            var t = this, maxWidth = this.maxWidth, maxHeight = this.maxHeight;
            var crop = t.crop, width = crop.width(), height = crop.height();
            console.log([maxWidth, maxHeight, width, height]);
            crop.css({
                marginLeft: (width < maxWidth) ? (maxWidth - width) / 2 : 0,
                marginTop: (height < maxHeight) ? (maxHeight - height) / 2 : 0
            });
        },
        /**
         * 选中文件后
         * @param fileInput
         */

        select: function (fileInput) {
            var t = this;
            var files = fileInput.files;
            if (files) {
                //ff and chrome
                if (!files.length) {
                    return;
                }
                //遍历files并处理
                for (var i = 0; i < files.length; i++) {
                    var file = files[i];
                    var imageType = /image.*/;
                    //通过type属性进行图片格式过滤
                    if (!file.type.match(imageType)) {
                        continue;
                    }
                    standardHandle(file);
                }
            } else {
                //ie
                ieHandle();
            }

            function ieHandle() {
                submitForm(t.form, {'flag': 0}, 'avatarUploadHandle', function (resp) {
                    if (resp) {
                        var info = resp["var"];
                        if (resp.code === 'S_OK') {
                            var path = avatarUrl + info;
                            t.crop.attr("src", path);
                            t.small.attr("src", path);
                            t.u.val(info);
                            t.create();
                        } else {
                            info = info || '未知错误';
                            tip(info, 2);
                        }
                    }
                });
            }

            function standardHandle(file) {
                //读入文件
                var reader = new FileReader();
                reader.onload = function (e) {
                    var imgData = e.target.result;
                    t.crop.attr("src", imgData);
                    t.small.attr("src", imgData);
                    t.create();
                }
                reader.readAsDataURL(file);
            }
        },
        /**
         * 更新选区
         * @param img
         */

        update: function (img) {
            var crop = this.cropApi,width = img.width,height = img.height,
                initValue = (width > height ? height : width) > this.SIZE_MID ? (width > height ? height : width) / 2 : width;

            crop.setSelection((width - initValue) / 2, (height - initValue) / 2, (width + initValue) / 2, (height + initValue) / 2, true);
            crop.setOptions({ show: true });
            crop.update();
            this.preview(img, crop.getSelection());
        },
        /**
         * 设置截图控件
         * @param img
         * @param width
         * @param height
         * @param afterInit
         */

        setCrop: function (img, width, height, afterInit) {
            //截图区域偏移值
            var initValue = (width > height ? height : width) > this.SIZE_MID ? (width > height ? height : width) / 2 : width;

            //初始化截图控件
            this.cropApi = $(img).imgAreaSelect({
                keys: true,
                fadeSpeed: 200,
                handles: true,
                aspectRatio: '1:1',
                parent: this.adapt,
                onInit: $.proxy(this.preview, this),
                onSelectChange: $.proxy(this.preview, this),
                ondblclick: $.proxy(this.dblclick, this),
                persistent: true,
                imageWidth: width,
                imageHeight: height,
                instance: true,
                x1: (width - initValue) / 2,
                y1: (height - initValue) / 2,
                x2: (width + initValue) / 2,
                y2: (height + initValue) / 2
            });

            //初始化完成后的回调
            typeof afterInit === 'function' && afterInit.call(img, width, height);
        },
        //预览图生成
        preview: function (img, selection) {
            if (!selection.width || !selection.height) {
                return;
            }
            var SIZE_MID = this.SIZE_MID;
            var w = selection.width;
            var h = selection.height;
            var iw = img.width;
            var ih = img.height;
            var ox = selection.x1;
            var oy = selection.y1;

            var getMargin = function (size, pos, limit) {
                return -Math.round(limit * pos / size);
            };

            var getSize = function (size, isize, limit) {
                return Math.round(limit * isize / size);
            };

            var setCss = function (elem, limit) {
                elem && elem.css({
                    width: getSize(w, iw, limit),
                    height: getSize(h, ih, limit),
                    marginLeft: getMargin(w, ox, limit),
                    marginTop: getMargin(h, oy, limit)
                });
            };
            setCss(this.small, SIZE_MID);
            this.w.val(w);
            this.h.val(h);
            this.x.val(ox);
            this.y.val(oy);
        },
        /**
         * 提交截图数据
         * @param callback
         */

        submit: function (callback) {
            var t = this;
            var flag = 1;
            if ($.browser !== 'msie') {
                flag = 2;
            }
            submitForm(t.form, {flag: flag}, 'avatarUploadHandle', function (resp) {
                var info = resp["var"];
                if (resp.code === 'S_OK') {
                    callback && callback.call(t, info)
                } else {
                    info = info || '未知错误';
                    alert(info);
                }
            });
            t.form.attr("action", t.options.action);
        },
        /**
         * 图片头数据加载就绪事件 - 更快获取图片尺寸
         * @version    2011.05.27
         * @author    TangBin
         * @see        http://www.planeart.cn/?p=1121
         * @param    {String}    图片路径
         * @param    {Function}    尺寸就绪
         * @param    {Function}    加载完毕 (可选)
         * @param    {Function}    加载错误 (可选)
         * @example imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
            alert('size ready: width=' + this.width + '; height=' + this.height);
         });
         */

        imgReady: (function () {
            var list = [], intervalId = null,
            // 用来执行队列
                tick = function () {
                    var i = 0;
                    for (; i < list.length; i++) {
                        list[i].end ? list.splice(i--, 1) : list[i]();
                    }
                    ;
                    !list.length && stop();
                },

            // 停止所有定时器队列
                stop = function () {
                    clearInterval(intervalId);
                    intervalId = null;
                };

            return function (url, ready, load, error) {
                var onready, width, height, newWidth, newHeight,
                    img = new Image();

                img.src = url;

                // 如果图片被缓存,则直接返回缓存数据
                if (img.complete) {
                    ready.call(img);
                    load && load.call(img);
                    return;
                }
                ;

                width = img.width;
                height = img.height;

                // 加载错误后的事件
                img.onerror = function () {
                    error && error.call(img);
                    onready.end = true;
                    img = img.onload = img.onerror = null;
                };
                // 图片尺寸就绪
                onready = function () {
                    newWidth = img.width;
                    newHeight = img.height;
                    if (newWidth !== width || newHeight !== height ||
                        // 如果图片已经在其他地方加载可使用面积检测
                        newWidth * newHeight > 1024
                        ) {
                        ready.call(img);
                        onready.end = true;
                    }
                    ;
                };
                onready();
                // 完全加载完毕的事件
                img.onload = function () {
                    // onload在定时器时间差范围内可能比onready快
                    // 这里进行检查并保证onready优先执行
                    !onready.end && onready();
                    load && load.call(img);
                    // IE gif动画会循环执行onload,置空onload即可
                    img = img.onload = img.onerror = null;
                };

                // 加入队列中定期执行
                if (!onready.end) {
                    list.push(onready);
                    // 无论何时只允许出现一个定时器,减少浏览器性能损耗
                    if (intervalId === null) intervalId = setInterval(tick, 40);
                }
                ;
            };
        })()
    };
    window["Cutting"] = Cutting;
    /**
     * 插件化
     * @param option
     * @returns {*}
     */

    $.fn.cutting = function (option) {
        var args = Array.apply(null, arguments);
        args.shift();
        return this.each(function () {
            var $this = $(this),
                data = $this.data('Cutting'),
                options = typeof option === 'object' && option;
            if (!data) {
                $this.data('Cutting', (data = new Cutting(this, options)));
            }
            if (typeof option == 'string' && typeof data[option] == 'function') {
                data[option].apply(data, args);
            }
        });
    };
    $.fn.cutting.Constructor = Cutting;

    /**
     * iframe upload
     * @param form
     * @param extraData
     * @param handleName
     * @param handle
     */

    function submitForm(form, extraData, handleName, handle) {
        var data = $.extend({
            'sid': window.sid,
            'func': handleName
        }, extraData);
        var action = form.data("action");
        form.attr('action', form.prop('action') + '?' + $.param(data));
        window[handleName] = handle;
        window.callback = function () {
        };
        form.submit();
    }
})(jQuery);

DOMO1 普通调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    var cut = new Cutting("#crop-wrap",{
        SIZE_MID: 96.0,  // 缩略图规格
        maxWidth: 256.0, // 画布最大宽度
        maxHeight: 248.0,// 画布最大高度
        drag: false,     // 是否初始化画布
        initcallback: function () {
            alert("初始化完成!");
        }, //初始化完成后,执行
        dblcallback: function () {
            alert("双击提交!");
        }   //双击选取执行
    });
    cut.create(function () {
        console.log("创建完毕");
    });

    cut.submit(function () {
        console.log("提交后的操作");
    });

DOMO2 插件调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    //创建API
    $("#crop-wrap").cutting({
        SIZE_MID: 96.0,  // 缩略图规格
        maxWidth: 256.0, // 画布最大宽度
        maxHeight: 248.0,// 画布最大高度
        drag: false,     // 是否初始化画布
        initcallback: function () {
            alert("初始化完成!");
        }, //初始化完成后,执行
        dblcallback: function () {
            alert("双击提交!");
        }   //双击选取执行
    });

    //方法调用
    $("#crop-wrap").cutting("submit",function(t, info){
        alert("提交后的操作!");
    });

最后,祝大家生活愉快!

JavaScript 原理,其一

转自:http://typeof.net

谈到 JavaScript 你会想到什么呢?浏览器?很好。Node?很好。还有其他的吗?肯定还会有。不过今天,我一不说浏览器,二不说 Node,我要说语言。

「语言这东西由说的必要吗?我每天写那么多程序,经手的代码都超过五位数行了,难道我还不会语言?笑话!还不如给我看 MVC、设计模式、代码案例,那些东西可更有用。」

——且慢——

没错,每个 JS 程序员都必定熟悉 JavaScript 语言,了解它的特性,比如对象、原型、函数以及 this等等。熟悉到甚么程度?没错,就是到天天用,用成条件反射,好像直接刻进脑子里。但是,你有没有想过,这些「特性」的背后是什么?它们的机理是什么?哦,你肯定不会想——废话,自己用了这么久的东西自己还不懂?另外,机理有用吗?对于「码农」来说,可能没用,但是不了解某个东西的机理,它的一些问题你就没法处理,至少没法处理清楚。

比如,许多人都知道细胞呼吸的过程是「燃烧」葡萄糖,产生二氧化碳和水,可以用方程 C 6H 12O 6 + O 2  CO 2 + H 2O 概括。然而简单的方程无法解释,为什么细胞可以进行无氧呼吸,同样消耗葡萄糖产生能量(尽管数量少于完整的呼吸),却不消耗氧气呢?原来,细胞呼吸实际上有三个步骤:糖酵解、三羧酸循环和氧化磷酸化。在第一步「糖酵解」中,葡萄糖被分解成更简单的化合物丙酮酸,随后丙酮酸被送入三羧酸循环,得到二氧化碳和 NADH。NADH 在最后一步送入氧化磷酸化,在线粒体内膜上和氧气结合,得到水,这个过程放出的能量形成质子浓差,推动 ATP 合成酶运转合成大量的「能量通货」ATP。整个过程只有第三个步骤消耗氧气;三个步骤里,糖酵解和氧化磷酸化放出能量,三羧酸循环反而消耗能量,因而在无氧条件下,只有糖酵解可以完成。

从这个例子可以看出,你若是不了解细胞呼吸的机理,你就无法解释无氧呼吸是怎么回事。不了解机理,就无法得心应手。于 JavaScript 也是一样的。不过呢,程序语言的境况要比生物学要简单的多。如果把我们研究的东西比作机器的话,生物学、化学、物理学摆在我们面前的是一个个黑箱,我们必须费尽心思,用各种实验如光谱、传感器、比较跟踪分析等等才能轻微地撬开这个箱子的一角,从而窥视里面无比精妙的机构;与之相反,编译器、解释器那个箱子则是完全透明,里面那台精密的机器就在你眼前,你可以直接靠过去看个够。编译器要是开源的话那就更好了,这回连箱子都没了,现在不仅可以看了,连拆都可以,它已经完全是你手里的玩物了。

不仅如此,语言的设计师们会把这门语言的所有机理(没错——所有)写在一本书里,这本书就是这门语言的规范,那台机器的蓝图。在实践中重要的语言——例如 C、C++、Java,它们的规范甚至是国际标准。JavaScript 也不例外:ECMA 组织的 ECMA 262 规范就是 JavaScript 这门语言的标准,也包含了它的全部机理。我可以说,要是读懂了 ECMA 262,完全可以做出一个 JavaScript 解释器出来。

在大多数程序员的眼里,做编译器和解释器的都是一群神奇生物,他们能制作出一个神奇的东西,能让你写的代码「动起来」,就像是魔法师一样。对啊,程序就是现代魔法,而写编译器的估计就是「现代魔法师」里最神秘的一群了。你可能知道语言有「语法」甚至能写出一个而简单语言的语法定义来,但是叫你写个语法解析器你就熄火了——从未接触过这个领域,怎么写的出来!我估计这里的各位写一个「计算器」都会比较困难——不是按按钮的计算器,是敲一行表达式出结果的计算器,而且表达式要支持加减乘除和括弧。(其实按按钮的计算器也会牵扯到语法解析的知识,各位都懂设计模式,知道建立联系吗?)

但语法解析真没你想象中的那么神秘,它实际上就是从句子里提取出含义的过程,只不过各位,你们平常「亲手处理」的字符串,要么表示数据(「配置文件」),要么表示流程(「自动化脚本」)。如果项目是那种老板甲方天天催,一坏损失几百万的那种的话,我估计,你会,数据上 XML 流程上 Lua,完事。表示数据?XML 各种复杂数据都不在话下,而且类库众多,全球通用;表示流程?Lua 是正经的嵌入式脚本语言,体积小,速度快,功能强大。

所以啊,你不会有机会亲自写个语法解析器的,至少在工作里,不会有的!语法解析器是庞大的编译原理冰山上最简单的一角,光这一角,Grune 和 Jacobs 写了整整一本书,比一本小字典还大;这一角之外的东西,比如编译器后端部分的各种优化,更是卷帙浩繁——而它们还只是编译器技术的部分。程序语言的机理更多的是建构在编译器技术之外的另一块大陆:语言理论之上。在语言理论的世界里,已经几乎不会出现代码了,全是公式。没错!公式!总之,「语言的机理」对一般程序员来说,实在是太遥远了,太不可企及了,我们只能像仰望星空一样仰望那群大魔导士。

各位可能穷尽一生也不会亲自写一个编译器,因而有些人就抛出论调说「编译器那一大堆东西,于我无用,不用学!」不过正如我前面说的,原理不掌握,你应用语言永远不能得心应手,你对程序语言的理解,绝对无法和掌握这个神秘领域的人相提并论,达到相同的高度。例如 JavaScript 的函数调用,你写 object.m(xs)可以进行正确的「方法调用」,但 f = object.m;f(xs) 就有问题(f 得不到正确的 this)。相比这个问题困扰过很多人吧!一些书籍会为了解释这个现象会把函数调用分成两种,一个是「普通调用」,一个是「方法调用」,然后强调他们在语法和语义上的区别云云。当然我不是想推翻这句话,它是对的,而且是个相当好的解释;但实际上,按照规范(11.2.3 节),JavaScript 根本就不区分所谓「普通调用」和「方法调用」。下面把 11.2.3 节原样抄下来:

产生式 「CallExpression : MemberExpression Arguments」 依如下方法解释

  1. 令 ref 为解释 MemberExpression 的结果
  2. 令 func 为 GetValue(ref)
  3. 令 argList 为解释 Arguments 的结果,它应该是由各个参数组成的列表(见 11.2.4)
  4. 如果 Type(func) 不是 Object,抛出 TypeError 异常
  5. 如果 IsCallable(func) 为假,抛出 TypeError 异常
  6. 如果 Type(ref) 是 Reference 则:
    1. 如果 IsPropertyReferernce(ref) 为真,则
      1. 令 thisValue 为 GetBase(ref)
    2. 否则(此时 ref 的基底是一个环境记录)
      1. 令 thisValue 为对 GetBase(ref) 调用 ImplicitThisValue 的结果
  7. 否则 ref 不是 Reference:
    1. 令 thisValue 为 undefined
  8. 返回对 func 调用 [[Call]] 内方法的结果,其中 this 指定为 thisValue,参数表指定为 argList

产生式 「CallExpression : CallExpression Arguments」 也依相同方法解释,惟第一步里MemberExpression 换成 CallExpression

Note

如果 func 是原生 ECMAScript 对象,CallExpression 解释结果一定不会是 Reference 类型;其为宿主对象时是否是 Reference 由实现决定,但如果是,只能是非严格的属性 Referrnce。

上面这些东西直接抄自 ECMA 262,原封未动。这段文字为 CallExpression 语法(JavaScript 里的函数调用)赋予了含义。ECMA 262 里 CallExpression 的语法是两条产生式

  • CallExpression
     → MemberExpression Arguments
  • CallExpression
     → CallExpression Arguments

这两条产生式随后被赋予「函数调用」的语义。所谓「方法调用」所必须的 this信息实际上是被MemberExpression 的值携带的。携带 this 信息的东西叫做 Reference,它是解释器使用的一种内部类型,可以记录某个表达式所「关联」的 this 信息。规范里并没有「方法调用」这个说法,「方法调用」这个词汇是为了解释 Reference 造出来的。「方法调用」这个词算是个不错的词,相比规范里精确却叫人难懂的 Reference,「方法调用」和「普通调用」的分野显然要简单得多。毕竟规范要精确、无歧义地定义一门语言,而程序员使用的时候,第一,不需要了解到如此程度;第二,说的太精确会伤人脑细胞,程序员的脑子可都金贵得很,不能磕着碰着。程序员的脑子是人脑,人脑可以接受不太精确的信息,然后结合经验,形成你写程序时候的行为准则。编译器则不同,编译器是运行在电脑上的,电脑里的东西必须精确,所以编译器作者看规范看得最勤快;也因为如此,做编译器的人往往是理解语言更为深入、最理解语言机理的人。

如果说「方法调用」算是个要解释的话,坊间书籍里有些解释就不那么好了,甚至是错误的,比如月影那本大部头《JavaScript 王者归来》里面那个「最长行匹配」就误导了不少人,还有 Douglas 宣称的「JavaScript 里所有数值都是浮点」。这些错处或多或少都是因为对规范不熟悉导致的(虽然 ECMA 262 v3 的佶屈聱牙也要负一般责任)。而它们也是我开始写「JavaScript 原理」的原因。我看规范看了不少次,本身参与了对它的翻译,外加现在在维护 moescript 这个 altjs 语言(编译到 JavaScript 的语言),对 JS 的各种特性,都有所接触。

所以,我打算在这里,第一从严谨的角度出发分析 ECMA 规范和 JavaScript,第二写写这两年来的一些真人经历,主要是 moescript 开发的经历。我希望 jser 们可以追随一个编译器作者和分析者的脚步,来审视你们日复一日使用的东西,看能不能得到一些启发。除开 JS,我也会谈及一些其他的知识和技术,还有少少的个人评价。个人水平有限,还望看官包涵;如果有错误,就大胆的来批好了。

有关「节后三问」

为了让我的书不变成「耳边风」,我会在每一节后面加三个问题。这些题目希望各位认真把它做出来。笔者比照 TAOCP,为每个题目标出了难度。它们如下表列:

  • [D] 非常简单,您应该在几分钟内就可以解答出来。
  • [C] 比较简单的问题。可能需要好好阅读问题,并且大概要 20 分钟。
  • [B] 中等难度的问题。为了完整解答它,你可能需要几个小时的努力。(当然,你的电脑联网的话时间可能更长)。本书中的多数题目都是这个难度。
  • [A] 相当困难或者繁杂的题目,或许需要几个星期才能把它做完。大学教授会把它作为学期大作业。
  • [论外] 对于这个难度的问题,可能还没有人知道答案。如果你在大学或者研究所的话,你可以把它作为研究方向。如果你把它做出来了,首先应该发篇论文,而不是找我(不过如果我看到阁下的研究成果的话,我会去亲自找您)。

下面是难度的示例:

[D] 难度 [B] 表示什么?
[C] 证明 173  = 4913;并推广你的结论。(这类问题笔者可不喜欢。)
[B] 证明用筛法筛选小于 n 的所有素数的时间复杂度(基于余数判断次数)是 O(n log log n)
[A] 写一个完整的 JavaScript 解释器,并通过 test262
[论外] P 和 NP 相等吗?

节后三问

[C] 查阅文献,世界上第一个编译器是谁写的?它叫什么名字?有哪些后续版本?
[B] 除了生物代谢过程,化学反应的机理也很重要(这也就是为什么许多诺贝尔奖得主都是研究化学动力学的)。有些看似简单的化学反应可能拥有相当复杂的反应历程,比如 H 2 + O 2  H 2O。查阅文献,这个反应的机理是什么?牵扯到了多少种中间产物?氢气和氧气按一定比例混合后会爆炸,而这个混合比有上下界,只有在上下界确定的范围内才会爆炸。从反应机理出发解释上下界存在的原因。
[B] 文章说「表示数据用 XML 很好」——XML 确实很适合来表示数据,然而近年来的 Web 服务提供的数据接口却以 JSON 为主。分析下 JSON 相对 XML 的优劣。

(本回完)

2012年12月21日 是 芥末日?

461