使用广播事件来实现模块解耦

模块化开发往往最大的问题是解耦,怎么设计一个低耦合的组件呢?这里我用到的方法是时间总线(eventBus)方法,也可以叫做广播事件,模块之间通过发送广播的方式来实现通信,事件发起者只需要派发事件,而不必关心事件是否被接收(订阅)。

广播事件也是自定义事件的一种,不同于自定义事件,广播事件没有绑定的主体,但是都是通过观察者设计模式来写的代码。
大体的javascript实现代码如下:其中包括一些简单方法没有列出,例如$.isUndefined

var _cache = {};
var broadcast = {
    /**
     * 派发
     * @param  {[type]} type 事件类型
     * @param  {[type]} data 回调数据
     * @return {[type]}      [description]
     */
    fire:function(type, data){
        var listeners = _cache[type],len = 0;
        if(!$.isUndefined(listeners)){
            var args = [].slice.call(arguments);
            args = args.length > 2 ? args.splice(2, args.length-1) : [];
            args = [data].concat(args);

            len = listeners.length;
            for(var i = 0; i<len;i++){
                var listener = listeners[i];
                if(listener && listener.callback) {
                    args = args.concat(listener.args);
                    listener.callback.apply(listener.scope, args);
                }
            }
        }
        return this;
    },
    /**
     * 订阅广播事件
     * @param  {[type]}   types     事件类型,支持,分隔符
     * @param  {Function} callback 回调函数
     * @param  {[type]}   scope    回调函数上下文
     * @return {[type]}            this
     */
    subscribe:function(types, callback, scope){
        types = types || [];
        var args = [].slice.call(arguments);

        if($.isString(types)){
            types = types.split(',');
        }
        var len = types.length;
        if(len===0){
            return this;
        }
        args = args.length > 3 ? args.splice(3, args.length-1) : [];
        for(var i = 0;i<len;i++){
            var type = types[i];
            _cache[type] = _cache[type] || [];
            _cache[type].push({callback:callback,scope:scope,args:args});
        }
        return this;
    },
    /**
     * 退订
     * @param  {[type]}   type     [description]
     * @param  {Function} callback 假如传入则移出传入的监控事件,否则移出全部
     * @return {[type]}            [description]
     */
    unsubscribe:function(type, callback, scope){
        var listeners = _cache[type];
        if (!listeners) {
            return this;
        }
        if(callback){
            var len = listeners.length,
                tmp = [];

            for(var i=0; i<len; i++) {
                var listener = listeners[i];
                if(listener.callback == callback && listener.scope == scope) {
                } else {
                    tmp.push(listener);
                }
            }
            listeners = tmp;
        }else{
            listeners.length = 0;
        }
        return this;
    },
    /**
     * 订阅别名
     * @return {[type]} [description]
     */
    on:function(){
        return this.subscribe.apply(this,arguments);
    },
    /**
     * 退订别名
     * @return {[type]} [description]
     */
    un:function(){
        return this.unsubscribe.apply(this,arguments);
    },
    dispatch:function(){
        return this.fire.apply(this,arguments);
    },

    removeAll:function(){
        _cache = {};
        return this;
    }
};

使用方法:

broadcast.fire('event name', json);
broadcast.on('event name', function(data){
    console.log(data);
});

将nodejs打包工具整合到鼠标右键

昨天放出了主要的nodejs打包代码(《nodejs写的简单项目打包工具》),今天放出整合到鼠标右键的代码,打包需要配置环境变量,添加NODE_PATH为node安装路径,打包用到的批处理文件代码如下:

@echo off
title Builder - 正在合并 ...

color 03
REM =====================================
REM     jsbuilder beta版
REM
REM =====================================
SETLOCAL ENABLEEXTENSIONS

echo.

REM 过滤文件后缀,只combo js文件
if "%~x1" NEQ ".js" (
    echo.
    echo **** 请选择JS文件
    echo.
    goto End
)

REM 检查NODE_PATH
if "%NODE_PATH%" == "" goto NoNodePath
if not exist "%NODE_PATH%\node.exe" goto NoNodePath

set RESULT_FILE=%~n1-combo%~x1

:ZIP_CHOICE

echo 选择是否【压缩】合并后的js文件?
set input=
set /p input= -^> 请选择(y/n):
if /i "%input%"=="n" goto UNZIP
if /i "%input%"=="y" goto ZIP

REM 调用build合并文件
:UNZIP
"%NODE_PATH%\node.exe" "%~dp0build.js" --unzip "%~n1%~x1" > "%RESULT_FILE%"
echo.
echo **** ~O(∩_∩)O~ 【合并】成功 ****
echo.
goto End

REM 调用build合并并且压缩文件
:ZIP
"%NODE_PATH%\node.exe" "%~dp0build.js" "%~n1%~x1" > "%RESULT_FILE%"
echo.
echo **** ~O(∩_∩)O~ 【合并并压缩】成功 ****
echo.
goto End

:NoNodePath
echo.
echo **** 请先安装NodeJS并设置NODE_PATH环境变量 ****
echo.

:End
ENDLOCAL
pause

nodejs写的一个简单项目打包工具

项目是模块加载的,类似require.js的用法,所以简单写了一个js打包工具。
项目的模块加载和定义部分代码是这样的:

XX.define('ns',['tool/cookie'],function(){
});
//或者
XX.define('ns.ns2','tool/cookie,tool/abc',function(){
})
//或者
XX.define('ns',function(){
})

所用到的js打包工具就是扫描文件,然后匹配出来需要加载的模块,然后先加载模块代码。
主要的nodejs打包工具代码如下:

ie设置document.domain会导致ueditor拒绝访问

使用百度的ueditor富文本编辑器在ie中如果页面设置了document.domain,则会导致编辑器初始化失败,错误信息是拒绝访问,可见是跨域问题导致的。
解决的方法就是在render方法中设置src为一个代理页面,或者javascript伪协议,例如:

container.firstChild.src = "javascript:void((function(){document.open();document.domain='"
+document.domain
+"';document.write('');document.close()})())";

弹出的dialog则需要在\dialogs\internal.js 里也加上:

document.domain = '根域';

至于flash上传问题,需要放个crossdomain.xml文件,具体内容你懂得了……

关于javascript位运算符的技巧

今天说下常用的javascript位运算符的技巧

js位运算来判断奇偶数

if(n&1===0){
//偶数
}else{
//奇数
}

js位运算符来代替Math.floor

(2.9|0)===2
(~~2.9)===2
(2.9>>>0)===2
(2.9>>0)===2
(2.9<<0)===2
//注意
~~(-2.999);//= -2
Math.floor(-2.999);// = -3

RGB2HEX

function RGB2HEX(a,b,c){return"#"+((256+a<<8|b)<<8|c).toString(16).slice(1)}
//或者
function toHexString(r,g,b) {
  return ("00000" + (r << 16 | g << 8 | b).toString(16)).slice(-6);
}
var hex = toHexString(red, green, blue);

检测相等关系

if(a!=123)
if(a^123)
//注意:
false ^ 1 // 1
true ^ 1 // 0
2 ^ 1 // 3
{} ^ 1 // 1

这个很好用的,比如下面的代码

var isReady = 0;

// somewhere else
if( isReady ) { }

// somewhere else, set isReady state to 1
isReady ^= 1;

// somewhere else, set isReady state to 0
isReady ^= 1;

默认值

if (!n) n = defaultValue;
//使用下面的代码
n||(n=defaultValue);

获取a标签和script标签的绝对路径和相对路径

做了一个a标签和script标签获取绝对路径和相对路径的实验,实验代码如下:

<a id="link" href="a.html">a.html</a>
<script type="text/javascript" id="script" src='a.js'></script>
<script type="text/javascript">
    function $(id){
        return document.getElementById(id);
    }
    alert('link.href=>'+$('link').href);
    alert('link.get=>'+$('link').getAttribute('href'));
    alert('link.get0=>'+$('link').getAttribute('href',0));
    alert('link.get1=>'+$('link').getAttribute('href',1));
    alert('link.get2=>'+$('link').getAttribute('href',2));
    alert('link.get4=>'+$('link').getAttribute('href',4));

    alert('script.src=>'+$('script').src);
    alert('script.get=>'+$('script').getAttribute('src'));
    alert('script.get0=>'+$('script').getAttribute('src',0));
    alert('script.get1=>'+$('script').getAttribute('src',1));
    alert('script.get2=>'+$('script').getAttribute('src',2));
    alert('script.get4=>'+$('script').getAttribute('src',4));
</script>

分别在IE6~IE9(都是虚拟机原生,非ie9模拟)、chrome21、FF14,opera12.01,safari5.1.7中做出如下的实验结果:

√代表绝对路径,×代表相对路径(即src或者href属性的值,如果为完整的路径,则是完整路径)

方法 IE6 IE7 IE8 IE9 chrome 21 FF 14 safari 5.1.7 opera 12
link.href
link.get × × × × × ×
link.get0 × × × × × ×
link.get1 × × × × × ×
link.get2 × × × × × × × ×
link.get4 × × × × × ×
script.src × ×
script.get × × × × × × × ×
script.get0 × × × × × × × ×
script.get1 × × × × × × × ×
script.get2 × × × × × × × ×
script.get4 × × × × × ×

详情见MSDN的介绍:http://msdn.microsoft.com/en-us/library/ms536429(v=VS.85).aspx.aspx)

【PPT】那些年,我们一起跨过域

这个是入职腾讯第二月做的跨域分享,介绍了五种跨域方式,比较了他们之间的优缺点,希望可以解决小组内经常遇见的跨域问题。另外简单列举了另外五个跨域的方式,因为不经常用,所以也就简单的一笔带过了。

名字有点小清新,我想最后一个月的分享也是《那些年系列》:《那些年,我们一起跨过域》《那些年,我们错过的爱情(html5+css3)》,为什么是那些年呢,装清纯吧,感觉自己老了呗。

言归正传,我介绍的五种跨域方式是:

  1. document.domain
  2. location.hash
  3. window.name
  4. postMessage
  5. flash

以上五种跨域方式可能是现实工作中最常用的跨域方式,例如简单的跨域iframe自适应高度,你可以使用location.hash,跨子域可以使用document.domain,ppt每个方法都要详细的介绍,并且附上了我亲自写的demo哦~很有诚意是不(不要吐血),哈哈,好了又凑了一篇文章。另外附上昨天用css3画的企鹅:http://4.qdemo.sinaapp.com/qie.htm。ps:office 2010的ppt切换动画好炫哦

slideshare被墙,下载地址:http://4.qdemo.sinaapp.com/xdomain/naxieniankuayu.pptx


微盘下载:http://vdisk.weibo.com/s/6wt-O

将uglifyjs添加到鼠标右键菜单

之前几天根据YUICompressor和TBCompressor整合到鼠标右键菜单自己将uglifyjs也添加到了右键菜单,下面简单记录下过程。效果如下

uglifyjs添加到鼠标右键菜单

配置windows nodepath环境

鼠标右键我的电脑,选择属性→高级属性管理→高级选项卡→环境变量,添加新的环境变量,名字为NODE_PATH,变量值为nodejs的安装路径,例如下面

C:\Program Files\nodejs

然后保存退出

安装uglifyjs

在命令行安装uglifyjs,

npm install uglify-js -g

安装uglifyjs到鼠标右键菜单

找到第二步安装的uglifyjs的路径下的bin文件夹,例如我的安装在:

C:\Program Files\nodejs\node_modules\npm\node_modules\uglify-js\bin

然后下载uglifyjs的安装文件解压到这个文件夹

uglifyjs到鼠标右键菜单安装文件

  • 双击运行install.cmd就可以安装了。
  • 卸载运行uninstall.cmd就可以卸载。

主要的代码还是根据YUICompressor的安装文件改的,压缩的时候首先将源文件copy为dev作为备份,然后压缩替换原文件。例如a.js,压缩后变成a.dev.js原文件和压缩后的js文件。美化则是将美化后的代码存到bea.js,即a.js美化后为a.bea.js

pngfix.js可能导致xss攻击

今天看了国外大佬Spanner的一篇关于pngfix的文章,已经到下班时间,就简单说说pngfix的xss攻击。

pngfix.js

pngfix.js是IE6中解决png不透明的js方案,原理其实就是遍历了页面的img,然后使用滤镜来解决。

pngfix导致xss

pngfix.js使用了如下的代码:

var imgID = (img.id) ? "id='" + img.id + "' " : "";

img.outerHTML = strNewHTML

如果使用如下的代码就会出现xss:

<img src=1.png id="'&gt;&lt;img src=1 onerror=alert(/png_fixed!/)&gt;">

详细原文地址:http://www.thespanner.co.uk/2012/06/12/pngfix/

IE中iframe和document.selection.createRange导致跨域

帮忙在活动中找bug,两层iframe,经过调试发现是document.selection.createRange导致的错误,在浏览器中报“拒绝访问”,猜测是跨域导致的。于是加上document.domain主域就搞定了。

本来以为是两层iframe导致的(因为他们反映一层iframe没有问题),于是自己建了个demo,test.qq.com/qq1.html代码如下:

<iframe src="http://demos.qq.com/qq2.html" frameborder="0" id="myIframe"></iframe>

demos.qq.com/qq2.html代码如下:

<script type="text/javascript">
    //document.domain = 'qq.com';
    var a = document.selection.createRange();
</script>

这样在IE9中访问test.qq.com/qq1.html就会报错,而把qq2.htmlqq1.html都添加document.domain='qq.com';就可以了。暂时原因不明了,记录在此备份下,懂得朋友可以来说道说道