JavaScript RegExp 正则表达式g模式的使用

说明下, 文章中的有些东西来自Franky大神的文章<浏览器中的 正则表达式陷阱>.

试考虑如下代码:

var re = /^\w$/g;
re.test(‘a’); //返回true
re.test(‘b’); //还是true吗?

猜猜返回值都是什么呢? 如果你已经阅读了Franky大神的那篇文章, 或者自己动手试了下, 就会发现, 一次是true, 一次是false. 原因在于正则表达式中的g, 使得搜索过程后, 如果匹配成功, 则记录上一次的位置, 如果匹配不成功, 则会归零. 所以, 如果在上面的语句中再加一条re.test(‘s’), 那么返回的将又会是我们期望的true了.

不过一般情况下, 我们自然会希望得到同样的结果, 这个时候可以在其中添加一条语句, 人工将位置归零, 防止这个 “错误” 的发生:

var re = /^\w$/g;
re.test(‘a’); //返回true
re.lastIndex = 0; //归零搜索的位置
re.test(‘b’); //返回true

或者我们可以更简单地直接将g去掉:

var re = /^\w$/;
re.test(‘a’); //返回true
re.test(‘b’); //返回true

于是, 利用g模式的这个性质, 可以这样使用. 考虑下面的代码:

var re = /\w/g; //注意, 我将分别表示开头的^和$去掉了
re.test(‘ab’);
re.test(‘ab’);

猜猜现在会返回什么呢? 答案是两个true. 用之前说到的东西也很好解释, 因为第一次记录了一个lastIndex, 但是在这个lastIndex的情况下, 同样能匹配成功第二个. 也就是说, 第一次匹配的是字符串中的a字母, 第二次则是b字母. 我在这里刻意地使用了两个相同的字符串, 因为我想告诉大家, 这才是g模式下, 正确的用法. 下面我们继续讨论这种机制存在的意义, 我也直接切入要害. 考虑下面的代码:

var re = /\d+/g;
var str = ‘1## %$xx 34*&920 3’; //包含了数字的字符串

var arr = [];

while (re.test(str))
    arr.push(RegExp.lastMatch);
/*
    上面的代码我更愿意写成:
    var parts;
    while (parts = re.exec(str))
        arr.push(parts[0]);
    这样可以避免全局的RegExp对象造成的一些问题.
*/

alert(arr); //”1,34,920,3″

点到这里, 相信大家已经初步明白这种机制的用意了,它方便自定义的遍历 (当lastIndex已经达到字符串末尾时, 并不归零), 要知道, 在明白这点之前, 我一直用着replace来完成这一步. 现在, 大家可以用自己的循环来搞定了. 这种情况也提醒了我们, 要正确地使用g参数, 像前面那种正则中包含^或者$的加上g参数, 就是完全的画蛇添足, 这些是应当避免的.

当然, 以上也只是Vilic的个人认识, 欢迎大家指正!

圆形碰撞算法 封装类

昨天群里有朋友问到, 顺便发下. FTP连不上, 我直接贴代码得了.

FTP弄好了, Demo地址 http://www.vilic.info/demo/collide/

沙发同学Lin.x爆料地下的代码部分半角被替换成了全角, 会报错. 如果需要代码的话, 打开Demo自己另存为吧.

groinup.js

/*
圆形碰撞算法
www.vilic.info
by Vilic Vane
©2010 Groinup
*/
var groinup = new function () {

    var array = this.array = new function () {
        this.clear = function (aimArray) {
            aimArray.length = 0;
        };

        this.remove = function (item, arr) {
            var i;
            for (i in arr)
                if (arr[i] == item)
                    arr.splice(i, 1);
        };

        this.addUnique = function (item, arr) {
            var found = false;
            var i;
            for (i in arr)
                if (arr[i] == item) {
                    found = true;
                    break;
                }
            if (!found) arr.push(item);
        };
    };

    var object = this.object = new function () {
        this.copy = function (obj) {
            var newObj = {};
            var i;
            for (i in obj)
                newObj[i] = obj[i];
            return newObj;
        };
    } ();

    var math = this.math = new function () {
        var sqr = this.sqr = function (n) { return n * n; };
        var sqrt = this.sqrt = function (n) { return Math.sqrt(n); };

        var modulus = this.modulus = function (a1, a2) {
            var x1 = a1.x;
            var y1 = a1.y;
            var x2 = a2.x;
            var y2 = a2.y;

            return (x1 * x2 + y1 * y2) / sqrt((sqr(x1) + sqr(y1)) * (sqr(x2) + sqr(y2)));
        };

        var point = this.point = new function () {
            var getDistance = this.getDistance = function (pos1, pos2) {
                return sqrt(sqr(pos1.x – pos2.x) + sqr(pos1.y – pos2.y));
            };
        } ();

        var circle = this.circle = new function () {
            this.getDistance = function (pos1, pos2, r1, r2) { return point.getDistance(pos1, pos2) – r1 – r2; };
        };

        var Position = this.Position = function (x, y) {
            this.x = x;
            this.y = y;
        };

    } ();

    var physics = this.physics = new function () {
        var sqr = math.sqr;
        var sqrt = math.sqrt;

        var getMovingDistance = this.getMovingDistance = function (v0, a, t) {
            return v0 * t + 0.5 * a * math.sqr(t);
        };

        var Velocity = this.Velocity = function (vx, vy) {
            this.x = vx;
            this.y = vy;
        };

        var Acceleration = this.Acceleration = function (ax, ay) {
            this.x = ax;
            this.y = ay;
        };
    } ();

    var collide = this.collide = new function () {

        //函数引用
        var sqr = math.sqr;
        var sqrt = math.sqrt;
        var modulus = math.modulus;

        var Circle = this.Circle = function (m, r, p, v, a, goFunc) {
            var _this = this;
            this.m = m;
            this.r = r;
            this.p = p;
            this.v = v;

            this.a = a || new physics.Acceleration(0, 0);
            a = this.a;

            var acTime = 0;
            this.resetAcTime = function () { acTime = 0; };
            this.onCollide = function () { };

            this.collideCallBack = function (p, c) {
                _this.onCollide(p, c);
            };

            this.onVelocityReverse = function () { };

            this.onSpeedUp = function () { };

            this._isOnSurface = false;

            if (goFunc)
                this.go = function (t) {
                    acTime += t;
                    if (goFunc(acTime) == false) go(t);
                };
            else
                this.go = go;

            function go(t) {
                var vx = v.x + a.x * t;
                var vy = v.y + a.y * t;

                var sqrVxy0 = sqr(v.x) + sqr(v.y);
                var sqrVxy = sqr(vx) + sqr(vy);

                p.x += (vx + v.x) / 2 * t;
                p.y += (vy + v.y) / 2 * t;

                v.x = vx;
                v.y = vy;

                if (sqrVxy >= sqrVxy0) _this.onSpeedUp();
            }
        };

        var Surface = this.Surface = function () {
            var kmax = sqrt(Number.MAX_VALUE) – 1;
            var circles = [];
            var isSearching = false;

            this.add = function (circle) {
                circle._isOnSurface = true;
                circles.push(circle);
            };

            this.remove = function (circle) {
                circle._isOnSurface = false;
                if (!isSearching) array.remove(circle, circles);
            };

            this.go = function (time, step) {
                collideSearch(time, step);
            };

            function collideSearch(time, step) {
                isSearching = true;

                var sAmt = Math.floor(time / step);
                for (var i = 0; i < sAmt; i++)
                    search();

                isSearching = false;

                function search() {
                    var i;

                    var removeList = [];

                    for (i in circles)
                        circles[i].go(step);

                    var getDis = math.circle.getDistance;
                    var len = circles.length;
                    for (var j = 0; j < len; j++) {
                        var c1 = circles[j];

                        //判断元素是否已经移除
                        if (!c1._isOnSurface) {
                            removeList.push(c1);
                            continue;
                        }
                        for (var k = j + 1; k < len; k++) {
                            var c2 = circles[k];

                            //判断元素是否已经移除
                            if (!c1._isOnSurface) {
                                removeList.push(c1);
                                break;
                            }
                            else
                                if (!c2._isOnSurface) {
                                    array.addUnique(c2, removeList);
                                    continue;
                                }

                            var d = getDis(c1.p, c2.p, c1.r, c2.r);
                            if (getDis(c1.p, c2.p, c1.r, c2.r) <= 0)
                                dealCollide(c1, c2);
                        }
                    }

                    //移除在搜索过程中被搁置的需要移除的项
                    for (i in removeList)
                        array.remove(removeList[i], circles);
                }

                function dealCollide(c1, c2) {
                    //获取相关数据
                    var p1 = c1.p, p2 = c2.p;
                    var v1 = c1.v, v2 = c2.v;
                    var m1 = c1.m, m2 = c2.m;
                    var x1 = p1.x, x2 = p2.x, y1 = p1.y, y2 = p2.y;
                    var vx1 = v1.x, vx2 = v2.x, vy1 = v1.y, vy2 = v2.y;

                    var a11 = { x: vx1, y: vy1 };
                    var a12 = { x: x2 – x1, y: y2 – y1 };
                    var a21 = { x: vx2, y: vy2 };
                    var a22 = { x: x1 – x2, y: y1 – y2 };

                    //计算两个圆圆心连线的斜率
                    var k;
                    var dx = x1 – x2;

                    //判断斜率是否存在
                    if (dx) k = (y1 – y2) / dx;
                    //如果不存在,则去尽可能大的值
                    else k = kmax;

                    //**速度分解

                    //*第一个圆
                    //圆心连线方向的分速度
                    var vox1 = apart(vx1, vy1);
                    var voy1 = vox1 * k;

                    //与连线方向垂直的分速度
                    var vpx1 = vx1 – vox1;
                    var vpy1 = vy1 – voy1;

                    //*第二个圆
                    //圆心连线方向的分速度
                    var vox2 = apart(vx2, vy2);
                    var voy2 = vox2 * k;

                    //与连线方向垂直的分速度
                    var vpx2 = vx2 – vox2;
                    var vpy2 = vy2 – voy2;

                    //碰撞后与连线方向垂直的分速度不变,连线方向的分速度变化
                    var vox1a = calculateV(m1, m2, vox1, vox2);
                    var vox2a = calculateV(m2, m1, vox2, vox1);

                    var voy1a = calculateV(m1, m2, voy1, voy2);
                    var voy2a = calculateV(m2, m1, voy2, voy1);

                    //重新复合速度
                    v1.x = vox1a + vpx1;
                    v1.y = voy1a + vpy1;
                    v2.x = vox2a + vpx2;
                    v2.y = voy2a + vpy2;

                    //告知动量变化量
                    var dp1 = sqrt(sqr(vox1a – vox1) + sqr(voy1a – voy1)) * m1;
                    var dp2 = sqrt(sqr(vox2a – vox2) + sqr(voy2a – voy2)) * m2;

                    c1.collideCallBack(dp1, c2);
                    c2.collideCallBack(dp2, c1);

                    //分解出与圆心连线方向的分速度的x方向速度
                    function apart(vx, vy) {
                        return (k * vy + vx) / (sqr(k) + 1);
                    }

                    //根据动量和能量守恒计算速度变化
                    function calculateV(m1, m2, v1, v2) {
                        return ((m1 – m2) * v1 + 2 * m2 * v2) / (m1 + m2);
                    }
                }
            }
        };
    } ();
}

index.html

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>

<html xmlns=”http://www.w3.org/1999/xhtml“>
<head>
    <title>碰撞类</title>
    <style type=”text/css”>
        #c1 { position: absolute; margin: -10px; width: 20px; height: 20px; background-color: Red; -webkit-border-radius: 10px; -moz-border-radius: 10px; -o-border-radius: 10px; }
        #c2 { position: absolute; margin: -15px; width: 30px; height: 30px; background-color: Blue; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; }
        #c3 { position: absolute; margin: -6px; width: 12px; height: 12px; background-color: Green; -webkit-border-radius: 6px; -moz-border-radius: 6px; -o-border-radius: 6px; }
    </style>
    <script src=”groinup.js” type=”text/javascript”></script>
</head>
<body>
    <div id=”c1″></div>
    <div id=”c2″></div>
    <div id=”c3″></div>
</body>
<script type=”text/javascript”>
    //使用举例

    //获取元素
    var c1 = document.getElementById(‘c1’);
    var c2 = document.getElementById(‘c2’);
    var c3 = document.getElementById(‘c3’);

    //容器
    var surface = new groinup.collide.Surface();

    //定义碰撞的圆形, 参数依次为 质量, 半径, 位置, 速度, 加速度, 搜索过程中的回调函数
    var ci1 = new groinup.collide.Circle(10, 10, new groinup.math.Position(210, 200), new groinup.physics.Velocity(40, 10), 0, null);
    var ci2 = new groinup.collide.Circle(5, 15, new groinup.math.Position(400, 220), new groinup.physics.Velocity(-50, -10), 0, null);
    var ci3 = new groinup.collide.Circle(2, 6, new groinup.math.Position(100, 320), new groinup.physics.Velocity(60, -10), 0, null);

    //向容器中添加圆形
    surface.add(ci1);
    surface.add(ci2);
    surface.add(ci3);

    //控制运动
    var i = 0;
    var interval = setInterval(setPos, 30);

    function setPos() {
        if (i++ > 100) clearInterval(interval);

        //移动, 参数分别为移动的时间和单次搜索的时间(单次搜索的时间越短, 运动越精确)
        surface.go(0.05, 0.005);

        //设置位置
        sP(c1, ci1.p.x, ci1.p.y);
        sP(c2, ci2.p.x, ci2.p.y);
        sP(c3, ci3.p.x, ci3.p.y);

        function sP(o, x, y) {
            o.style.left = x + ‘px’;
            o.style.top = y + ‘px’;
        }
    }

</script>
</html>

网页应用的未来与对InviScript的憧憬

自己弄JS的两年来,因为AIVOS和Prever Start的开发,也算有过密集JS的开发经验了,自然,在这中间,也有自己的体验与憧憬。

随着JS在前端中运用的不断延伸,它在网页中的地位越来越高,并且,更多几乎完全承载于JS的网页或网页程序出现了。由于JS以浏览器作为平台,它被赋予了跨平台的能力,成为了未来轻量级应用的一个优势选择。

我相信在未来若干年,网页应用程序会逐步发展,并最终与桌面应用并肩。当然,或许那个时候会有一个专门运行网页应用的软件,而不是像现在那么突兀地放在浏览器里。但是受限于脚本语言的效率,网页应用的范围仍然会有较大的限制。

即使如此,如之前所说,在“轻量级”的应用中,JS网页应用仍然具有不可替代的优势——随时随地随心情。不过我这里所说的“轻量级”可并不是指功能简单,而是指对效率、图形处理等要求不高。并且,网页应用的体积限制也随着本地储存的发展渐渐放宽,加上可以随用随加载,功能将会越来越丰富。

这时候,我想JS将会难以胜任更大规模的网页应用开发。JS的下一个版本或许能赋予它更强的功能,但如众多前端开发人员所恼火的,浏览器的更替是一个缓慢的过程,这时,兼容问题便无法避免了。

于是我想到了C++。

听说在C++诞生之初,还没有专门的编译器,要编译C++,就必须先把C++翻译为C语言。那同样的,我们是否能将一个强大的语言翻译为JS呢?

我把这个想象中的语言称为InviScript,除了更强大与完善的语法结构外,它还可以提供一些JS难以具备的东西,比如源代码保护。当然,尽管我看到了这样一个语言的众多优势,却对它的开发难度有些恐惧——这已经远非我一个人所能完成的了。到现在,所有的向往也仅仅是向往,但希望不久的将来,我能用InviScript开发出自己的第一款大型JS应用——Prever 2。

JS在IE和FF中的语法兼容

在前端开发的浏览器兼容工作中,我觉得主要分为CSS样式兼容和Javascript脚本兼容,而脚本兼容又分为浏览器实现的兼容和语法本身的兼容,下面我会对我所了解的JS在不同浏览器中语法的细小差别做一个归纳。

一、函数声明
函数声明(姑且这样说)有两种方法:
function abc(){};
var abc=function(){};
这两个都是通用的,只是效果有些许不同,但试想下面一种情况:
var obj={};
//直接赋值一个函数
obj.abc=function(){};
//用function关键字声明
function obj.abc(){};
这两种写法都能正确的在IE中运行,但第二种却无法在FF中使用,于是,兼容的做法就是使用第一种。

二、函数的嵌套
有时我们会把函数嵌套在if语句中,虽然我不知道这是不是一个坏习惯,但它在不同的浏览器中的确存在不同。一般情况下,如果我们使用function关键字来声明一个函数,它似乎会在一开始就完成初始化,所以我们可以在前面的代码里使用后面的函数,但函数写在if里呢?惨痛的调试经验告诉我,FF不会在if中优先初始化,也就是说,在FF的if中,function关键字声明的函数等价于var声明的函数,它们会逐语句执行。这时如果把要用到的函数写在了后面,就会不幸地报错了。不过在IE里,它是允许的。兼容的方法也就是将函数写在前面,或者写在if之外。

额,写完了才发现都是关于函数的,等我搜索完我的脑袋,要是还有就再加。

JS类的技巧 在初始化对象前添加属性

写Prever Start的时候,曾遇到过这样的问题,就是通过new关键字实例化一个对象,却希望它的原型在运行之前能具备一些外来的属性,可惜的是像这样:

var obj=new myClass();

只有一个语句,怎么添加这些属性呢?

这个我们需要从JS中类实例化的实现原理上来讲了(声明,我也只是听说,但的确管用)。听群里的高手说,上面的那个语句就等价于

var obj={};
myClass.call(obj);

我就不解释意思了,很明显的,两个代码之间的间隔给我们的操作提供了可能。

var obj={};
//在这里添加,如:
obj.ID=123;
myClass.call(obj);

不过我想一般可能用不到这样的写法,但既然曾经用到过就有它的价值,在此分享下。

JS中对象的表示方法

Javascript作为一个脚本语言,因为它的灵活性,可以用很多写法来达到同样的目的,现在我想说下接触Javascript以来所了解的对象的表示方法。

方法一

var obj=new Object();

Object貌似是JS中所有对象的原型,像上面这样的方法也最直接。众所周知的,它还有另外一个等价的写法。

方法二

var obj={};

不过它有更强的扩展性,这也是JSON的基础:

var obj=
{
Name:”Object”,
Age:0,
SayHello:function(){alert(“Hello!”);}
};

貌似很多类库的书写和对象的初始化都是使用的这种方式,但有时候,我更喜欢另一种建立在类的基础上的方法(不知道是否是原创的)。

方法三

var obj=new function()
{
this.Name=”Object”;
this.Age=0;
this.SayHello=function(){alert(“Hello!”);};
}();

从上面的例子很难看到这个方法的优点,但假如说我们这个对象里需要私有变量,或者需要有被内部多次使用的函数时,它就十分有用了。

当然,好用与否不是绝对的,根据需要选择才是王道。