命令行下获取和识别时间戳的工具 timestamp.js

Github:https://github.com/shenjia/timestamp.js

作为程序员经常要和时间戳打交道,有时想快速知道当前的时间戳,往往需要运行一段小程序:

<?php echo time(); ?>

又或者想从一个时间戳得出具体的时间:

<?php echo date( 'Y-m-d G:i:s', 1361525534 ); ?>

一次两次还好,次数多了真的很烦,于是写了一段js,通过nodejs运行,再自定义一个shell函数,以后就可以这样了:

$ ts
1361525534 

$ ts 1361525866
2013-2-22 17:37:46

为MongoDB编写Js维护脚本

最近项目上有一个需求,要对存储在MongoDB中的用户行为数据定期进行统计分析。

先使用PHP实现原型,发现因为数据量很大,大量时间都花在MongoDB服务器和Web服务器之间的数据交换上。考虑到这一点,必须在MongoDB服务器上进行本地计算,将结果保存起来,再使用PHP访问并展示给用户。

查阅文档得知,MongoDB可以执行JS脚本,这样思路就清楚了,用JS脚本实现统计的功能,再用crontab定期执行。

现在和大家分享一下在完成这个任务的过程中,遇到的一些问题和解决思路。(必须要说的是,MongoDB的官方文档对服务器端JS编程的文档极度缺乏,很多命令都是通过Google才找到的。)

在命令行输出信息

输出字符串:

print( 'Hello World' );

输出对象:

var obj = { 'key' : 'value' };
printjson( obj );

切换数据库

在Mongo JS Shell中,切换当前数据库的命令是:use xxx,这是一个magic helper。遗憾的是,在JS脚本中没有use可用。

如果运行mongo时没有加上–nodb参数,那么会自动connect到指定的服务器和数据库,并把句柄保存为db。如果运行时没有指定服务器会默认连接本机127.0.0.1,如果没有指定数据库会连接到test数据库。

你可以用conncet命令重新发起连接:

var userDB = connect( '123.123.123.123/user' );
userDB.user.find();

Continue reading 为MongoDB编写Js维护脚本

不引入依赖库,细粒度管理js代码

在一个项目刚起步时,往往除了jquery之类的库外,只有一个js文件。

然而随着功能的不断增加,js代码也会不断膨胀,当多到一定程度后(500行以上),维护难度就会大大增加。组织逻辑混乱,debug时很难迅速定位到想修改的地方,写新function时也很难抉择插入的位置。整天在一个长长的js文件里滚来滚去,想想都让人头疼。

一个可选的方案是使用依赖库,诸如requireJS、SeaJS、In.js等等。但需要引入更多的代码,要抽出精力来维护依赖关系,还会增加额外的http请求。

在大多数情况下,js代码不会膨胀到必需引入依赖库的地步。有没有办法可以在不增加新代码和http请求的前提下,对js代码进行细粒度的维护呢?

在目前的项目中,除去jquery等类库后的js代码约有120kb(压缩后58kb),我们使用这样的方案:

1、按模块或者按性质将js拆分成多个文件,比如将和用户相关的部分抽取出来成为user.js、共用的方法抽取出来成为helpers.js等。

2、编写sh脚本(linux)或bat脚本(windows)合并所有js为all.js,并调用yuicompressor进行压缩生成all-min.js。

//windows下的bat脚本
del all.js all-min.js
type source\*.js > all.js
java -jar yc.jar --preserve-semi -o all-min.js all.js

3、在开发环境使用all.js(方便deug),生产环境使用all-min.js。

 

取得URL相对于另一个URL的相对路径

今天群里灰大出了个题:

function getRelativeURL(url, baseURL) {
   // 获取url相对baseURL的相对路径
   // url和baseURL都是绝对路径或都是相对路径的情况下,有可能计算出结果,否则扔Error
   // 如果都是绝对路径,但不在同一个域、同一个协议、同一个端口下,即无法计算相对路径,扔Error
}

用了点时间写了一下:

function getRelativeURL ( url, baseUrl ) {

    var path, 
        basePath,
        i = 0,
        compare = {},
        r = {    'proto' : /(^[a-zA-Z]{1,4}):\/\/(.*)/,
                'domain' : /(^[-_a-zA-Z.]+)((?:\/|:|$)?.*)/,
                'port' : /(?:^:)(\d+)((?:\/|$).*)/,
                'word' : /[^, ]+/g,
        };

    compare[ 'url' ] = parseUrl( url );
    compare[ 'baseUrl' ] = parseUrl( baseUrl );

    if ( different( 'proto' ) || different( 'domain' ) || different( 'port' ) ) return false;    //协议、域名、端口不同

    path = compare.url.path;
    basePath = compare.baseUrl.path;

    while ( path.length && path[ 0 ] == basePath[ 0 ] ) { //去除相同父目录
        path.shift();
        basePath.shift();
    }

    if ( path.length == 0 && basePath.length == 0 ) return './';     //两个url相同
    
    if ( path.length == 0 ) return parent( basePath.length );        //baseUrl是url的子目录

    if ( basePath.length == 0 ) return path.join( '/' ) + '/';        //url是baseUrl的子目录

    return parent( basePath.length ) + path.join( '/' ) + '/';        //url和baseUrl互不包含

    function different ( name ) {
        return compare[ 'url' ][ name ] != compare[ 'baseUrl' ][ name ];
    }

    function notValidUrl () {
        return compare[ 'url' ][ 'proto' ] == '' || compare[ 'baseUrl' ][ 'proto' ] == '';
    }

    function parseUrl ( url ) {
        var parsed = {};
        'proto,domain,port'.replace( r.word, function( name ){
            var match, reg = r[ name ];
            if ( !reg.test( url ) ) {
                parsed[ name ] = '';
                return;
            }
            match = url.match( reg );
            url = match[ 2 ];
            parsed[ name ] = match[ 1 ];
        });
        parsed[ 'path' ] = url.replace(/\/+$/,'').split('/');
        return parsed;
    }

    function parent ( level ) {
        console.log(level);
        var result = [];
        while ( level-- ) result.push( '../' );
        return result.join('');
    }
};

使用单元测试如下:

test("url和baseURL并非都是绝对路径或都是相对路径,没有结果", function() {
    expect(4);
    ok(!getRelativeURL('http://aoeu/ao','/aoeu'),'绝对,相对');
    ok(!getRelativeURL('/aoeu','http://aoeu/ao'),'相对,绝对');
    ok(getRelativeURL('http://aoeu','http://aoeu/ao'),'绝对,绝对');
    ok(getRelativeURL('/aoeu','/ao'),'相对,相对');
});
 
test("不在同一个域、同一个协议、同一个端口下,没有结果", function() {
    expect(3);
    ok(!getRelativeURL('http://aoc.com','http://aoeu.com'),'不同域');
    ok(!getRelativeURL('https://aoc.com','ftp://aoeu.com'),'不同协议');
    ok(!getRelativeURL('https://aoc.com:88','ftp://aoc.com:89'),'不同端口');
});

test("是否以/结尾不影响结果",function() {
    expect(3);
    equal(getRelativeURL('http://aoeu.com/aoeu/','http://aoeu.com/'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'都以/结尾');
    equal(getRelativeURL('http://aoeu.com/aoeu/','http://aoeu.com'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'url以/结尾');
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'baseUrl以/结尾');
})

test("相同目录",function(){
    expect(3);
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/'),'./','根目录');
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/aoeu'),'./','单级');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com/ao/eu/'),'./','多级');
})

test("url和baseUrl中有一个为根目录",function() {
    expect(4);
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'aoeu/','url为baseUrl的单级子目录');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com'),'ao/eu/','url为baseUrl的多级子目录');
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/ao'),'../','baseUrl为url的单级子目录');
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/ao/eu'),'../../','baseUrl为url的多级子目录'); 
!
})

test("其他",function() {
    expect(2);
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/ueoa'),'../aoeu/','单级');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com/eu/ao'),'../../ao/eu/','多级');
})

如有更好解法或发现错误,请不吝赐教:)

解决google maps api阻塞页面渲染的问题

google maps的api使用很方便,只要在用使用google maps的页面引入http://maps.google.com/maps/api/js?sensor=false就可以了。


但是在项目中这样使用后,发现domReady事件被严重阻塞(甚至会延迟十几秒)

因为domReady只有页面中所有的script都运行完后才会触发,而google的服务器往往很慢……直接用script标签来加入这个壳,会使domReady变得不可控。

为了防止阻塞domReady,对于不是必须在渲染前执行的js代码,一般使用document.createElement创建script标签,这样浏览器会在domReady后再下载这些script并执行。我一般使用这样的代码:

$.loadJs = function ( src ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';
    script.src = src;
    document.body.appendChild( script );
}

在使用$.loadJs加载http://maps.google.com/maps/api/js?sensor=false后,domReady正常了,但是google maps却不能正常使用了。

审查元素发现,壳虽然被引入了,但是依赖的子模块都没有引入进来,于是我们检查壳里加载子模块的代码:

window.google = window.google || {};
google.maps = google.maps || {};
(function() {
    function getScript(src) {
        document.write('<' + 'script src="' + src + '"' +
                   ' type="text/javascript"><' + '/script>');
    }
    .....

乖乖,居然用document.write。我们知道document.write只有在输出流还没有闭合的时候才能输出内容,而我们用$.loadJs方法加载壳的时候,输出流早就关闭了。

这个怎么解决呢,总不能改google的代码吧?曾经想过把这个壳存到本地,修改后使用,但是谁知道google哪天修改了什么东东呢?

仔细分析了一下,这个壳实际上只是设定一些参数,然后用document.write加载另一个壳http://maps.gstatic.com/intl/zh_cn/mapfiles/api-3/7/11/main.js,实际加载模块的动作是在后者中进行。

万幸,后者加载子模块使用的是document.createElement方法……

冥思苦想后,想出一个hack的方法:在调用第一个壳之前将原生的document.write方法保存起来,将其改写为调用$.loadJs,调用之后恢复原有的方法(因为write只是用了一次)。

$.writeListener = function ( ) {
  var write = document.write;
  document.write = function ( html ) {
    var scriptPattern = new RegExp( ']*src=[\'"]([^\'"<>]*)["\'][^>]*>', 'i' );
    if ( scriptPattern.test( html ) ) {
      src = html.match( scriptPattern )[1];
      $.loadJs( src );
      document.write = write;
    } else {
      write.call( document, html );
    }
  }
}
$.loadJs( 'http://maps.google.com/maps/api/js?sensor=false' );

暂时只能想到这里,哪位有更好的解决思路,请不吝赐教。

用Cookie保存和恢复进度条

有时候我们需要在一个较长的列表中选择并刷新页面,刷新后页面会跳转到最顶部,当需要连续选择的时候就会很麻烦,每次选择后都要滚动到原先位置再选择下一个。

还有在分页程序中,如果一页显示的记录比较多,导致翻页后要滚动屏幕才能点击到翻页按钮,那么每次翻页后都要滚动屏幕,当需要连续翻页的时候就会很麻烦。

在做目录树的时候我们可以用无刷新技术,但是比较麻烦,而且在记录集分页显示的时候用无刷新技术明显是不可能的。

有鉴于此,我通过Cookie实现了保存页面进度条情况,每次跳转后自动跳转到原先进度的功能,希望对一些朋友们有用。

Scroll.asp如下:

<%
‘————————
‘Project By BTwork Studio
‘Code:[BT]Ok
‘Date:2006-4-4
‘————————
%>
<script language=”javascript”>
function SaveScroll()
{
//保存进度条情况
document.cookie = “Scroll=” + escape(document.body.scrollTop);
}

function ReadScroll()
{
//读取进度条情况
var aCookie = document.cookie.split(“; “);
for (var i=0; i < aCookie.length; i++)
{
var aCrumb = aCookie.split(“=”);
if (“Scroll” == aCrumb[0])
return unescape(aCrumb[1]);
}
}

function GoScroll()
{
//跳转进度条
var Scroll;
Scroll = ReadScroll();
window.scrollTo(0,Scroll);
}

function ClearScroll()
{
//清空进度条
document.cookie = “Scroll=0; expires=Fri, 31 Dec 1999 23:59:59 GMT;”;
}
</script>

————————————————————————

在页面首部include调用Scroll.asp,然后在修改<body>:

<body onLoad=”<%If Request(“Page”) = 1 Then%>ClearScroll()<%Else%>GoScroll()<%End If%>”>

其中Request(“Page”) = 1是初始化条件,你可根据情况更换。当设定条件成立时将清空Cookie中的进度条。

最后修改翻页的连接:

<a href=”?page=<%=Page+1%>” onclick=”SaveScroll()”>上一页</a>

大功告成,试试吧。