跳到主要内容

事件冒泡和事件捕获

· 阅读需 10 分钟

事件——怎样使用事件以及 IE 和 DOM 事件模型之间存在哪些主要差别

冒泡型事件

事件按照从最特定的事件目标到最不特定的事件目标 (document 对象) 的顺序触发。

  • IE 5.5: div -> body -> document
  • IE 6.0: div -> body -> html -> document
  • Mozilla 1.0: div -> body -> html -> document -> window

捕获型事件 (event capturing)

事件从最不精确的对象 (document 对象) 开始触发,然后到最精确 (也可以在窗口级别捕获事件,不过必须由开发人员特别指定)。

DOM 事件流

同时支持两种事件模型:捕获型事件和冒泡型事件,但是,捕获型事件先发生。两种事件流会触及 DOM 中的所有对象,从 document 对象开始,也在 document 对象结束。

支持 W3C 标准的浏览器在添加事件时用 addEventListener(event,fn,useCapture) 方法,基中第 3 个参数 useCapture 是一个 Boolean 值,用来设置事件是在事件捕获时执行,还是事件冒泡时执行。而不兼容 W3C 的浏览器 (IE) 用 attachEvent() 方法,此方法没有相关设置,不过 IE 的事件模型默认是在事件冒泡时执行的,也就是在 useCapture 等于 false 的时候执行,所以把在处理事件时把 useCapture 设置为 false 是比较安全,也实现兼容浏览器的效果。

事件冒泡

  • 事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标 (target)。
  • 事件冒泡阶段:事件从事件目标 (target) 开始,往上冒泡直到页面的最上一级标签。

举例

假设一个元素 div,它有一个下级元素 p。

<div>
  <p>元素</p>
</div>

这两个元素都绑定了 click 事件,如果用户点击了 p,它在 div 和 p 上都触发了 click 事件,那这两个事件处理程序哪个先执行呢?事件顺序是什么?

两种模型

以前,Netscape 和 Microsoft 是不同的实现方式。

  • Netscape 中,div 先触发,这就叫做事件捕获。
  • Microsoft 中,p 先触发,这就叫做事件冒泡。

两种事件处理顺序刚好相反。IE 只支持事件冒泡,Mozilla, Opera 7 和 Konqueror 两种都支持,旧版本的 Opera's 和 iCab 两种都不支持 。

事件捕获

当你使用事件捕获时,父级元素先触发,子级元素后触发,即 div 先触发,p 后触发。

事件冒泡

当你使用事件冒泡时,子级元素先触发,父级元素后触发,即 p 先触发,div 后触发。

W3C 模型

W3C 模型是将两者进行中和,在 W3C 模型中,任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达了事件源元素。然后,再从事件源往上进行事件冒泡,直到到达 document。

程序员可以自己选择绑定事件时采用事件捕获还是事件冒泡,方法就是绑定事件时通过 addEventListener 函数,它有三个参数,第三个参数若是 true,则表示采用事件捕获,若是 false,则表示采用事件冒泡

ele.addEventListener('click',doSomething2,true)
//true=捕获
//false=冒泡

传统绑定事件方式

在一个支持 W3C DOM 的浏览器中,像这样一般的绑定事件方式,是采用的事件冒泡方式

ele.onclick = doSomething2

IE 浏览器

如上面所说,IE 只支持事件冒泡,不支持事件捕获,它也不支持 addEventListener 函数,不会用第三个参数来表示是冒泡还是捕获,它提供了另一个函数 attachEvent

ele.attachEvent("onclick", doSomething2);

附:事件冒泡(的过程):事件从发生的目标(event.srcElement||event.target)开始,沿着文档逐层向上冒泡,到 document 为止。

事件的传播是可以阻止的

具体可参见另一篇博文《js 中的事件冒泡》

js 中的事件冒泡

· 阅读需 11 分钟

JavaSciprt 事件中有两个很重要的特性:

  • 事件冒泡
  • 目标元素

在一个对象上触发某类事件(比如单击 onclick 事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回 true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即 document 对象(有些浏览器是 window)。

打个比方说:你在地方法院要上诉一件案子,如果地方没有处理此类案件的法院,地方相关部门会帮你继续往上级法院上诉,比如从市级到省级,直至到中央法院,最终使你的案件得以处理。

事件冒泡

事件冒泡允许多个操作被集中处理

事件冒泡允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上),它还可以让你在对象层的不同级别捕获事件

<div onclick="eventHandle(event)" id="outSide" >
<div id="inSide" ></div>
</div>
<script type="text/javascript">
//本例子只在外面盒子定义了处理方法,而这个方法一样可以捕获到子元素点击行为并处理它。假设有成千上万子元素要处理,难道我们要为每个元素加“onclick="eventHandle(event)"”?显然没有这种集中处理的方法来的简单,同时它的性能也是更高的。
function eventHandle(e)
{
var e=e||window.event;
var obj=e.target||e.srcElement;
alert(obj.id+' was click')
}
</script>

让不同的对象同时捕获同一事件

让不同的对象同时捕获同一事件 , 并调用自己的专属处理程序做自己的事情,就像老板一下命令,各自员工做自己岗位上的工作去了

<div onclick="outSideWork()" id="outSide" >
<div onclick="inSideWork()" id="inSide" ></div>
</div>
<script type="text/javascript">
function outSideWork()
{
alert('My name is outSide,I was working...');
}

function inSideWork()
{
alert('My name is inSide,I was working...');
}

//因为下面程序自动激活单击事件,有些浏览器不允许,所以请单击灰色盒子,从这里开始下命令,这样因为冒泡的原因,黑色大盒子也会收到单击事件,并调用了自己的处理程序。如果还有更多盒子嵌套,一样道理。

/*
function bossOrder()
{
document.getElmentById('inSide').click();
}
bossOrder();
*/
</script>

冒泡类型

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标 (document 对象) 的顺序触发。

  • IE 5.5: div -> body -> document
  • IE 6.0: div -> body -> html -> document
  • Mozilla 1.0: div -> body -> html -> document -> window

阻止冒泡

通常情况下我们都是一步到位,明确自己的事件触发源,并不希望浏览器自作聪明、漫无目的地去帮我们找合适的事件处理程序,即我们明确最精准目标,这种情况下我们不需要事件冒泡。另外通过对事件冒泡的理解,我们知道程序将做多较多额外的事情,这必然增大程序开销。还有一个重要的问题是:事件冒泡处理可能会激活我们本来不想激活的事件,导致程序错乱,甚至无从下手调试,这常成为对事件冒泡不熟悉程序员的棘手问题。所以必要时,我们要阻止事件冒泡。

javascript 中兼容 IE 和 ff 的方式

//阻止事件冒泡函数
function stopBubble(e)
{
if (e && e.stopPropagation)
e.stopPropagation()
else
window.event.cancelBubble=true
}

阻止 jQuery 事件冒泡

jQuery 对 DOM 的事件触发具有冒泡特性。有时利用这一特性可以减少重复代码,但有时候我们又不希望事件冒泡。这个时候就要阻止 jQuery.Event 冒泡。

jQuery.Event 提供了一个非常简单的方法来阻止事件冒泡:event.stopPropagation();

$("p").click(function(event){
event.stopPropagation();
// do something
})

重放攻击 (Replay Attacks)

· 阅读需 16 分钟

根据百科的解释:

重放攻击(Replay Attacks)又称重播攻击、回放攻击或新鲜性攻击(Freshness Attacks),是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。 它是一种攻击类型,这种攻击会不断恶意或欺诈性地重复一个有效的数据传输,重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。从这个解释上理解,加密可以有效防止会话劫持,但是却防止不了重放攻击。重放攻击任何网络通讯过程中都可能发生。重放攻击是计算机世界黑客常用的攻击方式之一,它的书面定义对不了解密码学的人来说比较抽象。

概念性的几个防御手段

时间戳

“时戳”──代表当前时刻的数 基本思想──A 接收一个消息当且仅当其包含一个对 A 而言足够接近当前时刻的时戳 原理──重放的时戳将相对远离当前时刻 时钟要求──通信各方的计算机时钟保持同步 处理方式──设置大小适当的时间窗(间隔),越大越能包容网络传输延时,越小越能防重放攻击 适用性──用于非连接性的对话(在连接情形下双方时钟若偶然出现不同步,则正确的信息可能会被误判为重放信息而丢弃,而错误的重放信息可能会当作最新信息而接收)

序号

通信双方通过消息中的序列号来判断消息的新鲜性 要求通信双方必须事先协商一个初始序列号,并协商递增方法

提问与应答

“现时”──与当前事件有关的一次性随机数 N(互不重复即可) 基本做法──期望从 B 获得消息的 A 事先发给 B 一个现时 N,并要求 B 应答的消息中包含 N 或 f(N),f 是 A、B 预先约定的简单函数 原理──A 通过 B 回复的 N 或 f(N) 与自己发出是否一致来判定本次消息是不是重放的 时钟要求──无 适用性──用于连接性的对话 重放攻击是对协议的攻击中危害最大、最常见的一种攻击形式。

以登陆为例看具体的例子

常规流程

  1. 前端 web 页面用户输入账号、密码,点击登录。
  2. 请求提交之前,web 端首先通过客户端脚本如 javascript 对密码原文进行 md5 加密。
  3. 提交账号、md5 之后的密码
  4. 请求提交至后端,验证账号与密码是否与数据库中的一致,一致则认为登录成功,反之失败。

有什么问题呢?

上述流程看似安全,认为传输过程中的密码是 md5 之后的,即使被监听截取到,由于 md5 的不可逆性,密码明文也不会泄露。其实不然!监听者无需解密出密码明文即可登录!监听者只需将监听到的 url(如:http://****/login.do?method=login&password=md5 之后的密码 &userid= 登录账号)重放一下,即可冒充你的身份登录系统。

稍微安全点的方式

  1. 进入登陆页面时,生成一个随机码(称之为盐值),在客户端页面和 session 中各保存一份。
  2. 客户端提交登录请求时,将 md5 之后的密码与该随机码拼接后,再次执行 md5,然后提交(提交的密码 =md5(md5(密码明文)+随机码))。
  3. 后端接收到登录请求后,将从数据库中查询出的密码与 session 中的随机码拼接后,md5 运算,然后与前端传递的结果进行比较。

为何要这样?

该登录方式,即使登录请求被监听到,回放登录 URL,由于随机码不匹配(监听者的 session 中的随机码与被监听者的 session 中的随机码相同概率可忽略),无法登录成功。 该登录方式,由于传输的密码是原密码 md5 之后与随机码再次 md5 之后的结果,即使监听者采用暴力破解的方式,也很难解密出密码明文。

更进一步

考虑到密码输入的方便性,好多用户的密码都设置的很短,并且不够复杂,往往是 6 位数字字母组合,这样的密码 md5 之后保存到数据库,一旦数据库数据泄露,简单密码的 md5 结果很容易通过暴力破解的方式给解密出来,何况 md5 出现了这么多年,可能已经有不少字典了!同时为了方便用户登录的方便性,我们的系统一般不可能要求用户设置很长、很复杂的密码!怎么办?加固定盐值

  1. 系统设置一个固定的盐值,该盐值最好足够复杂,如 :1qaz2wsx3edc4rfv!@#$%^&qqtrtRTWDFHAJBFHAGFUAHKJFHAJHFJHAJWRFA
  2. 用户注册、修改密码时,将用户的原始密码与我们的固定盐值拼接,然后做 md5 运算。
  3. 传递至后端,保存进数据库(数据库中保存的密码是用户的原始密码拼接固定盐值后,md5 运算后的结果)。
  4. 登录时,将用户的原始密码与我们的固定盐值进行拼接,然后做 md5 运算,运算后的结果再拼接上我们的随机码,再次 md5 运算,然后提交。
  5. 后端接收到登录请求后,将从数据库中查询出的密码与 session 中的随机码拼接后,md5 运算,然后与前端传递的结果进行比较。

再再进一步

  1. 加登录验证码,可预防人为地暴力登录破解
  2. 账户锁定,如果用户密码输入错误次数达到一定量后(如 6 次),则可以锁定该账号

js 中的 dom 操作回顾

· 阅读需 7 分钟

创建节点

  • document.body 指向的是 body 元素
  • document.documentElement 则指向 html 元素
    //创建节点
var createNode = document.createElement("div");
var createTextNode = document.createTextNode("hello world");
createNode.appendChild(createTextNode);
document.body.appendChild(createNode);
document.documentElement.appendChild(createNode);

插入节点

可以使用

  • appendChild
  • insertBefore insertBefore 接收两个参数,第一个是插入的节点,第二个是参照节点,如 insertBefore(a,b),则 a 会插入在 b 的前面
    //插入节点
var createNode = document.createElement("div");
var createTextNode = document.createTextNode("hello world");
createNode.appendChild(createTextNode);
var div1 = document.getElementById("div1");
document.body.insertBefore(createNode,div1);

替换和删除元素

从 replaceChild 和 removeChild 的字面意思看,就是删除子节点,因此调用者,需要包含子节点 div1,不然调用会报错。返回的节点是替换的或删除的元素,被替换 / 删除的元素仍然存在,但 document 中已经没有他们的位置了。

//替换元素
var replaceChild = document.body.replaceChild(createNode,div1);
//删除元素
var removeChild = document.body.removeChild(div1);

节点的属性

  • firstChild: 第一个子节点
  • lastChild: 最后一个子节点
  • childNodes: 子节点集合,获取其中子节点可以 someNode.childNodes[index] 或者 someNode.childNodes.item(index)
  • nextSibling: 下一个兄弟节点
  • previousSibling:上一个兄弟节点
  • parentNode:父节点

文档片段

好处在于减少 dom 的渲染次数,可以优化性能。

//文本片段
var fragment = document.createDocumentFragment();
var ul = document.getElementById("ul");
var li = null;
for (var i = 4; i >= 0; i--) {
li = document.createElement("li");
li.appendChild(document.createTextNode("item "+i));
fragment.appendChild(li);
}
ul.appendChild(fragment);

克隆元素

  • someNode.cloneNode(true): 深度克隆,会复制节点及整个子节点
  • someNode.cloneNode(false): 浅克隆,会复制节点,但不复制子节点
//克隆
var clone = ul.cloneNode(true);
document.body.appendChild(clone);

注意

childNodes.length 存在跨浏览器的问题

可以看到有关列表的 html 片段没有用

<ul id="ul">
<li>sdsssssss</li>
<li>qqqq</li>
<li>wwww</li>
<li>eeee</li>
</ul>

这种书写格式而是使用没有换行的格式书写,是因为在不同的浏览器中,获取 ul.childNodes.length 的结果有差异: 在 ie 中,ul.childNodes.length 不会计算 li 之间的换行空格,从而得到数值为 4 在 ff、chrome,safari 中,会有包含 li 之间的空白符的 5 个文本节点,因此 ul.childNodes.length 为 9 若要解决跨浏览器问题,可以将 li 之间的换行去掉,改成一行书写格式。

cloneNode 存在跨浏览器的问题

在 IE 中,通过 cloneNode 方法复制的元素,会复制事件处理程序,比如,var b = a.cloneNode(true). 若 a 存在 click,mouseover 等事件监听,则 b 也会拥有这些事件监听。 在 ff,chrome,safari 中,通过 cloneNode 方法复制的元素,只会复制特性,其他一切都不会复制 因此,若要解决跨浏览器问题,在复制前,最好先移除事件处理程序。

Javascript 的 addEventListener() 及 attachEvent() 区别分析

· 阅读需 9 分钟

大家都知道事件的用法就是当某个事件 (状况) 被触发了之后就会去执行某个 Function, 尤其是 Javascript, 在当红 AJAX 的催化下 , 了解 Javascript 的 Event 用法更加重要 , 在这里就大概介绍一下 avascript 的 Event 用法。

Mozilla 中

addEventListener 的使用方式:

target.addEventListener(type, listener, useCapture);

  • target: 文档节点、document、window 或 XMLHttpRequest。
  • type: 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
  • listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
  • useCapture :是否使用捕捉,一般用 false 。 例如:
document.getElementById("testText").addEventListener("keydown", function (event) { alert(event.keyCode); }, false);

栗子

document.getElementById("testText").addEventListener("keydown", function (event) { alert(event.keyCode); }, false);

IE中

target.attachEvent(type, listener);

  • target: 文档节点、document、window 或 XMLHttpRequest。
  • type: 字符串,事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。
  • listener :实现了 EventListener 接口或者是 JavaScript 中的函数。 例如:document.getElementById("txt").attachEvent("onclick",function(event){alert(event.keyCode);});

移除事件

W3C 及 IE 同时支持移除指定的事件 , 用途是移除设定的事件 , 格式分别如下 :

W3C 格式

removeEventListener(event,function,capture/bubble);

Windows IE的格式如下

detachEvent(event,function);

浏览器兼容

适应的浏览器版本不同,同时在使用的过程中要注意

  • attachEvent 方法 按钮 onclick IE 中使用
  • addEventListener 方法 按钮 click fox 中使用

两者使用的原理:可对执行的优先级不一样:

  • attachEvent 方法,为某一事件附加其它的处理事件。(不支持 Mozilla 系列)
  • addEventListener 方法 用于 Mozilla 系列

栗子

document.getElementById("btn").onclick = method1;
document.getElementById("btn").onclick = method2;
document.getElementById("btn").onclick = method3;

**如果这样写 , 那么将会只有 medhot3 被执行 **

写成这样:

var btn1Obj = document.getElementById("btn1"); //object.attachEvent(event,function);
btn1Obj.attachEvent("onclick",method1);
btn1Obj.attachEvent("onclick",method2);
btn1Obj.attachEvent("onclick",method3);

**执行顺序为 method3->method2->method1 **

如果是 Mozilla 系列,并不支持该方法,需要用到 addEventListener

var btn1Obj = document.getElementById("btn1");
//element.addEventListener(type,listener,useCapture);
btn1Obj.addEventListener("click",method1,false);
btn1Obj.addEventListener("click",method2,false);
btn1Obj.addEventListener("click",method3,false);

执行顺序为 method1->method2->method3

兼容 IE 和 firefox 的事件处理

封装

function addListener(element, eventName, handler) {
if (element.addEventListener) {
element.addEventListener(eventName, handler, false);
}
else if (element.attachEvent) {
element.attachEvent('on' + eventName, handler);
}
else {
element['on' + eventName] = handler;
}
}

function removeListener(element, eventName, handler) {
if (element.addEventListener) {
element.removeEventListener(eventName, handler, false);
}
else if (element.detachEvent) {
element.detachEvent('on' + eventName, handler);
}
else {
element['on' + eventName] = null;
}
}

上面函数有两处需要注意一下就是 :

  1. 第一个分支最好先测定 w3c 标准 . 因为 IE 也渐渐向标准靠近 . 第二个分支监测 IE.
  2. 第三个分支是留给既不支持 (add/remove)EventListener 也不支持 (attach/detach)Event 的浏览器 .

优化

对于上面的函数我们是运用 " 运行时 " 监测的 . 也就是每次绑定事件都需要进行分支监测 . 我们可以将其改为 " 运行前 " 就确定兼容函数 . 而不需要每次监测 .

这样我们就需要用一个 DOM 元素提前进行探测 . 这里我们选用了 document.documentElement. 为什么不用 document.body 呢 ? 因为 document.documentElement 在 document 没有 ready 的时候就已经存在. 而 document.body 没 ready 前是不存在的 .

这样函数就优化成

var addListener, removeListener,
/* test element */
docEl = document.documentElement;

// addListener
if (docEl.addEventListener) {
/* if `addEventListener` exists on test element, define function to use `addEventListener` */
addListener = function (element, eventName, handler) {
element.addEventListener(eventName, handler, false);
};
} else if (docEl.attachEvent) {
/* if `attachEvent` exists on test element, define function to use `attachEvent` */
addListener = function (element, eventName, handler) {
element.attachEvent('on' + eventName, handler);
};
} else {
/* if neither methods exists on test element, define function to fallback strategy */
addListener = function (element, eventName, handler) {
element['on' + eventName] = handler;
};
}

// removeListener
if (docEl.removeEventListener) {
removeListener = function (element, eventName, handler) {
element.removeEventListener(eventName, handler, false);
};
} else if (docEl.detachEvent) {
removeListener = function (element, eventName, handler) {
element.detachEvent('on' + eventName, handler);
};
} else {
removeListener = function (element, eventName, handler) {
element['on' + eventName] = null;
};
}

简单说说 web 语义化

· 阅读需 6 分钟

一个小问题

先看一段 HTML 代码

<table>
<tr>
<td colspan="2">Student List</td>
</tr>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<tr>
<td>Byron</td>
<td>24</td>
</tr>
<tr>
<td>Vincent</td>
<td>25</td>
</tr>
<tr>
<td>Casper</td>
<td>27</td>
</tr>
</table>

就算是没有看到页面效果,相信很多同学看到内容后也能明白这个表格在描述什么,但是计算机(搜索引擎)却不明白,计算机并不知道 Name 或者 Age 代表 title,而下面的是内容。但如果这么写

<table>
<caption>Student List</caption>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>Byron</td>
<td>24</td>
</tr>
<tr>
<td>Vincent</td>
<td>25</td>
</tr>
<tr>
<td>Casper</td>
<td>27</td>
</tr>
</tbody>
</table>

这样即使我们不看内容也能了解这大概是什么,计算机也会理解哪些是标题,哪些是 header,哪些是内容。

Web 语义化

这个东西我也是和同学们聊了聊天儿,自己翻了些资料,所以这里仅说一下个人的理解。HTML 的每个标签都有其特定含义(语义),Web 语义化是指使用语义恰当的标签,使页面有良好的结构,页面元素有含义,能够让人和搜索引擎都容易理解

如果可以在合适的位置使用恰当的标签,那么写出来的页面语义明确,结构清晰,搜索引擎也可以认出哪些是页面重要内容,予以较高的权值。h1h6 这几个标签在搜索引擎中权值非常高,用它们作页面标题就是一个简单的 SEO 优化了,知道了这个你还使用 DIV+CSS 来写标题吗?(不是说直接用 h1h6 标签做标题,要这几个标签+CSS)

回到开始时的疑惑,这时候 header、footer、sidebar、article 等标签的出现就不是那么让人困惑了,HTML5 的一大革新就是语义化标签的完善。

语义化网页结构

使用这样结构写出的网页其语义显而易见。在有些面试的时候会问到类似 strong 和 font-weight: bold 有什么区别,这时候就可以从语义化的角度解答了。

一些标签语义

标签 语义

  • h1~h6 标题
  • th table 的 header
  • p 段落
  • ul 无序列表
  • ol 排序列表
  • dl definition list,定义列表
  • dt definition title,定义名称
  • dd definition description 定义描述
  • em emphasized,局部强调 , 段落内强调
  • strong 更强烈的强调,全文强调

js 键盘粘贴上传图片

· 阅读需 2 分钟

segmentfault 上看到的一个问直接在文本框中粘贴就可以上传图片的例子。

/** 粘贴上传图片 **/
document.body.addEventListener('paste', function(e) {
var clipboard = e.clipboardData;
for(var i=0,len=clipboard.items.length; i<len; i++) {
if(clipboard.items[i].kind == 'file' || clipboard.items[i].type.indexOf('image') > -1) {
var imageFile = clipboard.items[i].getAsFile();
var form = new FormData;
form.append('image', imageFile);
$.ajax({
url: channel.server+"/u.php?community="+channel.community,
type: "POST",
data: form,
processData: false,
contentType: false,
beforeSend: function() {
$('#uploadmessage').html('正在上传图片...');
},
error: function() {
$('#uploadmessage').html('上传失败请重新上传!');
setTimeout('$(\'#uploadmessage\').html(\'\')', 1000);
},
success: function(url) {
$('#uploadmessage').html('图片上传成功');
setTimeout('$(\'#uploadmessage\').html(\'\')', 1000);
var textarea = $('.textarea');
textarea.val(textarea.val() + '[![]('+url+')]('+url+')')
}
})
e.preventDefault();
}
}
});

这里是帖子原文

http://segmentfault.com/q/1010000002729267

IE9 以下不支持 JSON.parse 的一些解决方案

· 阅读需 4 分钟

在 IE9 以下是不支持 JSON.parse 方法来解析 JSON 字符串的。

原来,在 IE8 中使用原生 JSON 对象是有条件的。微软在介绍 JSON 对象时也有下面的一段注释:

Starting with JScript. 5.8, by default, the JScript. scripting engine supports the language feature set as it existed in version 5.7. This is to maintain compatibility with the earlier versions of the engine. To use the complete language feature set of version 5.8, the Windows Script. interface host has to invoke IActiveScriptProperty::SetProperty.

Internet Explorer 8 opts into the JScript. 5.8 language features when the document mode for Internet Explorer 8 is "Internet Explorer 8 Standards" mode. For other document modes, Internet Explorer uses the version 5.7 feature set.

JScript. 5.8 includes native JavaScript. Object Notation (JSON) support and the accessor methods for Document Object Model (DOM) prototypes.

由于 JSON 对象是在 JScript. 5.8 及其以后的版本引入的,所以,默认情况下,IE8 使用的是 JScript. 5.7 版本,所以,原生 JSON 对象是无法使用的。

1.eval 方式解析,恐怕这是最早的解析方式了

    function strToJson(str){
var json = eval('(' + str + ')');
return json;
}

但是出于安全性的考虑,建议尽量不要使用 eval,如果从第三方获取数据进行解析,会存在恶意脚本代码的风险。

2.new Function 形式,比较怪异。

    function strToJson(str){
var json = (new Function("return " + str))();
return json;
}

nodejs 扫盲 module.exports 与 exports

· 阅读需 5 分钟

在 nodejs 模块中,如果希望向外暴露出模块中的方法和变量,需要使用 module.exports 或者 exports,但是他们的意义却不同。

module.exports

module 表示该模块对象,在 module 对象中有个 exports 的属性,默认值为空对象;exports 是模块往外暴露方法或者变量的接口,可以暴露变量或者方法:

    var a =  10;
module.exports = a;

var a = 10;
var b = 'hello';
module.exports = {age:a,name:b};

var a = {
name : 'hello',
age: 10
}
module.exports = a;

function a(){
console.log('hello')
}
module.exports = a;

var a = function (){
console.log('hello')
}
module.exports = a;

var a = {
name : 'hello',
getName : function(){
console.log(this.name)
}
}
module.exports = a;

exports

exports 是 module.exports 的一个引用,可以为 exports 添加属性,但不能直接赋值,那样就会失去对 module.exports 的引用,便不会暴露出所的值。

    exports.name = 'hello';

exports.getName = function(){
console.log(this.name)
}

因为是 module.exports 的引用,所以每次为 exports 赋值的时候,就是为 module.exports 赋值。 如果直接为 exports 赋值:

    exports = 'hello';

即改变了 exports 对 module.exports 的引用,所以所赋的值无法通过 module.exports 暴露出去。

exports = module.exports = xxx

可以在很多项目中看到这句,当模块要输出一个非 Object 时(比如一个 Function),可以使用

    module.exports = function () {}

此时 module.exports 被覆盖了,而 exports 还是原来的对像的引用,为了避免在后面的代码中仍然使用 exports.xx = yy 而导致不能正确输出,需要把 exports 变量也重新设置为新的 module.exports 的引用,所以一般习惯写成

    exports = module.exports = xxx

JavaScript 异步编程的 Promise 模式

· 阅读需 25 分钟

作者 崔康

异步模式在 web 编程中变得越来越重要,对于 web 主流语言 Javascript 来说,这种模式实现起来不是很利索,为此,许多 Javascript 库(比如 jQuery 和 Dojo)添加了一种称为 promise 的抽象(有时也称之为 deferred)。通过这些库,开发人员能够在实际编程中使用 promise 模式。IE 官方博客最近发表了一篇 文章,详细讲述了如何使用 XMLHttpRequest2 来实践 promise 模式。我们来了解一下相关的概念和应用。

考虑这样一个例子,某网页存在异步操作(通过 XMLHttpRequest2 或者 WebWorkers)。随着 Web 2.0 技术的深入,浏览器端承受了越来越多的计算压力,所以“并发”具有积极的意义。对于开发人员来说,既要保持页面与用户的交互不受影响,又要协调页面与异步任务的关系,这种非线性执行的编程要求存在适应的困难。先抛开页面交互不谈,我们能够想到对于异步调用需要处理两种结果——成功操作和失败处理。在成功的调用后,我们可能需要把返回的结果用在另一个 Ajax 请求中,这就会出现“函数连环套”的情况(在笔者的另一篇文章《NodeJS 的异步编程风格》中有详细的解释)。这种情况会造成编程的复杂性。看看下面的代码示例(基于 XMLHttpRequest2):

    function searchTwitter(term, onload, onerror) {

var xhr, results, url;
url = 'http://search.twitter.com/search.json?rpp=100&amp;q=' + term;
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
onload(results);
}
};

xhr.onerror = function (e) {
onerror(e);
};

xhr.send();
}

function handleError(error) {
/* handle the error */
}

function concatResults() {
/* order tweets by date */
}

function loadTweets() {
var container = document.getElementById('container');

searchTwitter('#IE10', function (data1) {
searchTwitter('#IE9', function (data2) {
/* Reshuffle due to date */
var totalResults = concatResults(data1.results, data2.results);
totalResults.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);
}, handleError);
}

上面的代码其功能是获取 Twitter 中 hashtag 为 IE10 和 IE9 的内容并在页面中显示出来。这种嵌套的回调函数难以理解,开发人员需要仔细分析哪些代码用于应用的业务逻辑,而哪些代码处理异步函数调用的,代码结构支离破碎。错误处理也分解了,我们需要在各个地方检测错误的发生并作出相应的处理。

为了降低异步编程的复杂性,开发人员一直寻找简便的方法来处理异步操作。其中一种处理模式称为 promise,它代表了一种可能会长时间运行而且不一定必须完整的操作的结果。这种模式不会阻塞和等待长时间的操作完成,而是返回一个代表了承诺的(promised)结果的对象。

考虑这样一个例子,页面代码需要访问第三方的 API,网络延迟可能会造成响应时间较长,在这种情况下,采用异步编程不会影响整个页面与用户的交互。promise 模式通常会实现一种称为 then 的方法,用来注册状态变化时对应的回调函数。比如下面的代码示例:

    searchTwitter(term).then(filterResults).then(displayResults);

promise 模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以 CommonJS Promise/A 标准为例,promise 对象上的 then 方法负责添加针对已完成和拒绝状态下的处理函数。then 方法会返回另一个 promise 对象,以便于形成 promise 管道,这种返回 promise 对象的方式能够支持开发人员把异步操作串联起来,如 then(resolvedHandler, rejectedHandler); 。resolvedHandler 回调函数在 promise 对象进入完成状态时会触发,并传递结果;rejectedHandler 函数会在拒绝状态下调用。

有了 promise 模式,我们可以重新实现上面的 Twitter 示例。为了更好的理解实现方法,我们尝试着从零开始构建一个 promise 模式的框架。首先需要一些对象来存储 promise。

    var Promise = function () {
/* initialize promise */
};

接下来,定义 then 方法,接受两个参数用于处理完成和拒绝状态。

Promise.prototype.then = function (onResolved, onRejected) {
/* invoke handlers based upon state transition */
};

同时还需要两个方法来执行理从未完成到已完成和从未完成到拒绝的状态转变。

    Promise.prototype.resolve = function (value) {
/* move from unfulfilled to resolved */
};

Promise.prototype.reject = function (error) {
/* move from unfulfilled to rejected */
};

现在搭建了一个promise的架子,我们可以继续上面的示例,假设只获取IE10的内容。创建一个方法来发送Ajax请求并将其封装在promise中。这个promise对象分别在xhr.onload和xhr.onerror中指定了完成和拒绝状态的转变过程,请注意searchTwitter函数返回的正是promise对象。然后,在loadTweets中,使用then方法设置完成和拒绝状态对应的回调函数。

    function searchTwitter(term) {

var url, xhr, results, promise;
url = 'http://search.twitter.com/search.json?rpp=100&amp;q=' + term;
promise = new Promise();
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
promise.resolve(results);
}
};

xhr.onerror = function (e) {
promise.reject(e);
};

xhr.send();
return promise;
}

function loadTweets() {
var container = document.getElementById('container');
searchTwitter('#IE10').then(function (data) {
data.results.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);
}

到目前为止,我们可以把promise模式应用于单个Ajax请求,似乎还体现不出promise的优势来。下面来看看多个Ajax请求的并发协作。此时,我们需要另一个方法when来存储准备调用的promise对象。一旦某个promise从未完成状态转化为完成或者拒绝状态,then方法里对应的处理函数就会被调用。when方法在需要等待所有操作都完成的时候至关重要。

    Promise.when = function () {
/* handle promises arguments and queue each */
};

以刚才获取IE10和IE9两块内容的场景为例,我们可以这样来写代码:

    var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');
Promise.when(promise1, promise2).then(function (data1, data2) {

/* Reshuffle due to date */
var totalResults = concatResults(data1.results, data2.results);
totalResults.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);

分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js

除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。

Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:

    function searchTwitter(term) {

var url, xhr, results, def;
url = 'http://search.twitter.com/search.json?rpp=100&amp;q=' + term;
def = new dojo.Deferred();
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
def.resolve(results);
}
};

xhr.onerror = function (e) {
def.reject(e);
};

xhr.send();
return def;
}

dojo.ready(function () {
var container = dojo.byId('container');
searchTwitter('#IE10').then(function (data) {
data.results.forEach(function (tweet) {
dojo.create('li', {
innerHTML: tweet.text
}, container);
});
});
});

不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。

   var deferred = dojo.xhrGet({
url: "search.json",
handleAs: "json"
});

deferred.then(function (data) {
/* handle results */
}, function (error) {
/* handle error */
});

除此之外,Dojo 还引入了 dojo.DeferredList, 支持开发人员同时处理多个 dojo.Deferred 对象,这其实就是上面所提到的 when 方法的另一种表现形式。

    dojo.require("dojo.DeferredList");
dojo.ready(function () {
var container, def1, def2, defs;
container = dojo.byId('container');
def1 = searchTwitter('#IE10');
def2 = searchTwitter('#IE9');

defs = new dojo.DeferredList([def1, def2]);

defs.then(function (data) {
// Handle exceptions
if (!results[0][0] || !results[1][0]) {
dojo.create("li", {
innerHTML: 'an error occurred'
}, container);
return;
}
var totalResults = concatResults(data[0][1].results, data[1][1].results);

totalResults.forEach(function (tweet) {
dojo.create("li", {
innerHTML: tweet.text
}, container);
});
});
});

上面的代码比较清楚,不再详述。

说到这里,读者可能已经对 promise 模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise 模式就是一个很好的例子,它的风格比较人性化,而且主流的 JS 框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise 模式的使用需要恰当地设置 promise 对象,在对应的事件中调用状态转换函数,并且在最后返回 promise 对象。

技术社区对异步编程的关注也在升温,国内社区也发出了自己的声音。资深技术专家老赵就发布了一套开源的异步开发辅助库 Jscex,它的设计很巧妙,抛弃了回调函数的编程方式,采用一种“线性编码、异步执行”的思想,感兴趣的读者可以查看 这里

不仅仅是前端的 JS 库,如今火热的 NodeJS 平台也出现了许多第三方的 promise 模块,具体的清单可以访问 这里

注:本文中的所有代码示例均来自于 IE 官方博客。

原文来自 InfoQ :http://www.infoq.com/cn/news/2011/09/js-promise/