UNPKG

plus

Version:

Enhanced Array-like Objects support, Array iteration methods for plain Objects and more

357 lines (347 loc) 8.81 kB
var call = Function.prototype.call, apply = Function.prototype.apply, arrayProto = Array.prototype, forEach = call.bind( arrayProto.forEach ), slice = call.bind( arrayProto.slice ), concat = apply.bind( arrayProto.concat ), toString = call.bind( Object.prototype.toString ), r_cleanType = /^\[object |\]$/g, typeCache = {}; global.typeOf = function( obj ) { var type = ( obj == null ? String : toString )( obj ); return typeCache[ type ] || ( typeCache[ type ] = type.replace( r_cleanType, "" ).toLowerCase() ); }; function deepExtend( dest, src ) { if ( ( src && typeOf( src ) ) !== "object" ) { return ( src && src.isArrayLike() ) ? slice( src, 0 ) : src.valueOf(); } if ( ( dest && typeOf( dest ) ) !== "object" ) { dest = {}; } for ( var key in src ) { dest[ key ] = deepExtend( dest[ key ], src[ key ] ); } return dest; } Object.defineProperty( Object, "arrayTypes", { value: { arguments: true }, enumerable: true }); var methods = { extend: function() { if ( this == null ) { throw new TypeError(); } var target = this; forEach( arguments, function( src ) { for ( var key in src ) { target[ key ] = src[ key ]; } }); return target; }, deepExtend: function() { if ( this == null ) { throw new TypeError(); } var target = this; forEach( arguments, function( src ) { target = deepExtend( target, src ); }); return target; }, hiddenExtend: function() { if ( this == null ) { throw new TypeError(); } var definitions = {}, key; forEach( arguments, function( src ) { for ( key in src ) { definitions[ key ] = { value: src[ key ] }; } }); for ( key in definitions ) { if ( key in this ) { delete this[ key ]; } } Object.defineProperties( this, definitions ); return this; }, isArrayLike: function() { return !!( this && ( this.__array_like__ || Array.isArray( this ) || Object.arrayTypes[ typeOf( this ) ] ) ); } }; function flatten( object, array, callback ) { if ( object && object.isArrayLike() ) { forEach( object, function( object ) { flatten( object, array, callback ); }); } else { if ( callback ) { try { object = callback( object ); } catch( e ) { return array; } } if ( array ) { array.push( object ); } } return array; } function objectSortCompare( compare ) { return function( a, b ) { a = a.value; b = b.value; if ( compare ) { return compare( a, b ); } a = String( a ); b = String( b ); return a < b ? -1 : ( a === b ? 0 : 1 ); }; } var arrayMethods = { concat: false, every: function( callback, context ) { if ( typeof callback !== "function" ) { throw new TypeError( "First argument is not callable" ); } callback = callback.bind( context ); for( var key in this ) { if ( !callback( this[ key ], key, this ) ) { return false; } } return true; }, filter: function( callback, context ) { if ( typeof callback !== "function" ) { throw new TypeError( "First argument is not callable" ); } var output = {}, key; callback = callback.bind( context ); for( key in this ) { if ( callback( this[ key ], key, this ) ) { output[ key ] = this[ key ]; } } return output; }, flatten: function( callback, _ ) { var array = ( callback === false ) ? undefined : []; if ( !array ) { callback = _; } if ( callback && typeof callback !== "function" ) { throw new TypeError( ( array ? "Second" : "First" ) + " argument is absent or not callable" ); } if ( callback || array ) { return flatten( this, array, callback ); } }, forEach: function( callback, context ) { if ( typeof callback !== "function" ) { throw new TypeError( "First argument is not callable" ); } callback = callback.bind( context ); for( var key in this ) { callback( this[ key ], key, this ); } }, indexOf: function( element, fromIndex ) { var indexPassed = ( arguments.length < 2 ), key; for( key in this ) { if ( !indexPassed ) { if ( key !== fromIndex ) { continue; } indexPassed = true; } if ( this[ key ] === element ) { return key; } } return -1; }, join: function( separator ) { var array = [], key; for ( key in this ) { array.push( this[ key ] ); } return array.join( separator ); }, lastIndexOf: function( element, fromIndex ) { var indexPassed = ( arguments.length < 2 ), keys = Object.keys( this ), key, index = keys.length; while(( index-- )) { key = keys[ index ]; if ( !indexPassed ) { if ( key !== fromIndex ) { continue; } indexPassed = true; } if ( this[ key ] === element ) { return key; } } return -1; }, map: function( callback, context ) { if ( typeof callback !== "function" ) { throw new TypeError( "First argument is not callable" ); } var output = {}, key; callback = callback.bind( context ); for( key in this ) { output[ key ] = callback( this[ key ], key, this ); } return output; }, pop: false, push: false, reduce: function( cumulate, initial ) { if ( typeof cumulate !== "function" ) { throw new TypeError( "First argument is not callable" ); } var cumul = initial, key, loop; for( key in this ) { if ( !loop ) { loop = true; if ( arguments.length === 1 ) { cumul = this[ key ]; continue; } } cumul = cumulate( cumul, this[ key ], key, this ); } if ( !loop && arguments.length === 1 ) { throw new TypeError( "Empty array and no second argument" ); } return cumul; }, reduceRight: function( cumulate, initial ) { if ( typeof cumulate !== "function" ) { throw new TypeError( "First argument is not callable" ); } var cumul = initial, keys = Object.keys( this ), key, index = keys.length, loop; while(( index-- )) { key = keys[ index ]; if ( !loop ) { loop = true; if ( arguments.length === 1 ) { cumul = this[ key ]; continue; } } cumul = cumulate( cumul, this[ key ], key, this ); } if ( !loop && arguments.length === 1 ) { throw new TypeError( "Empty array and no second argument" ); } return cumul; }, reverse: function() { var key, value, keys = Object.keys( this ), index = keys.length; while(( index-- )) { key = keys[ index ]; value = this[ key ]; delete this[ key ]; this[ key ] = value; } }, shift: false, slice: false, splice: false, some: function( callback, context ) { if ( typeof callback !== "function" ) { throw new TypeError( "First argument is not callable" ); } callback = callback.bind( context ); for( var key in this ) { if ( callback( this[ key ], key, this ) ) { return true; } } return false; }, sort: function( compare ) { if ( arguments.length && typeof compare !== "function" ) { throw new TypeError( "First argument is not callable" ); } var array = [], key, index, length; for ( key in this ) { array.push({ key: key, value: this[ key ] }); } array.sort( objectSortCompare( compare ) ); for ( index = 0, length = array.length; index < length; index++ ) { key = array[ index ].key; delete this[ key ]; this[ key ] = array[ index ].value; } }, unshift: false }, needsFix = { concat: function( object, args ) { return concat( slice( object, 0 ), args ); } }; for ( var method in arrayMethods ) { (function( method, key ) { var arrayMethod = arrayProto[ key ]; if ( !arrayMethod ) { if ( method ) { Object.defineProperty( arrayProto, key , { value: (( methods[ key ] = method )) }); } } else { if ( method ) { method = apply.bind( method ); } arrayMethod = needsFix[ key ] || apply.bind( arrayMethod ); methods[ key ] = function() { if ( this == null ) { throw new TypeError(); } if ( this.isArrayLike() ) { return arrayMethod( this, arguments ); } if ( !method ) { throw new TypeError( "Object is not an array or an array-like object" ); } return method( this, arguments ); }; } })( arrayMethods[ method ], method ); } methods.hiddenExtend.call( Object.prototype, methods );