这是我2年前写的blog,原文在这里

来自爆栈这里

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
// 全局作用域变量定义
var a=1;

// 简单的例子:访问全局作用域
function one(){
alert(a);
}

// 简单的例子:访问的是局部作用域
function two(a){
alert(a);
}

// 简单的例子:还是局部作用域
function three(){
var a = 3;
alert(a);
}

// 有点难度的例子:在函数内脚本块里面定义的局部变量,可以被函数内部访问的局部作用域的变量
function four(){
if(true){
var a=4;
}

alert(a); // alerts '4', not the global value of '1'
}


// 有点难度的例子:对象属性作用域
function Five(){
this.a = 5;
}


// 高级货: 闭包
var six = function(){
var foo = 6;

return function(){
// javascript "闭包" 意思是我只能在这个函数里面访问到foo,
alert(foo);
}
}()


// 另外一个高级货: 基于prototype的作用域
function Seven(){
this.a = 7;
}

// [object].prototype.property 方式定义属性会在 [object].property 方式定义后丢失对应的属性
Seven.prototype.a = -1; // 此值会被覆盖,因为构造函数会将a属性重新复制
Seven.prototype.b = 8; // 同理此b属性没有被覆盖,所以值为8



// 以上例子的值分别为 1-8
one();
two(2);
three();
four();
alert(new Five().a);
six();
alert(new Seven().a);
alert(new Seven().b);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
我补充几个高级货:

// this作用域的区别
var nine = 9;
function Nine(){
alert(this.nine); // this指向window的全局作用域
}
Nine();
// 以上的直接调用Nine()的效果,但是this对象作用域可以变的
Nine.call({nine : 10});// this对象指向参数的对象

// 除了call和apply,还有另外一种方式调用会导致不同的this引用对象
var Eleven = 12;
var bar = {
Eleven : 11,
foo : function(){
alert(this.Eleven)
}
};
bar.foo();// this指向bar对象
var foo = bar.foo;
foo();// this指向window
// 以上例子分别输出9-12

这是我2年前写的blog,原文在这里

来自爆栈这里

引出问题的是楼主问jquery环境下preventDefault和return false的区别。

楼下有一位总结关于return false在各个环境下的区别,以下是节选和总结:

  • 在标准的DOM2 handler下(也就是使用addEventListener方式监听),return false不会做任何事情(也就是不会阻止事件,也不会停止冒泡)

  • 在Microsoft DOM2 handler下(也就是使用attachEvent方式监听),return false会阻止事件,但是不会停止冒泡

  • 在标准的DOM0 handler下(也就是在html结构里面事件中return,如:<a onclick="return ...">),return false会阻止事件,但是不会停止冒泡

  • 以上都是原生方式return false,都是不能默认阻止事件冒泡

  • 但是在jquery环境下监听事件(如:$(‘a’).click(…))return false会同时阻止事件和停止事件冒泡。

这是我2年前写的blog,原文在这里

最近在用xhr2做某个项目的后台,在这过程中遇到各种疑难杂症,对比起来,flash request的跨域其实是既简单又实用,而xhr2则有各种限制。

先来对比flash request和xhr2的跨域策略区别

  • flash request:使用配置xml “crossdomain.xml”,来进行配置,不需要修改其他比如返回http头,flash会先获取这个xml查询http请求,是否有权限进行跨域。

  • xhr2:使用浏览器自带的xmlhttprequest level2来进行跨域ajax请求,浏览器和服务器需要发送和返回对应的http头来进行跨域策略。

下面是在用到xhr2是遇到的问题:

  • 首先遇到第一个问题,因为extjs框架进行跨域访问时,会自动带上自定义头:X-Requested-With,这时候浏览器会提示出错,原因是因为当有自定义头时,服务器端需要返回头:Access-Control-Allow-Headers:X-Requested-With,表示允许可以设置请求自定义头

  • 第二个问题是发现跨域请求没有带上cookie,原因是因为在extjs的xhr2,withCredentials默认为false,需要加上这个属性请求才能带上cookie,但是加上withCredentials属性后发现还是出错,原因是当withCredentials为true时,返回的Access-Control-Allow-Origin的值不能为*,需要为一个特定的域名,最后把这个改为测试机的域名后,以为终于可以了吧,Holy Crap!还是不行,好吧,原因是服务器端http头也要返回Access-Control-Allow-Credentials:true

总结:

  • 当xhr2跨域ajax时,不需要带上cookie,没有自定义头,只需要返回设置Access-Control-Allow-Origin头就可以。

  • 如果需要设置自定义头,需要服务器端返回Access-Control-Allow-Headers:自定义头。

  • 如果需要带上cookie,则xhr2对象需要设置withCredentials为true,并且服务器返回Access-Control-Allow-Credentials:true ,并且Access-Control-Allow-Origin不能为*

这是我三年前写的blog,原文在这里

为什么要使用Observer模式

在大型的ajax项目前端开发中,模块分得很细,多个开发者负责不同的业务逻辑模块开发,会需要不同模块之间互相调用接口,在网易邮箱的前端开发中遇到过这样的情况也是家常便饭,虽然我们可以将公共调用的功能分离出来到一个“公共库”,然后开发者需要实现某个功能时,可以去“公共库”找相关的接口,然后在自己的代码中调用。

但是如果接口有变更、或者新需求需要增加调用,就导致各个模块负责人耦合维护,这种方式在小型的项目没有大问题,不过项目越做越大后,就会导致模块之间耦合越来越大,维护成本越来越高,在新的网易邮箱版本里面,使用了Observer的设计模式实现模块之间的解耦。

Observer模式的定义

Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

举例说明

先举个例子:邮箱中的收件箱模块和读信模块,分别是程序员A和程序员B负责,在旧的模式里,收件箱读取一封新邮件后,B会调用A的接口.setRead(sMailId)实现去掉该邮件未读标记,然后有一天,负责左侧导航的同事C说:产品增加一个需求,读取新邮件后,左侧导航的收件箱的未读数需要更新。这时,C发给B一个更新导航的接口,然后B就要修改代码,加上C提供的接口.updateNav()。

  • 使用Observer模式后:

首先定义了一个afterRead的事件,收件箱模块监听了这个事件:Listener.listen(“afterRead”, DoSetReadFunction),新邮件读取之后,会触发一个事件:Listener.fire(“afterRead”),这时,监听了这个事件的收件箱模块会调用接口,然后在新的需求里,C同事只需要监听:Listener.listen(“afterRead”, DoUpdateNavFunction),就实现了左侧导航的新邮件数更新了。

  • 这个过程的变化相当于:
    旧的方式:读取一封新邮件后 -> 调用收件箱接口 -> 调用导航接口
    新的方式:读取一封新邮件后 -> 告诉所有模块:我刚读了一封新邮件 -> 其他监听了这个事件的模块被通知了 -> 调用相关接口

  • 简单的自定义事件实现

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
var Listener = {
listen : // 实现自定义事件监听
unlisten : // 解除自定义事件监听
unlistenAll : // 解除所有自定义事件监听,
fire : // 触发事件发生
_events : // 相关监听的事件以及事件监听的句柄
};

// _events的结构大致如下:
_events : [
{
name : "event0", // 自定事件0
handlers : [
function(){...},// 监听的句柄
function(){...} // 监听的句柄
]
},
{
name : "event1", // 自定事件1
handlers : [
function(){...},// 监听的句柄
function(){...} // 监听的句柄
]
}
]

上面的简单的自定义事件实现方式,就可以很好的解决模块之间耦合的问题,当然也可以做得功能更加强大,比如事件触发可以加上参数支持、实现调用句柄获取当前的event对象等。

这是我三年前写的blog,原文在这里,因为这篇blog,我有幸被邀请到2012年的Velocity大会作为演讲嘉宾

本文是关于comet技术的介绍,以及网易邮箱在使用comet技术中的经验介绍和各种解决方案优劣对比。

什么是comet

Comet是一种基于web的服务器端主动推送消息给浏览器端的技术,在传统的web应用,浏览器和服务器之间的通讯,是通过“浏览器请求-服务器端返回”这个过程服务器端被动的返回信息,Comet则是服务器端主动推送消息给浏览器端,这种方式可以及时将消息传送浏览器,所以对于一些对于消息处理需要很及时的web应用就会使用到comet技术,例如:webim

Socket协议和http协议

socket包括有Flash XMLSocket、activex组件等浏览器插件,Flash XMLSocket的实现是在 HTML 页面中内嵌入一个使用了 XMLSocket 类的 Flash 程序,JavaScript 通过调用此 Flash 程序提供的套接口接口与服务器端的套接口进行通信,不过使用Socket协议的方式没有 HTTP 隧道功能,不能自动穿过防火墙,所以建议使用http协议的方式,本文也只讨论http协议的方式。

comet的实现方式

long-polling:长轮询

long-polling方式的特点有以下:

  • 服务器端会阻塞请求直到有数据传递或超时才返回。
  • 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
  • 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端才会把当前服务器端所有的信息取回。

long-polling的可选择载体:

  • 浏览器原生xhr
  • Jsonp(以html的script标签方式请求)
  • Flash xhr(随便跨域,无视浏览器兼容)
  • iframe(iframe页面方式请求)

long-polling优缺点:

优点包括:实现稍微简单,可供选择的载体多,并且浏览器兼容性好
缺点包括:轮询的方式定时请求,有数据返回后断开重新请求,这种方式消息到达可能不及时,以及浏览器端不断建立请求,导致过多资源,某些载体方式不支持post

Streaming:流方式

Streaming方式的特点有以下:

使用这种方式,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
Streaming的可以选择载体:

  • 浏览器原生xhr
  • Flash xhr(随便跨域,无视浏览器兼容)
  • iframe(iframe页面方式请求)

Streaming优缺点:

优点包括:消息到达及时,浏览器端只需要一次请求,服务器端即可实现多次推送,并且get或者post方式都无压力
缺点包括:

邮箱的pushmail实现方案对比

旧方案:

  • 长连接方式:long-polling
  • 载体:jsonp
  • 优点:实现简单,兼容性好,可以跨域
  • 缺点:只能get方式请求,jsonp方式不能实现Streaming,容易请求中断,firefox无法隐藏加载提示

新方案:

  • 长连接方式: Streaming
  • 载体:flash xhr
  • 优点:没有任何兼容性问题,可以使用Streaming的方式推送,可以跨域,可以使用post方式请求
  • 缺点:部分没有安装flash的用户无法使用,经过统计,这部分用户只占1%左右,这部分可以改用旧方案

总结

通过各种方案对比,streaming+flash xhr是最佳的选择。

  • 跨域请求:新的方案可以轻松实现跨域请求,不再为跨域烦恼。
  • 浏览器兼容:使用flash的xhr可以无视各种浏览器的原生xhr的差别。
  • 使用streaming方式:可以避免反复数据返回-断开重连的情况下对客户端运行效率问题。

这是我三年前写的blog,原文在这里

在上一篇关于跨域获取数据中,苦逼的前端开发工程师,刚才解决了一个cross domain的问题,还没有来得及沉浸在其中喜悦之际,又迎来了另外一个cross domain的问题:邮箱出现双滚动啦。

在页面中当需要加载外域app的iframe时候,最容易出现跨frame的cross domain问题,比如刚才那前端开发工程师遇到的双滚动的问题,在页面中放入一个外域的iframe,当iframe的高度大于iframe的页面的body高度时,就会出现滚动,这时候加上本身页面已经有一条滚动,那就出现经典的双滚动问题,如果iframe里面又嵌套另一个外域iframe,那可能会出现三滚动,继续嵌套..继续滚动..继续套..继续滚…,之所以出现这个问题,是因为外域的iframe不能直接调用:

parent.document.getElementById("iframe_id").style.height =   document.body.offsetHeight + "px"

这里就需要跨iframe进行cross domain,有以下提到的两个方式。
以下的例子会以这几个页面作为例子:
页面a:

http://www.a.com/a.htm

页面b:

http://www.b.com/b.htm

a的内容:

<iframe src="http://www.b.com/b.htm" id="ifrm_b"></iframe>

html5的postMessage方式

首先介绍一个浏览器原生的跨域调用方式,在支持html5的高级浏览器,支持这种方式:

oWin.postMessage(oMessage, sTargetDomain);
  • oWin为需要跨域调用的window对象
  • oMessage为传送的数据
  • sTargetDomain是跨域的frame的域

假如页面a跨域页面b,那么在页面a上调用以下进行跨域:

1
2
3
4
5
try{
document.getElementById("ifrm_b").contentWindow.postMessage(JSON.stringify({value:"this is cross call by PostMessage."}), "http://www.b.com");
}catch(e){
alert("请使用以下或者更高版本浏览器:\nIE8+, Firefox 3, Opera 9, Chrome 3和 Safari 4!");
}

然后在b,需要加一个window的message事件监听

1
2
3
4
fAddEvent(window, "message", function (o) {
var oMessage = o.data;// data属性就是postMessage的oMessage参数
alert(oMessage.value); // 这时候会显示:this is cross call by PostMessage.
});

这样就完成了跨frame的跨域通讯

  • 优点:完美的跨frame跨域调用
  • 缺点:只支持html5的浏览器

代理iframe方式

使用代理iframe的方式有两种,一种通过window.name方式跨域调用,一种是通过url参数的方式传递调用,不过两种方式的调用原理都是创建一个隐藏的iframe,iframe的url指向需要跨域的域名的一个代理页面,然后通过这个代理的iframe,和跨域的iframe通讯,因为这时代理的iframe和跨域的iframe完全同域,就可以畅通无阻进行。
在上面的例子加多一个代理页面c:
代理iframe c:http://www.b.com/c.htm

  • 在页面a加入以下函数:
1
2
3
4
5
6
7
8
9
10
11
12
function fCrossByName(sDomain, oData) {
var oIframe = document.getElementById("ifrmCross");
if(oIframe){
oIframe.parentNode.removeChild(oIframe);
}
oIframe = document.createElement("IFRAME");
oIframe.style.display = "none";
oIframe.id = "ifrmCross";
oIframe.name = JSON.stringify(oData);
document.body.appendChild(oIframe);
oIframe.src = "http://" + sDomain + "/c.htm";
}
  • 然后在页面a就可以进行跨域:
1
2
3
4
5
fCrossByName("www.b.com", {
value:'this is cross call by iframe',
func : "fCrossByNameCall", // 要跨域调用的函数
win : "ifrm_b"
});
  • 然后在代理iframe加入以下代码:
1
2
3
4
var oData = !window.name?null:(new Function('return '+window.name))();
if(oData.win && oData.func){
var oResult = (oData.win == "top" ? top : parent[oData.win])[oData.func](oData);
}

这样就可以实现整个的跨域调用,通过url参数方式跨域调用,和这个类似,只是需要将数据放到代理iframe的url参数上,而不是name。
下面这个页面demo显示这三种方式的调用(因为没加JSON的转换js包,需要使用支持内置JSON对象的浏览器运行..):
http://mimg.163.com/demo/crossdomain_test.htm

  • 优点:没有浏览器兼容问题
  • 缺点:因为要部署代理iframe的文件,所以比较麻烦,整个流程也比较复杂,如果要实现和html5的postMessage兼容的接口,需要做大量封装,就会更加造成更加复杂

总结:在邮箱实际使用过程中,当只是简单的跨域调用比如前面提到的解决双滚动问题,一般可以简单的使用第二种方式,但是如果需要复杂的双向互相调用,就需要支持html5的浏览器调用postMessage,不支持的需要第二种方法实现兼容。

这是我三年前写的blog,原文在这里

作为一个苦逼前端开发工程师,不得不面对各种cross,比如面对五花八门的浏览器我们必须cross browser,面对各种终端,我们必须cross device,在这么多年的前端开发经历中,在不同的域之间穿越中,遭受各种折磨,所以这次和大家分享的是cross domain。

这次分享的cross domain,是包括所有跨域调用,无论是跨域获取数据,还是跨域跨frame调用,所以会分为两部分,这次会先分享跨域获取数据,跨域获取数据大概有以下方式。

jsonp方式

这是最简单,也是最实用的跨域获取数据方式,原理是在浏览器端通过生成script标签,并通过js callback的形式实现跨域访问,比如一个jsonp接口是这样:

http://mail.163.com/someapp/jsonp?somequery=xxx&callback=fSomeMethod

然后服务器端会通过以下方式返回数据:

fSomeMethod({返回的json数据对象})
  • 优点:实现简单,使用灵活,并且原生cross all browser
  • 缺点:只能get方式获取数据,某些浏览器会显示加载中提示

表单post方式

这个方式会复杂一点点,原理是post表单到一个隐藏的iframe,然后iframe将数据post回来同域的一个url,这时候就可以直接调用同域的回调:

1
2
3
4
5
<form action="${backurl}" method="post">
<input type="hidden" value="返回的数据">
<input type="hidden" name="callback" value="${callback}">
</form>
<script>document.getElementsByTagName("FORM")[0].submit();</script>
1
2
3
<script>
parent.${param.callback}("返回的数据")
</script>

这样就完成整个跨域获取数据的过程
优点:支持post方式,并且原生cross all browser
缺点:实现有点复杂,并且流程有点曲折,需要两次请求,而且表单post方式会引起刷新提示的问题

服务器代理

服务器代理方式跨域调用也是使用的比较广泛的方式,原理就是在服务器端来获取跨域数据,然后在同域里ajax方式或者其他方式返回给浏览器。
优点:客户端实现简单,没有cross browser问题
缺点:需要在服务器端实现模拟请求获取数据

html5的XDomainRequest

终于在html5迎来了对跨域的ajax,泪牛满脸啊,这下完全可以通过浏览器的原生方式跨域ajax获取数据,这里一个对浏览器的各种跨域ajax的测试:
http://www.debugtheweb.com/test/teststreaming.aspx
优点:浏览器原生支持跨域方式ajax请求
缺点:只能在支持html5的高级浏览器中支持

flash request

这个是目前跨域请求最好的一个解决方案,建议在不能用jsonp的方式时候,都可以使用flash request方式,而且flash request可以配置一个安全策略,可以允许哪些域可以调用,然后被跨域的调用需要配置一个crossdomain.xml,允许可以被那个域的flash跨域调用,内容如下:

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="www.xxx.com" />
</cross-domain-policy>

这种方式的局限就是必须依赖flash,我们这边在邮箱曾经做过一个统计,99%的用户可以通过这种方式获取到数据,另外1%的用户可能是没有安装flash或者flash版本有问题。

  • 优点:依赖flash下可以无视各个浏览器兼容问题
  • 缺点:正所谓成也萧何败也萧何,缺点也是因为要依赖flash

以上是获取数据时需要cross domain的解决方案,下一篇是跨frame时候需要cross domain的分享。