Let's "call" around!

记JavaScript的种种神奇.

自己博客更新频率越来越低了, 很大程度上, 是因为技术上的收获不再有那么多, 另外即使有, 也不是特别容易归纳或者表达亦或者常用的技巧, 所以也没必要写到博客上来. 不过, 还是想写一篇, 说说怎么玩JavaScript.

标题是 <Let’s “call” around!>, 所以什么是call? 既然玩的是JavaScript, call自然不能少. 当然, 本文包含但不仅限于call, call在这里代表的, 应该是一类东西.

那, 什么时候, 我们会使用call或者apply呢? 做个不完全归纳吧.

  • OOP.
  • 函数劫持.
  • this绑定.

OOP

其实这个应该算在this绑定里, 但是在OOP中, 又运用得特别多, 于是单做一类. 假设有一个class, MyClass. 那么创建一个实例 var mc = new MyClass(), 在某些时候, 可以做如下替换. var mc = {}; MyClass.call(mc); 当然, 他们并不是等价的, 而且差距还蛮大. 但有些时候, 从功能上讲, 是可以代替一部分的. 不过在这里, 由于建立对象和调用构造函数分成了两部分, 我们就可以在其中增加一些trick. 至于什么trick, 就不多做讨论了.

函数劫持

这个时候用apply可能偏多吧, 毕竟能传递几乎所有的参数, 也方便些. 如之前存在函数fn. 现在要劫持它, 那么就可以 var fnBackup = fn; fn = function () { fnBackup.apply(this, arguments); }; 好吧, 这个仍然牵涉到了this…

this绑定

于是, 就说说单纯的this绑定咯. 对于Web前端开发来说, 在事件处理上, 一定会很熟悉. 当然对于我, 就更熟悉啦, 嘎嘎嘎. 因为写到的很多东西里, 我都会通过this绑定一些方法什么的, 供函数使用, 比如VEJIS的类有这样的写法:

var C = static_class_(function () {
    this.public_({
        xxx: 123
    });
    this.private_({
        yyy: 456
    });
});

匿名函数配合this, 很好用!

OK, 说完了call和apply, 说说callee和caller. callee大家应该很熟悉, 不过caller就… 其实实话, 我也不是很熟, 但是觉得很有意思. 虽然现在还基本没用到过, 以后就说不定咯. 用法请arguments.callee.caller.caller.caller… 自己试试咯.

然后关于正则. 先说match. 在非global情况下, 我习惯这么用.

var result = string.match(regexp) || [];

一个JS里很常见的技巧不是么, 如果我想得到整个匹配的字符串, 那我会这么做. (string.match(regexp) || [”])[0]. 当然, 这个也很简单, 就看有没这个习惯了. 使用这种方式能省不少代码哦.

那说说exec. 这是我最近两年来比较喜欢的方法, 在global模式下非常管用. 我习惯的用法类似:

var groups;
while (groups = regexp.exec(string)) { }

关于instanceof. 这个是JS的一个好东西啊, 这个运算符给了JS这种弱类型语言处理强类型工作的能力. 既然说到它, 就不得不说说继承.

JS中如何实现继承? 其他的方法就不在赘述了, 推荐我比较喜欢的一种:

//更新, 之前的方法有问题, 今天看到TypeScript的做法之后借鉴下.
var Class = function () {};
function __() { this.constructor = Class; }
__.prototype = Base.prototype;
Class.prototype = new __();

var c = new Class();
var o = {};
Base.call(o);
copy(o, c); //这儿就伪代码了, 把o上面的东西拷贝到c上, 根据需要覆盖或不覆盖.

当然… 这样写有点长, 于是, 请自行封装… 使用VEJIS当然也是可以的, 简单多了:

var Class = class(function () { }).inherit_(Base);

这种写法的优势是什么呢? 相对单纯的prototype继承, 它能够更好地保证成员的独立性, 相对单纯的工厂模式, 它能保证instanceof为true. 代价稍大, 但可以忽略.

关于参数的合法性, 有时候我们的代码是给第三方用的, 同一个方法, 或许我们在内部调用的时候, 需要使用某个参数, 但却不希望第三方使用这个参数, 这个时候怎么办? 有人会说, 约定好就好了. 但显然约定只是自我安慰. 我是一个不喜欢暴露变量的人, 不该暴露的东西绝不暴露, 不该给别人用的东西绝对不能给别人用. 于是推荐的方式是, 在自己可控的闭包内, 创建一个类, 如下:

function Value(value) {
    this.value = value;
}

然后再验证参数的时候, 使用instanceof就可以了. 因为Value这个类在闭包内, 只要不传出去 (当然, 也不能把它的实例传出去), 就是安全的.

逻辑运算符的妙用. 这个场景不多见, 也只能是抛砖引玉. 场景是这样的, 通过XHR请求到一些脚本, 脚本内容是调用了一个函数, 现在我需要通过eval来得到这个函数的返回值. 特殊之处在于, 如果前面有注释怎么办? 用正则清除掉? 可行, 但是不大好吧… 于是前后加括号? 万一结尾有分号呢? 于是, 最后我采取的方法如下: var value = global.eval(‘(function () { return false || ‘ + script + ‘ })()’);

困了… 睡觉去.