prototype: Enhance the Enumerable and Array APIs to more closely match those of JavaScript 1.6 as implemented in Firefox 1.5. Closes #6650, #8409.
This commit is contained in:
parent
29cd62ebeb
commit
1c17b6381a
|
@ -1,5 +1,12 @@
|
|||
*SVN*
|
||||
|
||||
* Enhance the Enumerable and Array APIs to more closely match those of JavaScript 1.6 as implemented in Firefox 1.5. Closes #6650, #8409. [Mislav Marohnić, Sylvain Zimmer]
|
||||
- Add Array#lastIndexOf, and change Array#indexOf not to overwrite the native method.
|
||||
- Make Enumerable use Array.prototype.forEach instead of _each when possible (slight speed increase).
|
||||
- Add "filter", "entries", "every", and "some" Array aliases.
|
||||
- All Enumerable methods now have an additional parameter, "context", which, if present, specifies the object to which the iterators' "this" is bound.
|
||||
- Function#bind and #curry now return the receiving function if the binding object is undefined.
|
||||
|
||||
* Temporary workaround for Prototype.BrowserFeatures.SpecificElementExtensions incorrectly evaluating to true on iPhone. (needs further investigation) [sam]
|
||||
|
||||
* The action for Form#request defaults to the current URL if the "action" attribute is empty. (This is what most of the major browsers do.) Fixes #8483. [Tomas, Mislav Marohnić]
|
||||
|
|
33
src/array.js
33
src/array.js
|
@ -1,8 +1,7 @@
|
|||
function $A(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) {
|
||||
return iterable.toArray();
|
||||
} else {
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
else {
|
||||
var results = [];
|
||||
for (var i = 0, length = iterable.length; i < length; i++)
|
||||
results.push(iterable[i]);
|
||||
|
@ -29,8 +28,7 @@ Array.from = $A;
|
|||
|
||||
Object.extend(Array.prototype, Enumerable);
|
||||
|
||||
if (!Array.prototype._reverse)
|
||||
Array.prototype._reverse = Array.prototype.reverse;
|
||||
if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
|
||||
|
||||
Object.extend(Array.prototype, {
|
||||
_each: function(iterator) {
|
||||
|
@ -71,12 +69,6 @@ Object.extend(Array.prototype, {
|
|||
});
|
||||
},
|
||||
|
||||
indexOf: function(object) {
|
||||
for (var i = 0, length = this.length; i < length; i++)
|
||||
if (this[i] == object) return i;
|
||||
return -1;
|
||||
},
|
||||
|
||||
reverse: function(inline) {
|
||||
return (inline !== false ? this : this.toArray())._reverse();
|
||||
},
|
||||
|
@ -115,6 +107,25 @@ Object.extend(Array.prototype, {
|
|||
}
|
||||
});
|
||||
|
||||
// use native browser JS 1.6 implementation if available
|
||||
if (typeof Array.prototype.forEach == 'function')
|
||||
Array.prototype._each = Array.prototype.forEach;
|
||||
|
||||
if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
|
||||
i || (i = 0);
|
||||
var length = this.length;
|
||||
if (i < 0) i = length + i;
|
||||
for (; i < length; i++)
|
||||
if (this[i] === item) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
|
||||
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
|
||||
var n = this.slice(0, i).reverse().indexOf(item);
|
||||
return (n < 0) ? n : i - n - 1;
|
||||
}
|
||||
|
||||
Array.prototype.toArray = Array.prototype.clone;
|
||||
|
||||
function $w(string) {
|
||||
|
|
|
@ -68,6 +68,7 @@ Object.extend(Object, {
|
|||
|
||||
Object.extend(Function.prototype, {
|
||||
bind: function() {
|
||||
if (arguments.length < 2 && arguments[0] === undefined) return this;
|
||||
var __method = this, args = $A(arguments), object = args.shift();
|
||||
return function() {
|
||||
return __method.apply(object, args.concat($A(arguments)));
|
||||
|
@ -82,6 +83,7 @@ Object.extend(Function.prototype, {
|
|||
},
|
||||
|
||||
curry: function() {
|
||||
if (!arguments.length) return this;
|
||||
var __method = this, args = $A(arguments);
|
||||
return function() {
|
||||
return __method.apply(this, args.concat($A(arguments)));
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
var $break = {};
|
||||
|
||||
var Enumerable = {
|
||||
each: function(iterator) {
|
||||
each: function(iterator, context) {
|
||||
var index = 0;
|
||||
iterator = iterator.bind(context);
|
||||
try {
|
||||
this._each(function(value) {
|
||||
iterator(value, index++);
|
||||
|
@ -13,40 +14,45 @@ var Enumerable = {
|
|||
return this;
|
||||
},
|
||||
|
||||
eachSlice: function(number, iterator) {
|
||||
eachSlice: function(number, iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var index = -number, slices = [], array = this.toArray();
|
||||
while ((index += number) < array.length)
|
||||
slices.push(array.slice(index, index+number));
|
||||
return slices.map(iterator);
|
||||
return slices.collect(iterator, context);
|
||||
},
|
||||
|
||||
all: function(iterator) {
|
||||
|
||||
all: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var result = true;
|
||||
this.each(function(value, index) {
|
||||
result = result && !!(iterator || Prototype.K)(value, index);
|
||||
result = result && !!iterator(value, index);
|
||||
if (!result) throw $break;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
any: function(iterator) {
|
||||
|
||||
any: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var result = false;
|
||||
this.each(function(value, index) {
|
||||
if (result = !!(iterator || Prototype.K)(value, index))
|
||||
if (result = !!iterator(value, index))
|
||||
throw $break;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
collect: function(iterator) {
|
||||
|
||||
collect: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var results = [];
|
||||
this.each(function(value, index) {
|
||||
results.push((iterator || Prototype.K)(value, index));
|
||||
results.push(iterator(value, index));
|
||||
});
|
||||
return results;
|
||||
},
|
||||
|
||||
detect: function(iterator) {
|
||||
detect: function(iterator, context) {
|
||||
iterator = iterator.bind(context);
|
||||
var result;
|
||||
this.each(function(value, index) {
|
||||
if (iterator(value, index)) {
|
||||
|
@ -57,7 +63,8 @@ var Enumerable = {
|
|||
return result;
|
||||
},
|
||||
|
||||
findAll: function(iterator) {
|
||||
findAll: function(iterator, context) {
|
||||
iterator = iterator.bind(context);
|
||||
var results = [];
|
||||
this.each(function(value, index) {
|
||||
if (iterator(value, index))
|
||||
|
@ -66,20 +73,24 @@ var Enumerable = {
|
|||
return results;
|
||||
},
|
||||
|
||||
grep: function(pattern, iterator) {
|
||||
grep: function(pattern, iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var results = [];
|
||||
this.each(function(value, index) {
|
||||
var stringValue = value.toString();
|
||||
if (stringValue.match(pattern))
|
||||
results.push((iterator || Prototype.K)(value, index));
|
||||
})
|
||||
results.push(iterator(value, index));
|
||||
});
|
||||
return results;
|
||||
},
|
||||
|
||||
include: function(object) {
|
||||
if (typeof this.indexOf == 'function')
|
||||
return this.indexOf(object) != -1;
|
||||
|
||||
var found = false;
|
||||
this.each(function(value) {
|
||||
if (value == object) {
|
||||
if (value === object) {
|
||||
found = true;
|
||||
throw $break;
|
||||
}
|
||||
|
@ -95,7 +106,8 @@ var Enumerable = {
|
|||
});
|
||||
},
|
||||
|
||||
inject: function(memo, iterator) {
|
||||
inject: function(memo, iterator, context) {
|
||||
iterator = iterator.bind(context);
|
||||
this.each(function(value, index) {
|
||||
memo = iterator(memo, value, index);
|
||||
});
|
||||
|
@ -109,30 +121,33 @@ var Enumerable = {
|
|||
});
|
||||
},
|
||||
|
||||
max: function(iterator) {
|
||||
max: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var result;
|
||||
this.each(function(value, index) {
|
||||
value = (iterator || Prototype.K)(value, index);
|
||||
value = iterator(value, index);
|
||||
if (result == undefined || value >= result)
|
||||
result = value;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
min: function(iterator) {
|
||||
min: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var result;
|
||||
this.each(function(value, index) {
|
||||
value = (iterator || Prototype.K)(value, index);
|
||||
value = iterator(value, index);
|
||||
if (result == undefined || value < result)
|
||||
result = value;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
partition: function(iterator) {
|
||||
partition: function(iterator, context) {
|
||||
iterator = iterator ? iterator.bind(context) : Prototype.K;
|
||||
var trues = [], falses = [];
|
||||
this.each(function(value, index) {
|
||||
((iterator || Prototype.K)(value, index) ?
|
||||
(iterator(value, index) ?
|
||||
trues : falses).push(value);
|
||||
});
|
||||
return [trues, falses];
|
||||
|
@ -140,13 +155,14 @@ var Enumerable = {
|
|||
|
||||
pluck: function(property) {
|
||||
var results = [];
|
||||
this.each(function(value, index) {
|
||||
this.each(function(value) {
|
||||
results.push(value[property]);
|
||||
});
|
||||
return results;
|
||||
},
|
||||
|
||||
reject: function(iterator) {
|
||||
reject: function(iterator, context) {
|
||||
iterator = iterator.bind(context);
|
||||
var results = [];
|
||||
this.each(function(value, index) {
|
||||
if (!iterator(value, index))
|
||||
|
@ -155,7 +171,8 @@ var Enumerable = {
|
|||
return results;
|
||||
},
|
||||
|
||||
sortBy: function(iterator) {
|
||||
sortBy: function(iterator, context) {
|
||||
iterator = iterator.bind(context);
|
||||
return this.map(function(value, index) {
|
||||
return {value: value, criteria: iterator(value, index)};
|
||||
}).sort(function(left, right) {
|
||||
|
@ -192,6 +209,9 @@ Object.extend(Enumerable, {
|
|||
map: Enumerable.collect,
|
||||
find: Enumerable.detect,
|
||||
select: Enumerable.findAll,
|
||||
filter: Enumerable.findAll,
|
||||
member: Enumerable.include,
|
||||
entries: Enumerable.toArray
|
||||
entries: Enumerable.toArray,
|
||||
every: Enumerable.all,
|
||||
some: Enumerable.any
|
||||
});
|
||||
|
|
|
@ -111,6 +111,33 @@
|
|||
assertEqual(-1, [0].indexOf(1));
|
||||
assertEqual(0, [1].indexOf(1));
|
||||
assertEqual(1, [0,1,2].indexOf(1));
|
||||
assertEqual(0, [1,2,1].indexOf(1));
|
||||
assertEqual(2, [1,2,1].indexOf(1, -1));
|
||||
assertEqual(1, [undefined,null].indexOf(null));
|
||||
}},
|
||||
|
||||
testLastIndexOf: function(){ with(this) {
|
||||
assertEqual(-1,[].lastIndexOf(1));
|
||||
assertEqual(-1, [0].lastIndexOf(1));
|
||||
assertEqual(0, [1].lastIndexOf(1));
|
||||
assertEqual(2, [0,2,4,6].lastIndexOf(4));
|
||||
assertEqual(3, [4,4,2,4,6].lastIndexOf(4));
|
||||
assertEqual(3, [0,2,4,6].lastIndexOf(6,3));
|
||||
assertEqual(-1, [0,2,4,6].lastIndexOf(6,2));
|
||||
assertEqual(0, [6,2,4,6].lastIndexOf(6,2));
|
||||
|
||||
var fixture = [1,2,3,4,3];
|
||||
assertEqual(4, fixture.lastIndexOf(3));
|
||||
assertEnumEqual([1,2,3,4,3],fixture);
|
||||
|
||||
//tests from http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf
|
||||
var array = [2, 5, 9, 2];
|
||||
assertEqual(3,array.lastIndexOf(2));
|
||||
assertEqual(-1,array.lastIndexOf(7));
|
||||
assertEqual(3,array.lastIndexOf(2,3));
|
||||
assertEqual(0,array.lastIndexOf(2,2));
|
||||
assertEqual(0,array.lastIndexOf(2,-2));
|
||||
assertEqual(3,array.lastIndexOf(2,-1));
|
||||
}},
|
||||
|
||||
testInspect: function(){ with(this) {
|
||||
|
|
|
@ -54,41 +54,31 @@
|
|||
assert3(a3);
|
||||
}
|
||||
|
||||
var globalBindTest = null;
|
||||
|
||||
new Test.Unit.Runner({
|
||||
|
||||
testFunctionBind: function() { with(this) {
|
||||
function methodWithoutArguments(){
|
||||
globalBindTest = this.hi;
|
||||
}
|
||||
function methodWithArguments(){
|
||||
globalBindTest = this.hi + ',' + $A(arguments).join(',');
|
||||
}
|
||||
function methodWithBindArguments(){
|
||||
globalBindTest = this.hi + ',' + $A(arguments).join(',');
|
||||
}
|
||||
function methodWithBindArgumentsAndArguments(){
|
||||
globalBindTest = this.hi + ',' + $A(arguments).join(',');
|
||||
}
|
||||
|
||||
methodWithoutArguments.bind({hi:'without'})();
|
||||
assertEqual('without', globalBindTest);
|
||||
|
||||
methodWithArguments.bind({hi:'with'})('arg1','arg2');
|
||||
assertEqual('with,arg1,arg2', globalBindTest);
|
||||
|
||||
methodWithBindArguments.bind({hi:'withBindArgs'},'arg1','arg2')();
|
||||
assertEqual('withBindArgs,arg1,arg2', globalBindTest);
|
||||
|
||||
methodWithBindArgumentsAndArguments.bind({hi:'withBindArgsAndArgs'},'arg1','arg2')('arg3','arg4');
|
||||
assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4', globalBindTest);
|
||||
function methodWithoutArguments() { return this.hi };
|
||||
function methodWithArguments() { return this.hi + ',' + $A(arguments).join(',') };
|
||||
var func = Prototype.emptyFunction;
|
||||
|
||||
assertIdentical(func, func.bind());
|
||||
assertIdentical(func, func.bind(undefined));
|
||||
assertNotIdentical(func, func.bind(null));
|
||||
|
||||
assertEqual('without', methodWithoutArguments.bind({ hi: 'without' })());
|
||||
assertEqual('with,arg1,arg2', methodWithArguments.bind({ hi: 'with' })('arg1','arg2'));
|
||||
assertEqual('withBindArgs,arg1,arg2',
|
||||
methodWithArguments.bind({ hi: 'withBindArgs' }, 'arg1', 'arg2')());
|
||||
assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4',
|
||||
methodWithArguments.bind({ hi: 'withBindArgsAndArgs' }, 'arg1', 'arg2')('arg3', 'arg4'));
|
||||
}},
|
||||
|
||||
testFunctionCurry: function() { with(this) {
|
||||
var split = function(delimiter, string) { return string.split(delimiter); };
|
||||
var splitOnColons = split.curry(":");
|
||||
assertNotIdentical(split, splitOnColons);
|
||||
assertEnumEqual(split(":", "0:1:2:3:4:5"), splitOnColons("0:1:2:3:4:5"));
|
||||
assertIdentical(split, split.curry());
|
||||
}},
|
||||
|
||||
testFunctionDelay: function() { with(this) {
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
|
||||
Nicknames: $w('sam- noradio htonl Ulysses'),
|
||||
|
||||
Basic: [1, 2, 3],
|
||||
|
||||
Primes: [
|
||||
1, 2, 3, 5, 7, 11, 13, 17, 19, 23,
|
||||
29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
|
||||
|
@ -57,7 +59,7 @@
|
|||
new Test.Unit.Runner({
|
||||
testEachBreak: function() {with(this) {
|
||||
var result = 0;
|
||||
[1, 2, 3].each(function(value) {
|
||||
Fixtures.Basic.each(function(value) {
|
||||
if ((result = value) == 2) throw $break;
|
||||
});
|
||||
|
||||
|
@ -66,7 +68,7 @@
|
|||
|
||||
testEachReturnActsAsContinue: function() {with(this) {
|
||||
var results = [];
|
||||
[1, 2, 3].each(function(value) {
|
||||
Fixtures.Basic.each(function(value) {
|
||||
if (value == 2) return;
|
||||
results.push(value);
|
||||
});
|
||||
|
@ -76,7 +78,26 @@
|
|||
|
||||
testEachChaining: function() {with(this) {
|
||||
assertEqual(Fixtures.Primes, Fixtures.Primes.each(Prototype.emptyFunction));
|
||||
assertEqual(3, [1, 2, 3].each(Prototype.emptyFunction).length);
|
||||
assertEqual(3, Fixtures.Basic.each(Prototype.emptyFunction).length);
|
||||
}},
|
||||
|
||||
testEnumContext: function() {with(this) {
|
||||
var results = [];
|
||||
Fixtures.Basic.each(function(value) {
|
||||
results.push(value * this.i);
|
||||
}, { i: 2 });
|
||||
|
||||
assertEqual('2 4 6', results.join(' '));
|
||||
|
||||
assert(Fixtures.Basic.all(function(value){
|
||||
return value >= this.min && value <= this.max;
|
||||
}, { min: 1, max: 3 }));
|
||||
assert(!Fixtures.Basic.all(function(value){
|
||||
return value >= this.min && value <= this.max;
|
||||
}));
|
||||
assert(Fixtures.Basic.any(function(value){
|
||||
return value == this.target_value;
|
||||
}, { target_value: 2 }));
|
||||
}},
|
||||
|
||||
testAny: function() {with(this) {
|
||||
|
@ -86,11 +107,11 @@
|
|||
assert([true, false, false].any());
|
||||
assert(![false, false, false].any());
|
||||
|
||||
assert([1, 2, 3, 4, 5].any(function(value) {
|
||||
return value > 3;
|
||||
assert(Fixtures.Basic.any(function(value) {
|
||||
return value > 2;
|
||||
}));
|
||||
assert(![1, 2, 3, 4, 5].any(function(value) {
|
||||
return value > 10;
|
||||
assert(!Fixtures.Basic.any(function(value) {
|
||||
return value > 5;
|
||||
}));
|
||||
}},
|
||||
|
||||
|
@ -101,11 +122,11 @@
|
|||
assert(![true, false, false].all());
|
||||
assert(![false, false, false].all());
|
||||
|
||||
assert([1, 2, 3, 4, 5].all(function(value) {
|
||||
assert(Fixtures.Basic.all(function(value) {
|
||||
return value > 0;
|
||||
}));
|
||||
assert(![1, 2, 3, 4, 5].all(function(value) {
|
||||
return value > 3;
|
||||
assert(!Fixtures.Basic.all(function(value) {
|
||||
return value > 1;
|
||||
}));
|
||||
}},
|
||||
|
||||
|
@ -129,7 +150,7 @@
|
|||
assertEnumEqual([], [].eachSlice(2));
|
||||
assertEqual(1, [1].eachSlice(1).length);
|
||||
assertEnumEqual([1], [1].eachSlice(1)[0]);
|
||||
assertEqual(2, [1,2,3].eachSlice(2).length);
|
||||
assertEqual(2, Fixtures.Basic.eachSlice(2).length);
|
||||
assertEnumEqual(
|
||||
[3, 2, 1, 11, 7, 5, 19, 17, 13, 31, 29, 23, 43, 41, 37, 59, 53, 47, 71, 67, 61, 83, 79, 73, 97, 89],
|
||||
Fixtures.Primes.eachSlice( 3, function(slice){ return slice.reverse() }).flatten()
|
||||
|
@ -184,18 +205,20 @@
|
|||
assertEnumEqual([1, 2, 3, 4], arr[0]);
|
||||
assertEnumEqual([5, 6, null, null], arr[1]);
|
||||
|
||||
arr = [1, 2, 3].inGroupsOf(4,'x');
|
||||
var basic = Fixtures.Basic
|
||||
|
||||
arr = basic.inGroupsOf(4,'x');
|
||||
assertEqual(1, arr.length);
|
||||
assertEnumEqual([1, 2, 3, 'x'], arr[0]);
|
||||
|
||||
assertEnumEqual([1,2,3,'a'], [1,2,3].inGroupsOf(2, 'a').flatten());
|
||||
assertEnumEqual([1,2,3,'a'], basic.inGroupsOf(2, 'a').flatten());
|
||||
|
||||
arr = [1, 2, 3].inGroupsOf(5, '');
|
||||
arr = basic.inGroupsOf(5, '');
|
||||
assertEqual(1, arr.length);
|
||||
assertEnumEqual([1, 2, 3, '', ''], arr[0]);
|
||||
|
||||
assertEnumEqual([1,2,3,0], [1,2,3].inGroupsOf(2, 0).flatten());
|
||||
assertEnumEqual([1,2,3,false], [1,2,3].inGroupsOf(2, false).flatten());
|
||||
assertEnumEqual([1,2,3,0], basic.inGroupsOf(2, 0).flatten());
|
||||
assertEnumEqual([1,2,3,false], basic.inGroupsOf(2, false).flatten());
|
||||
}},
|
||||
|
||||
testInject: function() {with(this) {
|
||||
|
|
Loading…
Reference in New Issue