Javascript hashCode 函数

网上找了好一轮,找到个比较像样而且足够短的 hashCode 实现,是从 Java 的 hashCode 中借鉴而得的。原理见 Java hashCode() ,也可以跟这里的 Java String 的源码 参照对比一下。

为了使用的方便,稍稍再改良了一下

function hashcode(str) {
  var hash = 0, i, chr, len;
  if (str.length === 0) return hash;
  for (i = 0, len = str.length; i < len; i++) {
    chr   = str.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
}

hashcode("this is a string")
//-1853110172

这里接受的参数是一个 String,其它类型怎么办?可以先做一个统一的处理,比如

hashcode(JSON.stringify(obj))

序列化之后再使用 hashCode 函数,基本所有类型数据都通吃,除了含有循环嵌套的对象。

PS:
函数实现中有一行使用了 “|” 运算符,只是利用 Bitwise 运算符转换参数为 32bit,用来确保结果是个 32位整数。

如何生成唯一且不可预测的 ID

通常数据库可以生成唯一的 ID,最多的就是数字序列,也有像 MongoDB 这样产生组合序列的,不过这种形式的 ID 由于是序列,是可以预测的。如果想得到不可预测且唯一的 ID,方法还是有的。

下面主要以 Node.js 的环境为例。

Node-uuid

Github 上有个 node-uuid 项目,它可以快速地生成符合 RFC4122 规范 version 1 或者 version 4 的 UUID。

安装,既可以通过 npm install node-uuid ,也可以在 package.json 中定义。使用方式:

var uuid = require('node-uuid');

// 产生一个 v1 (基于时间的) id
uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'

// 产生一个 v4 (随机) id
uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1’

听起来很有保证,只是……有点太长了。

坊间也有一些在 UUID 基础上随机截取几位的办法来组成一个新的短一点的字符串,只不过唯一性就又重新成为一个问号了。

Hashids

另外一个方法,来自 hashids,它的原理就是从数字经过一个加盐(salted)算法产生一个哈希(hash)字符串。这样算法就是通过混淆使结果具有不可预测性,而唯一性依然由数字本身来达成,从而得到(类似 youtube 里的)足够短,不可预测且唯一的 ID。

安装方式类似,只是 hashids 建议使用一个固定的版本。比如在 package.json 中

"dependencies": {
  "hashids": "0.3.3"
}

使用方法也很简单。先设定一个字符串作为 salt。

var Hashids = require("hashids"),
    hashids = new Hashids("this is my salt”);

然后对数字(准确来说是数字数组)进行编码

var hash = hashids.encrypt(12345);
// Nkk9

var hash = hashids.encrypt(683, 94108, 123, 5);
// aBMswoO2UB3Sj

对数组编码可以有很有趣的用法,比如需要在一个分布式的环境里使用,可以使用机器编号 + 序列数字组合数组然后再编码。

有编码就有解码

var numbers = hashids.decrypt("NkK9”);
// [12345]

编码的输出会随着数字的增大而变长,为了达到足够混淆,它可以指定最少长度。

hashids = new Hashids("this is my salt", 8);

var hash = hashids.encrypt(1);
// gB0NV05e

还有其它选项,比如控制输出所使用的字符,编码 MongoDB 的 ObjectId 等等。

上面介绍的两个项目,各有各优缺点,不过目的是一致的,可以根据实际情况选用。两个项目在其它语言上也有类似的实现。

Javascript void(0)

JavaScript-logo常有人疑惑一个看上去很简单的问题,<a href=”javascript:void(0)”>some button</a> 里面的 void(0) 是什么意思?又或者不明白为什么好像很多人要这么写而不是其它更加简洁的写法,比如 <a href=”#”>some button</a>

href=”javascript:void(0)” 实际上是为了在 link 被点击的时候,返回一个空值,告诉浏览器什么都不用做。因为浏览器会把当前页面里的内容替换为返回的结果。(这个效果反而让我感到奇怪,因为真的很少这么用)

而 href=”#” 看上去简洁,但是它其实有个默认的行为,会把页面重新定位到页面顶部,这就是为什么页面会跳一下的一个原因。因为更多的情况,点击后页面保持在原位才是我们想要的结果。使用 # 甚至可能出现一些更加奇葩的问题

除了这个,还有没有其它更加简洁的写法呢?会不会是 <a href=””>some button</a> 呢?这个点击后会刷新当前页面,显然不符合要求。应该是 <a href=”javascript:;”>some button</a> ,什么都不执行。但有时过分简洁也容易造成“不清晰”的印象从而引起不必要争拗。如果从代码可读性考虑,也为了团队内沟通少点分歧,我会建议使用 href=”javascript:void(0)”。

void 操作符最原始就是用来“制造” undefined ,特别是某段代码只需要其副作用(side effect)又不希望有返回值产生。void 本身没有副作用,也没有正作用(返回值是空),但却能包含一切,犹如黑洞一般神秘。

== 参考 ==
Void Operator in MDN

jQuery SelectAll Plugin

这是一个处理 全部选中/不选 的插件。

这个插件节省不了你太多代码,但是能帮助你分离处理逻辑。通过在改变选择项时,向 controller 发送一个 “change:select” 事件(还附带 selected 和 total)来实现。这样你就可以灵活地选择在什么地方处理这个事件。

controller 不一定是 jQuery 对象,也可以是 Backbone.Events 的派生对象。

jquery-selectAll.js

Replace [class*=”span”] in LESS

Bootstrap 里的 grid system 里面 (源代码) 有这么一段,

[class*="span"] {
  float: left;
  margin-left: @gridGutterWidth;
}

RECESS 去跑会看到提示:Universal selectors should be avoided。这个既可以说是 selector 的问题,也可以说是 RECESS 的问题,但可以在运行的时候加个参数忽略掉。

自己去写扩展时也会写到类似的规则,比如需要兼容某浏览器,如果不这样写,就会需要像 Bootstrap-IE6 那样写 Continue reading

不错的 JavaScript 格式

在重制 在线密码生成器 这个单页工具时,尝试的一种交互方式需要 tooltip (来自 Bootstrap) 作为操作提示的载体,动态修改 tooltip 的内容。于是查阅了其 源代码,虽然没发现有这样的功能支持(后来小改了一行之后就可以了),但是却发现其代码格式有些有趣的地方。

Tooltip.prototype = {

  constructor: Tooltip

, init: function (type, element, options) {
    var eventIn
      , eventOut

    this.type = type
    // ...
    this.options.selector ?
      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
      this.fixTitle()
    }
// ...
}

可以注意到下面几点: Continue reading

Javascript Object 杂谈

看到 一个论坛帖子 问到:(类似下面的代码)

var arr = [1, 2, 3];
arr.a = 'a';
arr.b = 'b';

为什么可以这样写?arr 自身到底是什么?

原因其实很简单,因为 js 里一切都是对象,array 也是 object,所以它就能做所有 object 能做的事情,比如,给它加个属性 a

如果 Array 能那么搞,BooleanDate, Function, NumberRegExpString 也可以就不足为奇了。

function toStr(obj) {
  return Object.prototype.toString.call(obj); 
}

function addAttr(arr) {
  for(var i = arr.length-1; i >= 0; i--) {
    arr[i].attr = toStr(arr[i]);
  }
  return arr;
}

function checkAttr(arr) {
  for(var i = 0; i < arr.length; i++) {
    console.log(typeof arr[i], arr[i].attr);
  }
}

var arr = [
    new Boolean(false), 
    new Date(), 
    new Function(), 
    new RegExp('\\w+'), 
    new Number(1.0), 
    new String('str')];
checkAttr(addAttr(arr));

/** output >>
object [object Boolean]
object [object Date]
function [object Function]
object [object RegExp]
object [object Number]
object [object String]
*/

细心的人会发现上面都是使用 new 构造的对象,如果换成是字面方式 (literal) 定义的原生类型 (primitive) 又会是如何呢? Continue reading

Number 的失真边界

Javascript 中的 Number 是个神奇的类型,它模糊了 整形浮点数 之间的边界,当你拿到一个 Number 的时候,它就只是一个 Number,而且可以很方便地表示非常大的数 Number.MAX_VALUE 或者,或者非常小的数 Number.MIN_VALUE。
实际应用场合里面除了要表示大数和小数,有时还会讲究下精度的。

MDN 的 Number.toPrecision 中有描述 ECMA-262 only requires a precision of up to 21 significant digits. Other implementations may not support precisions higher than required by the standard.”

我们可以知道 JavaScript 的精度在 21 位。究竟是不是代表只要少于 21 位的数都能精确表示呢?简单来个试验就可以知道了。可以猛击这个链接 看看你的浏览器里的 js 到底能有多精确

var arr = [];
for(var i = 0; i< 21; i++) {
  arr.push('9');
  console.log((i+1) + ' : ' + arr.join('') + ' >>' + Number(arr.join('')));
}

我在 Firefox 11 上的结果是到 16 位的时候就已经失真了。

如果是小数的情况:

var arr = ['9.'];
for(var i = 0; i< 21; i++) {
  var str = arr.join('') + '9';
  push((i+1) + ' : ' + str, Number(str));
  arr.push('9');
}

同样地,小数位达到 15 位时出现失真。上面的试验用的是 Number() 进行转换,这与手写同样长度的数字效果是一样的。

幸运的是,这些精度基本够用了。但如果哪位仁兄希望用 JavaScript 来处理一些高精度方面的问题,就必须得注意了,其实 15 位已经是一道坎了。

函数抽象

JavaScript 里 Array 非常常用。还记得当初是因为直接字符串拼接效率好差,但是用 […].join(”) 却神奇地快而使我印象深刻。

在 JavaScript 1.6 里还引入了一些新的方法,虽然有些看上去好像没什么特别,甚至会使人疑惑为什么需要多这么个方法。我很好奇这是基于什么设计出来的。

忘记了是从哪里看来的,说一些方法是为了可以支持函数式编程而引入的。比如 forEach, filter, every, map, reduce

实际使用过后,还真是尝到甜头了。最直接的感觉就是,代码变短了(不是字符数,而是独立的语法单位数量)。比如 every

如果没有它,你需要写这些代码

var arr = [15, 20, 30];
var isbigger = true;
for (var i = 0; i < arr.length; i++) {
    if (!(arr[i] > 10))  {
        isbigger = false;
        break;
    }
}
isbigger === true

有了它,你可以这样写

var arr = [15, 20, 30];
arr.every(function(it) {
    return it > 10;
}) === true;

少定义了一个变量 isbigger, 没有了 for 循环,自然也不需要考虑什么时候 break 了。需要做的判断可以非常清晰地用 it > 10 表达出来。真是简单明快。

或许想要表达的就是这种抽象方式,将某种特定过程抽象成一个 every,使用者可以只关心所需要的逻辑。而这个逻辑是用一个函数来表示的,作为参数传入到 every 当中去。由于 JavaScript 支持函数作为参数,使得使用函数来抽象非常的方便,不需要额外构建一些模式来进行胶连。如果从这个角度看,的确很符合函数式编程的模式。

再看看 every 的实现,和第一段代码的主体是非常的相像的。

/**
 * Copyright (c) Mozilla Foundation http://www.mozilla.org/
 * This code is available under the terms of the MIT License
 */
if (!Array.prototype.every) {
    Array.prototype.every = function(fun /*, thisp*/) {
        var len = this.length >>> 0;
        if (typeof fun != "function") {
            throw new TypeError();
        }

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in this && !fun.call(thisp, this[i], i, this)) {
                return false;
            }
        }

        return true;
    };
}

就这样一个独立功能的简单函数就可以实现关注点的分离。而且,分离之后,各自都可以很好地测试,关注点的单一,使得测试更加容易进行。功能变得简单(专注)了,少打了许多字,代码更加清晰易懂,出错的几率也相应地减少。出错少了,你会感觉是自己变聪明了(Ruby 创始人在一次演讲中说到的一个很有趣的观点)。

同样的原理,我觉得是可以很方便地应用到自己编写的代码当中去,仔细分析代码,分离不同的关注点,是实现自下而上的编程方式的重要一环。

也许看上去很简单,但是我觉得这种抽象方式作为一个很好的基础,可以帮助我们设计出更好的代码。