Gong Yong的Blog

fast.js和它对应的javascript内置函数

fast.js是一个javascript函数库,它重写了一些javascript的内置函数,像forEach()、map()、reduce()、apply()之类的函数,还提供了一些常用的函数,例如clone()。fast.js是一个较新的项目,当前还处于开发阶段,暂时只能用于nodejs的环境中,不太适合在浏览器环境中上使用。

fast.js所提供的函数的性能比起对应的内置函数的性能要好很多,你可以看看它的benchmark。它也比提供类似功能的其他函数库(例如underscore)的性能要好。这篇文章主要是介绍怎么使用这些函数以及它对应的内置函数。

apply

var fast = require('fast.js');
function fn(a,b) {
    console.log(a+b);
}

//builtin way
fn.apply(null,[1,2]);

//fast.js way
fast.apply(fn,null,[1,2]);

apply()的内置函数是Function.prototype.apply(),它是函数对象的原型函数。

原生的apply()会被一个函数对象调用,它接受两个参数,第一个参数可以用于改变函数执行时的this对象,用于改变函数执行的上下文;第二个参数是要传递给函数的参数,类型是一个数组,数组中每个元素代表一个参数。

fast.apply()接受三个参数:第一个是函数对象,第二个是改变函数this对象的参数,第三个才是传递给函数的参数,类型也是数组。

assign

var fast = require('fast.js');
var obj1 = {a : Math.random()};
var obj2 = {b:Math.random(),c:Math.random()};

//builtin way
Object.assign(obj1,obj2);

//fast.js way
fast.assign(obj1,obj2);

assing()的内置函数是Object.assign()函数,这个是ECMAScript 6中才支持的函数,由于ES6还没有正式发布,所以目前还只是一个实验性的产品。

assing()可以对对象进行合并。它可以包含任意多个参数,第一个参数是目标对象(target object),其他的参数是源对象(source object),它会将源对象的数据拷贝到目标对象中,最终返回目标对象。

原生的assign()函数的语法格式为:

Object.assign(target,...sources)

fast.assign()的用法跟原生的assign()的方法一致。

这个函数的使用有一些规则:

  1. 这个函数只会拷贝源对象中的可枚举(enumerable)属性和自有属性(own properties,非原型链上的属性)

  2. 它会在源对象上使用[[Get]],而在目标对象上使用[[Put]],所以它会调用getters和setters(请参见Object.defineProperty()函数)。因此这个函数采用了赋值的方式,而不是简单的复制和定义一个新属性。

  3. String类型和Symbol类型的属性都是直接复制。

  4. 如果在赋值过程中发生错误,例如某个属性不可写,那么调用assign时就会产生TypeError错误,不过赋值还是会继续,错误会在赋值结束后抛出。

  5. 如果源对象的属性值为undefined或者null,Object.assign不会抛出任何错误。

bind

var fast = require('fast.js');
function fn(a,b) {
    return a+b;
}

//builtin way
fn.bind(this,1,2);

//fast.js way
fast.bind(fn,this,1,2);

bind()的内置函数是Function.prototype.bind()函数,跟apply()一样,它也是函数对象的原型函数,而且它的功能跟apply()函数也有点类似。

apply()会立即执行调用它的函数,而bind()会根据调用它的函数创建一个新函数,留作以后执行。

bind()函数接受任意多个参数。第一个参数是this对象,跟apply()一样,可以改变函数执行的上下文;其他的参数将是新函数的参数,跟apply()不同的是,它用的是可变参数列表来传递参数,而不是数组。

下面是一个使用fast.bind()的示例:

var fast = require('fast.js');

this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var getX = module.getX;
getX(); // 9, because in this case, "this" refers to the global object

// Create a new function with 'this' bound to module
var boundGetX = fast.bind(getX,module);
boundGetX(); // 81

clone

var fast = require('fast.js');

var inputObject = {
    a : 1,
    b : 2,
    c : 3,
    d : 4,
    e : 5,
    f : 6
};

var inputArray = [1,2,3,4,5,6];

//fast.js way
fast.clone(inputObject);
fast.clone(inputArray);

javascript中没有内置的clone()函数。clone()函数可以克隆对(数组),返回一个新的对象(数组)。新的对象的属性和它的值跟传入的对象一模一样(新数组的元素跟传入的数组也是一模一样),但它们是不同的对象(数组)。

concat

var input = [1,2,3,4,5,6,7,8,9];

//builtin way
input.concat(11,12,[13]);

//fast.js way
fast.concat(input,11,12,[13]);

concat()的内置函数是Array.prototype.concat()函数,它是数组的原型函数。

原生的concat()函数由数组调用,它接受任意多个参数,这些参数可以是数组或者值。它会返回一个新的数组,新数组会先将调用这个函数的数组的元素加入到新的数组中,然后处理传入的参数,如果传入的是值,则直接加入到新数组中;如果是数组,则会将数组中的元素加入到新数组中,这相当于执行数组连接操作。

原生concat()函数对数组的处理规则为:

  1. concat()会返回一个新数组,新数组跟调用它的数组没有任何关系。对新数组进行操作不会影响原数组,或者传入的作为参数的数组

  2. 如果数组中包括对象,concat()会将对象的引用拷贝到新数组中,所以新数组中这个引用跟老数组中的引用指向同一个对象。

  3. 对于String或者Number类型,直接拷贝值

fast.concat()的第一个参数为原生的concat()中调用它的数组,其他的跟原生的concat()没有任何差别。

every

var fast = require('fast.js');

var input = [1,2,3];

function fn(i) {
    return i < 4;
}

//builtin way
input.every(fn);

//fast.js way
fast.every(input,fn);

every()的内置函数是Array.prototype.every(),它是数组的原型函数。

原生every()函数会被数组对象调用,every()会遍历数组的各个元素,并将每个元素作为参数传递给callback,callback是every()的第一个参数。如果callback返回false,则遍历会立即终止,相应的every()也会返回false。如果每次调用callback都返回true,最终遍历完后every()会返回true。这个函数可以判断数组中的每个元素是否都满足某种条件。

原生every()函数的语法是:

arr.every(callback[, thisArg])

第一个参数callback可以接受3个参数:

  1. currentValue:当前元素

  2. index:当前元素索引

  3. array:调用every()的数组

every()还可以接受第二个参数,这个跟apply()和bind()一样,用于改变callback函数的this对象。

fast.every()的第一个参数为要处理的数组,它跟原生的every()有一些细小的差别,对此最后一节会更详细的说明。

filter

var fast = require('fast.js');

function fn(item){
    return (item + Math.random()) % 2;
}

var input = [1,2,3];

//builtin way
input.filter(fn);

//fast.js way
fast.filter(input,fn);

filter()的内置函数是Array.prototype.filter()函数,它也是数组对象的原型函数。

filter()跟every()有些类似,every()函数可以判断数组中的元素是否都满足某个条件,而filter函数则是会将不满足条件的元素都过滤掉,最后返回一个新的数组。

原生filter()函数的语法为:

arr.filter(callback[, thisArg])

它的参数跟every()的参数一样,这里就不赘述了。

fast.filter()的第一个参数为要处理的数组,它跟原生的filter()有一些细小的差别,对此最后一节会更详细的说明。

forEach

var fast = require('fast.js');
function fn(item) {
    console.log(item);
}

var input = [1,2,3];

//builtin way
input.forEach(fn);

//fast.js way
fast.forEach(fn);

forEach()的内置函数是Array.prototype.forEach(),这个函数会遍历数组中的所有元素,并将每个元素作为callback的参数执行callback。这个可能是用得最多的数组函数,至少我是这样的。

原生forEach()函数的语法为:

arr.forEach(callback[, thisArg])

它的参数跟上面提到的every()和filter()的参数一样。实际上这篇文章中提到的几个数组函数(every、filter、forEach、map和some)都是一类函数,它们的用法和行为都有一些类似之处,对于这点各自自己去感受吧。

关于forEach()函数还有两点需要注意:

indexOf

var fast = require('fast.js');

var input = [1,2,3,4,5,6];

//builtin way
input.indexOf(4);

//fast way
fast.indexOf(input,4);

indexOf的内置函数是Array.prototype.indexOf(),它是数组对象的原型函数。它用于查找某个元素在数组中的位置,如果未找到则返回-1。

内置函数的语法为:

arr.indexOf(searchElement[, fromIndex = 0])

第二个参数表示查找的起始位置,默认为0,也就是从头开始查找。对于这个参数有几点需要注意:

lastIndexOf

var fast = require('fast.js');

var input = [1,2,3,4];

//builtin way
input.lastIndexOf(1);

//fast.js way
fast.lastIndexOf(input,1);

lastIndexOf()的内置函数是Array.prototype.lastIndexOf(),它跟indexOf()是一对,只不过indexOf()是从前往后在数组中查找,而lastIndexOf()则是从后往前查找,它们都返回第一个找到元素的位置,找不到的时候都返回-1。

内置函数的语法为:

arr.lastIndexOf(searchElement[, fromIndex = arr.length])

lastInfoOf()的参数的使用规则为:

注意:lastIndexOf()和前面的indexOf()在查找的元素的时候都采用严格比较(strict equality,也就是三等号'==='操作符进行比较的)

map

var fast = require('fast.js');
var input = [1,2,3,4];

function fn(item) {
	return item * item + Math.random();
}

//builtin way
var input2 = input.map(fn);

//fast.js way
var input3 = fast.map(input,fn);

map()的内置函数是Array.prototype.map()

map()的用法跟前面介绍的every()、filter()和forEach()一样,都会对数组的元素进行遍历。map跟它们不同的是,它会返回一个新数组,这个数组中的元素就是每次调用callback后返回的值。

我们来看一个fast.map()的示例:

var fast = require('fast.js');

var numbers = [1,4,9];
var roots = fast.map(numbers,Math.sqrt);
//root is now [1,2,3], numbers is still [1,4,9]

partial

var fast = require('fast.js'); 

var input = function(a,b,c){
	return a + b + c * Math.random();
};

//builtin way
var fn1 = input.bind(this,1,2);
fn1(3);

//fast way
var fn2 = fast.partial(input,1,2);
return fn2(3);

javascript中采用内置的bind来实现partial的功能,我们先来看一个示例:


function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

bind()函数创建了一个新函数,在创建函数时就传入了部分参数。调用新函数时,之前传入的参数依旧存在。

fast.partial()就是用于创建这种部分函数的。它跟用内置的bind()创建的部分函数有个较大的差别,fast.partial()没有改变this对象的参数,这也是它跟fast.bind()的不同点。fast.partial()和fast.bind()跟内置的bind()函数的其他差别请参加最后一节

reduce

var fast = require('fast.js');

var input = [1,2,3];

function fn(last,item) {
	return last * item + Math.random();
}

//builtin way
input.reduce(fn,0);

//fast.js way
fast.reduce(input,fn,0);

reduce()的内置函数是Array.prototype.reduce()

reduce()会对数组元素进行遍历并调用callback,最终返回一个单个的值(注意不是数组)。我们先看看内置reduce()的语法,然后再来分析它的用法:

arr.reduce(callback[, initialValue])

第一个参数callback可以接受4个参数:

  1. previousValue:在遍历数组过程中前一次调用callback函数返回的结果,或者是initialValue(下面详细说明)。

  2. currentValue:当前迭代的值。

  3. index:当前迭代的索引。

  4. array:调用reduce()的数组对象。

第二个参数initialValue,这是一个可选参数。如果提供了,那么它将作为第一次调用callback时的第一个参数。

使用这个函数时有以下几点需要注意:

  1. previousValue、currentValue、initialValue关系:

    • 如果在调用reduce()时没有提供initialValue,那么第一次调用callback时previousValue将是数组的第一个元素,currentValue是数组的第二个元素。

    • 如果提供了initialValue,那么第一次调用callback时previousValue是initialValue的值,而currentValue是第一个数组元素。

  2. 如果数组为空,并且没有提供initialValue参数,那么会抛出TypeError。

  3. 如果数组中只有一个元素,并且没有提供initialValue参数,仅有的一个元素的值会被返回,而不会调用callback。

  4. 如果数组为空,但是提供了initialValue参数,reduce会返回initialValue的值,并且不会调用callback。

最后再看一个使用fast.js的reduce函数的示例:

var fast = require('fast.js');
var result = fast.reduce([0,1,2,3,4],function(previousValue, currentValue, index, array) {
  return previousValue + currentValue;
}, 10);

//the result would be 20
console.log(result);

关于fast.reduce()跟原生reduce()的差别请参加最后一节

reduceRight

var fast = require('fast.js');

var input = [1,2,3,4,5,6,7,8,9,10];

function fn(last,item) {
	return last*item + Math.random();
}

//builtin way
input.reduceRight(fn,0);

//fast.js way
fast.reduceRight(input,fn,0);

reduceRight()的内置函数为Array.prototype.reduceRight()

这个函数跟reduce()的唯一不同是:reduce()是从左往右遍历数组,也就是从第0个元素遍历到最后一个,而reduceRight()则是从右往左遍历数组,从最后一个元素遍历到第0个。

some

var fast = require('fast.js');

var input = [1,2,3,4,5,6,7,8,9,10];

function fn(item) {
	return item === 10;
}

//builtin way
input.some(fn);

//fast.js way
fast.some(input,fn);

some()的内置函数为Array.prototype.some(),它是数组对象的原型函数。

some()用于判断数组中是否有满足某个条件的元素,它跟every()有点类似,只不过every()用于判断数组中所有元素是否都满足某个条件。我们来看看内置some()的语法结构:

arr.some(callback[, thisArg])

它跟every()、filter()、forEach()的语法相同,参数的使用方法也相同。

fast.some()的示例:

var fast = require('fast.js');

function isBigEnough(element, index, array) {
	return element >= 10;
}

var passed = fast.some([2, 5, 8, 1, 4],isBigEnough);
//passed is false

var passed = fast.some([12, 5, 8, 1, 4],isBigEnough);
//passed is true 

try

var fast = require('fast.js');

function factorial(op) {
	var z = op + 1;
	var p = [1.000000000190015, 76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 1.208650973866179E-3, -5.395239384953E-6];

	var d1 = Math.sqrt(2 * Math.PI) / z;
	var d2 = p[0];

	for (var i = 1; i <= 6; ++i) {
		d2 += p[i] / (z + i);
	}

	var d3 = Math.pow((z + 5.5), (z + 0.5));
	var d4 = Math.exp(-(z + 5.5));

	d = d1 * d2 * d3 * d4;

	return d;
}

function doSomeWork() {
	var d = 0;
	d += factorial(10 * Math.random());
	d += factorial(2 * Math.random());
	return d;
}

//builtin way
try {
	doSomeWork();
} catch (e) {
	console.log(e);
}

//fast.js way
fast.try(doSomeWork);

相信大家对try...catch都有一定了解,这里是内置try...catch的文档。fast.js中的try跟内置的try的不同之处是fast.js中的try是一个函数,并且不需要使用catch。

不同之处

到这里fast.js中的所有常用的函数都介绍完了,这些函数基本上都有它对应的内置函数。它们的用法也是基本上完全一致,所以每个函数都重点介绍了内置函数的用法,因为这些内置的函数的文档更详细,而且它们比fast.js中的函数更成熟。

尽管如此,fast.js中的函数和它对应的内置函数的也存在一些细节上有些差别:

这些差别在现实的环境中很少会带来问题,所以你可以不用担心它们。