5

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

 2 years ago
source link: http://gywbd.github.io/posts/2014/9/fast-js.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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()函数还有两点需要注意:

  • 当数组对象为null或者undefined的时候,如果使用这个函数的时候会报错,所以在使用的时候最好先判断数组是否为null或者undefined。

  • forEach函数对数组的迭代是不可终止的,只有Array.every()和Array.some()这两个函数才可以通过返回值终止迭代。

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,也就是从头开始查找。对于这个参数有几点需要注意:

  • 如果这个参数比数组的长度要大,则直接返回-1,查找不会进行

  • 如果这个参数是负数,则开始查找的位置为这个参数的值加上数组的总长度的结果,查找还是从前往后进行。如果相加后的结果仍然为负数,则从第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()的参数的使用规则为:

  • 如果这个参数比数组的长度更大,则查找会从最后一个元素开始,整个数组的所有元素都会查找。

  • 如果这个参数是负数,那么将这个参数的值与数组长度相加得到的结果就是查找的起始位置。如果这个结果依旧为负数,那么将返回-1,不会进行查找操作。

注意: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中的函数和它对应的内置函数的也存在一些细节上有些差别:

  • 对于数组对象上的原型函数,像map、forEach、every、some、filter这几个函数的语法基本相同,它们只会处理某个索引已赋值的元素,如果某个索引没有赋值,或者是已被删除,则它们不会被处理。

    不过fast.js则会处理这些元素,fast.js会将undefined作为这个索引的值传给callback作为参数。

  • 使用fast.bind()和fast.partial()创建的函数跟调用原生的函数Function.prototype.bind()所创建的函数并不完全相同:

    • fast.partial()创建的函数没有不可变的“毒药”方法和参数属性,它们在调用get、set或者被删除的时候会抛出TypeError错误。

    • fast.partial()创建的函数有prototype属性,而原生的bind()创建的函数没有prototype属性。

    • fast.partial()创建的函数的length属性只能为0,这不符合ECMA-262规范,原生函数创建的函数的length属性可能为非零值。

  • fast.reduce()函数的跟原生的Array.prototype.reduce()函数有一些较大的差别:

    • fast.reduce()中initialValue参数的值为undefined跟不设置initialValue参数的情况相同。这跟原生函数的规定不同,原生函数只判断参数的个数,而不管参数的值。而fast.reduce()会检查initialValue的值是否为undefined,因为undefined有时候会产生一些意外的结果。如果你希望fast.reduce()在这点上跟原生函数一致,你可以把它设置为null。

    • fast.reduce()支持4个参数,第四个参数是传给callback的this对象,这个原生函数是不支持的。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK