使用html5 postMessage和window.name实现多浏览器跨域

跨域是个“很古老”的问题,因为浏览器的同源策略,导致不同域名下不能进行跨域名请求数据,虽然这样设计安全了很多,但是对于大型的网站同时维护多个域名就需要进行跨域操作。例如:微博开放平台的jssdk实现的跨域请求数据,再例如weibo.com和sina.com的同步登录。

之前我也说过不少跨域的方式了,有结合服务器端的,有纯粹javascript实现的跨域,例如:《利用跨域资源共享(CORS)实现ajax跨域调用》、《用document.domain+iframe实现Ajax跨子域》、《通过JSONP实现完美跨域》。在我的上一篇文章《javascript入门到高级PPT》中也提到了跨域。
当然也有不少关于跨域的文章,例如口碑UED的文章《跨域资源共享的10种方式》等等。大家可以去阅读一下。

今天我说的html5 postMessage和window.name也不是一种新的跨域方式,因为有不少人写文章写了,而实际应用的我不知道有哪些人?不过新浪微博的新旧两个jssdk都是采用这种方式,包括之前的人人网的xd.html,当然现在的人人和facebook都是通过flash实现的跨域,这不是今天说的内容。

html5 postMessage实现跨域

postMessage是html5的一个新功能,可以实现不同域名之间的通信,通过给postMessage方式发送数据,监听则通过在父子窗口添加onmessage事件进行。
缺点也就很明显了,只有支持html5的浏览器才支持这种跨域方式,像IE6、7当然就拒之门外了!

window.name实现跨域

window.name实现跨域也是一个比较老的问题,之前kejun写过一个demo,可是给的却是同域名的通信。
其实kejun的实例中就是实现跨域的,不过他采用了同一个域名,而且过程比较崎岖:

  1. 建立iframe,指定src为被跨域的页面
  2. 被跨域文件修改window.name,将数据传给window.name
  3. 将iframe.src修改为本域代理文件,然后就可以取到contentWindow.name
  4. 进行处理数据,清除iframe
    充分的运用了window.name因为页面的url改变而name不改变的特性。
    但是如果我们是自己用,还是可以的,而如果我们放出去要别人使用我们写的东西,那样学习成本太大。

多浏览器双向跨域

为了解决上面的问题,我们使用的方法就是如果支持postMessage的浏览器就使用postMessage,如果不支持的就采用window.name的方式,幸运的是在IE6、7中支持跨域设置window.name,而我们就可以简单的通过window.name来跨域。然后建立计时器来监听window.name是否发生了变化,如果变化则接收并分析window.name,然后做请求。

javascript入门到高级PPT

前些日子参加了公司RIA的实习生和新员工的javascript培训课程,做了个PPT,今天拿来跟大家分享下,高手飘过,欢迎拍砖。
本课程分了两段,第一部分是javascript入门基础和中级教程,后面是高级,讲到了javascript的作用域链和原型链,以及javascript面向对象的继承写法,最后简单提了一下前端安全,最后简单介绍了下前端的发展和展望,要新员工开阔下眼界,同时提供了一些学习的资源和RSS源。

javascript的词法作用域

大家应该写过下面类似的代码吧,其实这里我想要表达的是有时候一个方法定义的地方和使用的地方会相隔十万八千里,那方法执行时,它能访问哪些变量,不能访问哪些变量,这个怎么判断呢?这个就是我们这次需要分析的问题——词法作用域

var classA = function(){
    this.prop1 = 1;
}
classA.prototype.func1 = function(){
    var that = this,
        var1 = 2;

    function a(){
        return function(){
            alert(var1);
            alert(this.prop1);
        }.apply(that);
    };
    a();
}
var objA = new ClassA();
objA.func1();

词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)。

下面通过几个小小的案例,开始深入的了解对理解词法作用域和闭包必不可少的,JS执行时底层的一些概念和理论知识。

经典案列重现

1、经典案例一

/*全局(window)域下的一段代码*/
function a(i) {
    var i;
    alert(i);
};
a(10);

疑问:上面的代码会输出什么呢?
答案:没错,就是弹出10。具体执行过程应该是这样的

a 函数有一个形参 i,调用 a 函数时传入实参 10,形参 i=10
接着定义一个同名的局部变量 i,未赋值
alert 输出 10

思考:局部变量 i 和形参 i 是同一个存储空间吗?

2、经典案例二

/*全局(window)域下的一段代码*/
function a(i) {
    alert(i);
    alert(arguments[0]); //arguments[0]应该就是形参 i
    var i = 2;
    alert(i);
    alert(arguments[0]);
};
a(10);

疑问:上面的代码又会输出什么呢?(( 10,10,2,10 || 10,10,2,2 ))
答案:在FireBug中的运行结果是第二个10,10,2,2,猜对了… ,下面简单说一下具体执行过程

javascript变量类型

javascript的变量类型真的很要人纠结,即使你是很有经验的js工程师,你也很难说清楚js的类型和分类。
最近在讲javascript入门指南的时候,有人提出数组为什么不是基本类型,我通过演示typeof []的结果来说明,数组是对象类型派生出来的,而不是六种基本类型。
其实要理解javascript的变量类型很简单,就是我们要找到一个划分的标准。

javascript变量类型分类

在很多书中都提到了javascript的变量类型,每本书都有不同的划分标准,如果按照typeof和instanceof的返回值来区分,可以把javascript的变量类型分为两套类型系统:基本类型和对象类型衍生出来的对象类型系统。
基本类型包括:undefined,number,boolean,string,object,function,他们之前通过typeof的返回值来区分。
第二套对象类型系统是由第一套系统衍生发展而来的,例如前面提到的Array,还有Null,Number,Boolean等等,对象类型可以通过instanceof来判断。
那么对象类型中的Number和基本类型中的number又是什么关系呢?他们又是什么区别呢?
答案就是,他们是映射关系,即下例:

var a = new Number(123);
console.log(a.valueOf()===123);//true
var b = new String(123);
console.log(b.valueOf()===123);//false
console.log(b.valueOf()==='123');//true

obj.valueOf()返回的是该对象的原始值。

值类型和引用类型

IE的fireEvent方法

在IE中提供了一个fireEvent方法,顾名思义就是触发某个事件发生的意思。刚开始我以为是会跟平时使用onclick()一样,没想到最近在写javascript入门ppt的时候发现了,原来自己太自以为是了!看来还有很多javascript的细节没有掌握好啊!

现在根据自己的总结详细的记录下fireEvent方法的使用。fireEvent是IE提供的一种方法,msdn文档地址:http://msdn.microsoft.com/en-us/library/ms536423(v=vs.85).aspx

onclick()

我们先看第一段实例代码:


*   i am one;
*   i am two;
*   i am three;

这段代码中我们没有个id1的li添加onclick事件,点击button,会报错,提示“对象不支持此属性或方法”。由此可见,DOM.onclick()需要添加onclick事件之后才能使用。 假如我们把以上的代码修改为:

*   i am one;
*   i am two;
*   i am three;

javascript匿名函数

今天在整理javascript入门培训的PPT时,提到了匿名函数,所以拿来分享下心得。

匿名函数的写法

顾名思义,就是没有名字的函数(⊙﹏⊙b汗)。匿名函数通常用于javascript作用域的控制,可以有效的避免对全局变量的污染。常见的匿名函数有下面四种写法,欢迎补充:

匿名函数写法一


(function(){
    //do something
})();

匿名函数写法二


!function(){
    //do something
}();

上面!还可以写成+,!!等多种方式。
上面两种方法是我常用的,下面两种方法是google上找的,我没用过(好吧,我out啦)。

匿名函数写法三


(function(){
    //do something
}());

匿名函数写法四


void function(){
    //do something
}();

刚才微博上@朴灵 提出来:写法三比较安全,正如网友@Rain的留言,匿名函数上面的写法都存在前后文;问题,所以需要注意的是匿名函数在压缩工具打包压缩后会出现上下文错误合并()的问题,例如第一种写法,如果下面的代码,未压缩之前是正常的,压缩后就不正常了,所以要严格上下文的;问题,而第三种就不会出现类似问题:


var a = function(){}
(function(){
    alert(1);
})();

上例中就出现了错误,这就是因为a函数会把他后面的匿名函数作为参数传入a中,这也就解释了为什么有人习惯在匿名函数之前添加;了,就是为了防止上文没有严格遵循javascript语法,漏掉;的问题。

IE6中a标签location.href失效解决方法

今天遇见IE6一个location.href的bug,具体情况是这样的,IE6下,在a标签中,将href写成javascript:; javascript:void(0);,并且给这个标签绑定onclick事件,点击后,执行location.href实现页面跳转,例如下面的代码:

<a href="javascitp:;">点击跳转</a>

或者

<a href="javascitp:void(0);">点击跳转</a>

toURL函数的代码如下所示:

function toURL(){
    location.href = "http://js8.in";
}

这样在非IE6浏览器下都可以使用,但是在IE6下就是跳转不了,而且不报错,在location.href之后的代码,例如(alert(1);)也是执行的。

IE6下location.href失效解决的方法

解决IE6中a标签中location.href失效的方式就是a标签中的href不用使用javascript:;javascript:void(0);。具体原因还不清楚,不过我们可以使用href=”#”来代替。

javascript事件:获取事件对象getEvent函数

javascript开发中我们会经常获取页面中的事件对象,然后来处理这些事件,例如下面的getEvent函数就是获取javascript下的页面事件对象。


function getEvent(event){
    return event || window.event;
}

我们使用getEvent的时候可以这样调用。


function foo(event){
    var evt = getEvent(event);
    alert(evt);
}

并且把foo函数绑定到一个onclick事件上。
但是常用的是foo函数不会有参数,或者第一个参数没有传入,我们可以通过arguments[0]来获取第一个参数。
在IE中event是一个全局变量,即window.event,而在Firefox等浏览器,事件会作为第一个参数传入foo函数,所以getEvent可以改为:


function getEvent(event){
    return arguments[0] || window.event;
}

这时在一些版本的浏览器,例如Firefox 中会出现获取不到event对象的问题,其实我们可以看成事件的触发是下面的方式:


function onclick(event) {
    foo();
}

这样,第一个函数是onclick,即event会默认作为第一个参数传入,而foo函数没有将event作为参数传入,这样严重限制了getEvent的灵活性,所以我们需要修改下getEvent函数。

新浪微博jsSDK操作指南

新浪微博的开放平台提供了jsSDK的方法,但是很多人反映不会使用,这里最关键的一点是不会放跨域文件。
的确,我也承认开放平台的开发文档写的不是很详细,比如对于一些接口是使用REST的,要人云里雾里,而对于REST的介绍却很隐晦,我也是尝试了几次才找到的方法(例如删除微博接口)。
下面简单说说jsSDK的操作。

放置跨域文件

为了实现跨域请求api的接口,我们需要在自己的域名下放置一个xd.html的文件,这个文件可以理解成在你网站做代理的,通过它才可以请求道api.t.sina.com.cn的内容。感兴趣的童靴可以研究下这种跨域的实现方式,国内的人人、腾讯,国外的facebook都是这种跨域方式。

基本原理是通过html5的postMessage和window.name来实现跨域。其实如果进一步研究是完全可以不要这个跨域文件的!相信下一个版本的JSSDK不会出现跨域文件来误导大家。
跨域文件放置要点

  1. 放置来应用的同域名下,例如你的应用网址是js8.in,则你的xd.html文件就要放在js8.in域名下
  2. 不一定是根目录下,因为可以通过jsSDK中的xdpath来传入xd.html路径,默认是根目录,即js8.in/xd.html

javascript继承的写法

严格来说javascript是基于对象而不是面向对象的语言,因为javascript没有像java语言那么丰富的类、继承、封装,但是javascript是基于原型(prototype)的面向对象开发,是一种动态、弱类型、基于原型的语言。前几天看了阿里UED的《重温javascript继承机制》,今天重新看了,很有感触,特地转载了重要的部分,记录一下,下次有需要的时候还要重新看一下!的确是不错的一篇文章。

继承的演变

1、采用new关键字生成实例

处理表单验证这样简单功能脚本语言显然是不需要”继承”机制的,然而如果Javascript里面都是对象,就需要有一种办法来把所有对象联系起来。最后,Brendan Eich还是设计了”继承”。只是,他并没有引入”类”(class)的概念,因为一旦有了”类”,Javascript就是一种完整的面向对象编程语言了,
这好像有点太正式了,与设计初衷也远了,同时增加了初学者的入门难度。
参照到C++和Java语言都使用new命令来生成实例:

C++这样写:


> ClassName *object = new ClassName(param);

Java这样写:


> Foo foo = new Foo();

那么,也可以把new命令引入了Javascript,用来从原型对象生成一个实例对象。但是,Javascript中没有”类”的话,怎样表示原型对象呢?
依然是参照C++和Java使用new命令时,都会调用”类”的构造函数(constructor)。Brendan Eich简化了设计,在Javascript语言中,new命令后面跟的是构造函数,不再是类。
我们举个例子来说,现在有一个叫做WD构造函数,表示前端开发(web-developper)对象的原型。


> function WD(skill){
>     this.skill = skill;
> }

对这个构造函数使用new关键字,就会生成一个前端开发对象的实例。


> var WD1 = new WD('html');
> console.log(WD1.skill); // html

在构造函数中的this关键字,它其实代表的是新创建的实例对象。

2、new 出来对象的缺陷

采用new关键字,用构造函数生成实例对象无法共享属性和方法。
比如,在WD对象的构造函数中,设置一个实例对象的共有属性skill。


> function WD(skill){
>     this.skill = skill;
>     this.sex = '男';
> }

然后,生成两个实例对象:


> var WD1 = new WD('html');
> var WD2 = new WD('css');

这两个对象的skill属性是独立的,修改其中一个,不会影响到另一个。


> WD1.skill= 'Javascript';
> console.log(WD2.skill);//“css”,不受WD1的影响

每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。

3、引入prototype属性

为了实现属性和方法的共享,Brendan Eich决定为构造函数设置一个prototype属性。
这个属性包含一个对象(以下简称”prototype对象”),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
还是以WD构造函数为例,现在用prototype属性进行改写:


> function WD(skill){
>     this.skill = skill;
> }
> 
> WD.prototype = { sex : '男' };
> 
> var WD1 = new WD('html');
> var WD2 = new WD('css');
> 
> console.log(WD1.sex); // 男
> console.log(WD2.sex); // 男

现在,sex属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。


> WD.prototype.sex = '女';
> console.log(WD1.sex); //女
> console.log(WD2.sex); // 女

由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像”继承”了prototype对象一样。这就是Javascript继承机制的设计思想。

三、构造函数如何实现继承

现在有一个”MED”对象的构造函数(MED:Marketing Experience Design,营销体验设计)


> function MED(){
>     this.aim = "营销体验设计";
> }

依然是”WD”对象的构造函数,


> function WD(skill,sex){
>     this.skill = skill;
>     this.sex = sex;
> }

怎样才能使”WD”继承”MED”呢?