JavaScript中有一些看起来像却又不是数组的对象,唤作: 类数组。一个类数组对象:
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属性,所以就不是类数组。
(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);
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}
用法一,因为Array.prototype.shift
的截断,arguments,即剩下[2]了;相当于如下代码
var obj = Array.prototype.shift.call(arguments);
return Array.prototype.push.apply(obj, arguments);//此时arguments=2;
实现方式二,很有趣;可参见@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。原文如下:
Function.prototype.call
is a function.this
pointer of call
points to Function.prototype
.apply
to change the this
pointer of call
to Array.prototype.push
.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设计模式与开发实践》第三章~高阶函数.