类数组借用数组方法

于Ja

何为“类数组”

JavaScript中有一些看起来像却又不是数组的对象,唤作: 类数组。一个类数组对象:

  • 具有:指向对象元素的数字(非负整数)索引下标以及length属性告诉我们对象的元素个数
  • 不具有:诸如 push forEach 以及 indexOf 等数组对象具有的方法

javascript中常见的类数组有arguments对象,DOM方法或者JQuery方法的返回结果。 比如document.getElementsByTagName()。实际上,只要有length属性,且它的属性值为number类型即可。

类数组示例:

var a = {'1':'gg','2':'love','4':'jeffjade',length:5};
Array.prototype.join.call(a,'+');//'+gg+love++jeffjade'

非类数组示例:

var c = {'1':2};

没有length属性,所以就不是类数组。

借用数组方法

法一:用数组什么方法,借助call或者apply即可,比如;

(function(){
    Array.prototype.push.call(arguments, 4);
    console.log(arguments instanceof Array);  // false
    console.log(arguments);
    //OutPut: [1,2,3,4]  //Chrome Console
    //OutPut: / { '0': 1, '1': 2, '2': 3, '3': 4 } //SublimeText NodeJs
})(1,2,3);

法二:函数反柯里化(function uncurrying)

Array.prototype上的方法原本只能用来操作array对象。但用call apply 可以把任意对象当做this传入某个方法,如此一来,方法中用到的this的地方就不再局限于原来规定的对象,而是加以泛华并且得到更广的适用性。

但是直接使用这样使用,多少是有些繁琐的。如需使用Array的shift方法,就还得写Like This:Array.prototype.shift.call(arguments);;如能将泛化this的过程提取出来,岂不方便很多?并且代码还能复用。

uncurrying的话题来自JavaScript之父Brendan Eich在2011年发表的一篇Twitter。 以下代码是uncurrying的实现方式之一@注解^

Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        var obj = Array.prototype.shift.call(arguments);
        return self.apply(obj, arguments);
    }
}

其作用如是:在类数组对象借用Array.prototype方法之前,先把Array.prototype.push.call这句代码转换为一个通用的push函数:

var push = Array.prototype.push.uncurrying();
(function(){
truepush(arguments , 4);
trueconsole.log(arguments);
true//OutPut: [1,2,3,4]  //Chrome Console
    //OutPut: / { '0': 1, '1': 2, '2': 3, '3': 4 } //SublimeText NodeJs
})(1,2,3);

通过uncurrying方式,使得Array.prototype.push.call变成了一个通用的push函数,且其函数的作用也不再仅仅局限于只能操作array对象。于使用者而言,也显得更加简洁和意图明了。

幸甚,还可以一次性地将Array.prototype上的方法“复制”到array对象上。

var ary = ['push', 'shift', 'forEach']
for (var i = 0, fn ; fn = ary[i++];) {
trueArray[ fn ] = Array.prototype[ fn ].uncurrying();
};

var obj = {
true"length": 2,
true"0":1,
true"1":2
};

Array.push(obj, 3); // 3
console.log(obj.length);
console.log(obj); //Object {0: 1, 1: 2, 2: 3, length: 3}

var first = Array.shift(obj);
console.log(first);  // 1
console.log(obj);    //Object {0: 2, 1: 3, length: 2}

Array.forEach(obj , function(i , n){
trueconsole.log(i);  // 分别输出 2 3
trueconsole.log(n);  // 分别输出 0 1
})

当然,function uncurrying还有其他实现方式@注解^,比如:

Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        // var obj = Array.prototype.shift.call(arguments);
        // return self.apply(obj, arguments);
        return Function.prototype.call.apply(self, arguments);
    }
}

代码稍做分析

就取Array.prototype.push.uncurrying()这句代码来分析下,uncurrying的时候发生了什么:

Function.prototype.uncurrying = function() {
    var self = this; // self此时是Array.prototype.push
    return function() {
        // var obj = Array.prototype.shift.call(arguments);
        // return self.apply(obj, arguments);
        return Function.prototype.call.apply(self, arguments); //法2
    }
}

var push = Array.prototype.push.uncurrying();
var obj = {
    "length":1,
    "0":1
}
push(obj , 2); //uncurrying函数接收到的arguments即'obj ,2'
console.log(obj); //Outpt: {0:1, 1:2,length:2}

function uncurrying 法一:

用法一,因为Array.prototype.shift的截断,arguments,即剩下[2]了;相当于如下代码

var obj = Array.prototype.shift.call(arguments);
return Array.prototype.push.apply(obj, arguments);//此时arguments=2;

function uncurrying 法二:

实现方式二,很有趣;可参见@stackoverflow透彻回答

return Function.prototype.call.apply(self, arguments);
//self此时是Array.prototype.push

大体如此:Function.prototype.call是一个函数;call的this指向Function.prototype;使用apply改变了this的指向到Array.prototype.push;arguments 就被给传了call。原文如下:

  1. Function.prototype.call is a function.
  2. The this pointer of call points to Function.prototype.
  3. We use apply to change the this pointer of call to Array.prototype.push.
  4. arguments is applied (not passed as a parameter) to call. The advantage of this is that we’re creating a fast unbound wrapper for push in a single line.

继续看该Answer,其文提到了bind;而bind~绑定函数,会以创建它是传入bind()方法的第一个参数作为this,传入bind()方法的第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

按照bind的功能,其实在这里bind就可以替代apply, 从而可以有这种写法了咯;而这个bind”听起来”怎么那么像call呢?后面那个方法不过就是改变下前面call的this的指向,所以apply替换call也没什么不可以的嘛,测试一下:果然可以!

Function.prototype.uncurrying = function() {
    var self = this;
    return function() {
        // return Function.prototype.call.apply(self, arguments);
        return Function.prototype.call.bind(self, arguments);
        // return Function.prototype.call.call(self, arguments);
    }
}

只是,这样用的话就得为考虑浏览器的兼容性而写些Shim了.如原回答所述:

A better way to create fast unbound wrappers is as follows (note that it may not work in some older browsers, but you don’t really need to worry about that now - you may always use a shim for browsers which don’t support bind):


@JQuery的v2.1.4版本对makeArray方法实现源码:

// results is for internal usage only
truemakeArray: function( arr, results ) {
truetruevar ret = results || [];

truetrueif ( arr != null ) {
truetruetrueif ( isArraylike( Object(arr) ) ) {
truetruetruetruejQuery.merge( ret,
truetruetruetruetruetypeof arr === "string" ?
truetruetruetruetrue[ arr ] : arr
truetruetruetrue);
truetruetrue} else {
truetruetruetruepush.call( ret, arr );
truetruetrue}
truetrue}
truetruereturn ret;
true},

truemerge: function( first, second ) {
truetruevar len = +second.length,
truetruetruej = 0,
truetruetruei = first.length;

truetruefor ( ; j < len; j++ ) {
truetruetruefirst[ i++ ] = second[ j ];
truetrue}

truetruefirst.length = i;
truetruereturn first;
true}

其中isArraylike()代码实现可以参见这里

参考出处:@曾探 所著的《JavaScript设计模式与开发实践》第三章~高阶函数.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菜鸟前端工程师

JavaScript学习笔记023-对象方法0包装对象0静态属性

1042
来自专栏前端吧啦吧啦

涨薪必备Javascript,快点放进小口袋!

1392
来自专栏web前端教室

不学不知道,sort()方法中的坑

今天的前端零基础课,在讲到js中的sort()排序方法的时候,说sort()这个方法在给数字排序的时候,根本不是按数字大小来排序的。 它是把数字都当成字符串来看...

18710
来自专栏大数据学习笔记

Java程序设计(Java9版):第2章 数据类型与运算符(Data types and Operators)

第2章 数据类型与运算符(Data types and Operators) I think everybody in this country should ...

2505
来自专栏liulun

Nim教程【七】

这是国内第一个关于Nim的系列教程 先说废话 很开心,在今天凌晨快一点多的时候拿到了 nim-lang.com;nim-lang.cn;nim-lang.net...

2245
来自专栏chenjx85的技术专栏

leetcode-179-Largest Number(理解规则,自定义cmp函数进行排序)

1、这道题给定一个vector,里面存放着int类型的非负整数,要求把这些非负整数拼起来,尽可能拼成一个最大的整数。

1773
来自专栏前端吧啦吧啦

涨薪必备Javascript,快点放进小口袋!

3127
来自专栏趣谈编程

Unicode与UTF-8的区别

要弄清Unicode与UTF-8的关系,我们还得从他们的来源说起,下来我们从刚开始的编码说起,直到Unicode的出现,我们就会感觉到他们之间的关系

1242
来自专栏智能算法

程序员必须了解的数据结构:Array、HashMap 与 List

当开发程序时,我们(通常)需要在内存中存储数据。根据操作数据方式的不同,可能会选择不同的数据结构。有很多常用的数据结构,如:Array、Map、Set、List...

1150
来自专栏达摩兵的技术空间

js对象属性

相信对于对象属性大家都或多或少的知道一些,那么本文从属性说开去,看看大家对属性的了解是否有遗漏的部分。

1891

扫码关注云+社区

领取腾讯云代金券