FIS和FISP使用心得

fis的wiki整理的不是很好,结合最近几天的体会,整理了FIS常用的功能,加上自己对fis的理解,整理了这篇文章

FIS 和 FISP

fis是百度的开源的前端开发解决方案,另外还有个fisp(即fis-plus,前身是fis-pc),fisp可以说是百度业务定制版,主要集成(并扩展)了smarty模板,本地调试等功能。

fisp是在fis基础上做的一层封装,订制了一些 config 配置,所以一些fis的配置,在fisp里面是会自动忽略,不会生效的,比如说jswraper配置。

FIS原理

fis其实是一个 半成品 ,只有经过更多的包装(例如插件)才能发挥最大的作用,fis的核心是生成的 map.json ,很多想法可以通过 map.json 这个表来实现。比如我自己订制的require inline方式。

fisp结合 map.jsonFISSource.class.php ,对smarty语法进行了扩展,扩展出来了 html、widget、require、script等smarty自定义标签。

想了解详情的童鞋,可以直接看下fisp的 plugin

FISP环境部署

安装

  • 安装node环境
  • 配置java环境,并放入环境变量
  • 安装php环境,并且将php.exe放进环境变量

然后进入cmd,依次执行:

#安装fis-plus
$ npm install -g fis-plus
#安装fis-plus调试环境
$ fisp server install pc
# 启动fisp
$ fisp server start
# 这时应该会自动打开系统默认浏览器访问127.0.0.1
# 查看www路径
$ fisp server open
# 清理www路径,慎用,会清理掉之前安装的调试环境
$ fisp server clean

Gruntjs——前端任务管理工具

Gruntjs是前端项目构建工具,基于node的命令行工具。很多公司或者js项目都是用gruntjs来搭建,例如jQuery,twitter,Qunit等。主要功能有:

  • 合并文件
  • 压缩html/js/css/图片文件
  • 语法检测
  • 单元测试(基于Qunit)
  • watch功能

相对于Ant的打包工具,grunt更加灵活,语法采用json,比xml语法更加简洁,作为前端工程师可能更喜欢写json。

GruntJS的安装

Grunt是基于nodejs的,所以安装GruntJS之前,需要保证你的电脑有nodejs环境,node环境比之前安装简单多了,现在windows平台只需要下载node安装程序,一路next就可以了。

双屏切图:使用livereload实现自动刷新

livereload是一个web开发辅助工具,当我们修改完html、css和js的时候会自动刷新浏览器,解放码农的双手。这样在双屏切图、写js代码的时候会提高很多效率。livereload有很多版本,比如基于ruby的版本,我们今天介绍的是node+grunt+chrome插件一体化方案。

我们使用的这个livereload的基本原理是试用node开启一个websocket服务,并且检测文件变化,浏览器打开一个页面时候,引入固定的livereload.js(chrome插件会帮忙加上)会建立ws请求,当node检测到文件变化,则自动推送消息给浏览器,实现刷新。

livereload环境搭建

  • nodejs安装
  • grunt安装
  • grunt-contrib-watch:npm install grunt-contrib-watch –save-dev
  • grunt-livereload:npm install grunt-livereload –save-dev
  • chrome插件:安装

javascript实现比较相似图片算法

昨天看了阮一峰老师的文章:《相似图片搜索原理》,于是把直方图和向量那块算法用js实现了一下,

源码如下:

function getHistogram(imageData) {
    var arr = [];
    for (var i = 0; i < 64; i++) {
        arr[i] = 0;
    }
    var data = imageData.data;
    var pow4 = Math.pow(4, 2);
    for (var i = 0, len = data.length; i < len; i += 4) {
        var red = (data[i] / 64) | 0;
        var green = (data[i + 1] / 64) | 0;
        var blue = (data[i + 2] / 64) | 0;
        var index = red * pow4 + green * 4 + blue;
        arr[index]++;
    }

    return arr;
}

function cosine(arr1, arr2) {
    var axb = 0,
        a = 0,
        b = 0;
    for (var i = 0, len = arr1.length; i < len; i++) {
        axb += arr1[i] * arr2[i];
        a += arr1[i] * arr1[i];
        b += arr2[i] * arr2[i];
    }
    return axb / (Math.sqrt(a) * Math.sqrt(b));
}
function gray(imgData) {
    var data = imgData.data;
    for (var i = 0, len = data.length; i < len; i += 4) {
        var gray = parseInt((data[i] + data[i + 1] + data[i + 2]) / 3);
        data[i + 2] = data[i + 1] = data[i] = gray;
    }
    return imgData;
}

有个问题,假如图片是灰色的跟原图进行比较,那么要比较相似度,需要将图片都转成灰色的,即使用上面代码的gray函数来处理

怎么清空javascript数组

这是一个很基础的问题,但是我们往往经常做错。

当一个数组需要清空的时候,很多人使用下面的方法:

a = [];

我们知道javascript变量存储方式分为引用类型和直接量。数组属于对象,即引用类型,引用的是变量指针地址,之所以这样设计也是为了节省内存。

再说到上面的请空数组的方式,如果采用直接赋值一个新数组的方式,那么之前引用的数组可能不会被释放(有其他的引用),例如下面的代码:

var a = [2,3];
var b = a;
a = [];
console.log(b);

这时候a和b就不是同一个数组了,清空了a而b还是指到之前的引用地址,除非你是故意的,否则这将会有隐患。

所以最佳的请空数组方式是:将length设置为0,即:

a.length = 0;

javascript的类型数组(Typed Array)

在《javascript canvas图片像素》中提到了类型数组(Typed Array),javascript这是一种快速操作二进制数据的解决方案。

随着web发展和html5的推进,javascript来处理音视频、二进制文件和网络协议等都成为了可能,这时候原生的数组Array已经不能够满足需求,需要一种快速操作二进制流的东东产生,于是ArrayBuffer类型和Typed Array诞生了。

类型数组介绍

类型化数组类型表示可编制索引和操纵的ArrayBuffer对象的各种识图。所有数组类型的长度固定不变。如下表所示:

名称 大小(以字节为单位) 说明
Int8Array 1 8 位二补码有符号整数
Uint8Array 1 8 位无符号整数
Int16Array 2 16 位二补码有符号整数
Uint16Array 2 16 位无符号整数
Int32Array 4 32 位二补码有符号整数
Uint32Array 4 32 位无符号整数
Float32Array 4 32 位 IEEE 浮点数
Float64Array 8 64 位 IEEE 浮点数

html5 canvas 图片像素

html5 canvas图像转换成数组

html5的canvas可以进行图片处理,图片是由像素组成的。我们知道canvas有方法getImageData,返回的对象中可以取出,image的data 数组(类似数组的Type Array类型数组,Uint8ClampedArray)。

假设canvas宽高是500*400,那么应该有200000个像素点,而通过getImageData().data获取的data数组的length是800000。

我们还知道rgba表示颜色的方法:

  • r=red
  • g=green
  • b=blue
  • a=alpha

即红绿蓝三原色和透明值。

所以这个data数组是每四个数组值代表一个像素点。这样一来,一个图像就转化成了数据表示的数组。如下面的代码:

var canvas = document.getElementById('canvas');
var ctxt = canvas.getContext('2d');
var img = new Image;
img.onload = function(){
    ctxt.drawImage(img, 0, 0);
    var data = ctxt.getImageData(0, 0, 480, 480).data;
    console.log(data, data.toString());
}
img.src = 'img/pic.jpg';

图像数组的遍历

如上面的代码,我们可以看到数组data就是图片转换成的数组,那么遍历这个数组就可以取得图片的像素点内容:

var data = ctxt.getImageData(0, 0, 480, 480).data;
for(var i =0,len = data.length; i<len;i+=4){
    var red = data[i],
    green = data[i+1],
    blue = data[i+2],
    alpha = data[i+3];

}

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

模块化开发往往最大的问题是解耦,怎么设计一个低耦合的组件呢?这里我用到的方法是时间总线(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打包工具代码如下: