1

在进行一些调试的过程中,我遇到了一种情况,最好是泛化该toUpperCase方法。以下是我想出的几种方法:

//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.push("a");
foo.toUpperCase();

//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.push("a");
toUpperCase(foo);

//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.push("a");
foo.toUpperCase();

//Constructor Prototype
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.push("a");
foo.constructor();

//toString override
var foo = [];
foo.push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();

大多数 String 和 Array 方法在规范中都有这个免责声明:

因此,它可以转移到其他种类的对象中作为方法使用。

是否有实现这种抽象的传统方法?

4

3 回答 3

3

如果您需要toUpperCase函数仅与数组一起使用,那么您可以Array像这样扩展类:

Array.prototype.toUpperCase = function () {
    return String(this).toUpperCase();
};

之后你可以写:

var foo = [];
foo.push('a');
foo.toUpperCase();
于 2013-06-13T20:42:10.930 回答
1

你不需要做任何事情:

String.prototype.toUpperCase.call(anyObject)

如果你想缩短它,你需要绑定calltoUpperCase

var toUpperCase = Function.call.bind(String.prototype.toUpperCase);
于 2013-06-13T18:47:24.843 回答
0

附加到全局对象的静态方法是以前实现的解决方案(更少的代码,更多的数据)。

必须编写通用方法,以便它们只要求具有最少的方法集。例如,大多数通用数组方法只需要它来提供长度和索引访问。

在数组中使用Array.prototype.slice转换有一个缺点:它会阻止浏览器 JavaScript 引擎执行优化arguments

推荐的方法是使用 Array.slice 泛型方法 - Array 泛型方法是专门实现用于其他类型的 - 但它不是 ECMAScript 规范的一部分

/**
 * Implementation of standard Array methods (introduced in ECMAScript 5th
 * edition) and shorthand generics (JavaScript 1.8.5)
 *
 * Copyright (c) 2013 Alex K @plusdude
 * http://opensource.org/licenses/MIT
 */
(function (global, infinity, undefined) {
    /*jshint bitwise:false, maxlen:95, plusplus:false, validthis:true*/
    "use strict";

    /**
     * Local references to constructors at global scope.
     * This may speed up access and slightly reduce file size of minified version.
     */
    var Array = global.Array;
    var Object = global.Object;
    var Math = global.Math;
    var Number = global.Number;

    /**
     * Converts argument to an integral numeric value.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
     */
    function toInteger(value) {
        var number;

        // let number be the result of calling ToNumber on the input argument
        number = Number(value);
        return (
            // if number is NaN, return 0
            number !== number ? 0 :

            // if number is 0, Infinity, or -Infinity, return number
            0 === number || infinity === number || -infinity === number ? number :

            // return the result of computing sign(number) * floor(abs(number))
            (0 < number || -1) * Math.floor(Math.abs(number))
        );
    }

    /**
     * Returns a shallow copy of a portion of an array.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.10
     */
    function slice(begin, end) {
        /*jshint newcap:false*/
        var result, elements, length, index, count;

        // convert elements to object
        elements = Object(this);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // calculate begin index, if is set
        if (undefined !== begin) {

            // convert to integer
            begin = toInteger(begin);

            // handle -begin, begin > length
            index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
        } else {
            // default value
            index = 0;
        }
        // calculate end index, if is set
        if (undefined !== end) {

            // convert to integer
            end = toInteger(end);

            // handle -end, end > length
            length = 0 > end ? Math.max(length + end, 0) : Math.min(end, length);
        }
        // create result array
        result = new Array(length - index);

        // iterate over elements
        for (count = 0; index < length; ++index, ++count) {

            // current index exists
            if (index in elements) {

                // copy current element to result array
                result[count] = elements[index];
            }
        }
        return result;
    }

    /**
     * Returns the first index at which a given element
     * can be found in the array.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.14
     */
    function indexOf(target, begin) {
        /*jshint newcap:false*/
        var elements, length, index;

        // convert elements to object
        elements = Object(this);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // calculate begin index, if is set
        if (undefined !== begin) {

            // convert to integer
            begin = toInteger(begin);

            // handle -begin, begin > length
            index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
        } else {
            // default value
            index = 0;
        }
        // iterate over elements
        for (; index < length; ++index) {

            // current index exists, target element is equal to current element
            if (index in elements && target === elements[index]) {

                // break loop, target element found
                return index;
            }
        }
        // target element not found
        return -1;
    }

    /**
     * Returns the last index at which a given element
     * can be found in the array.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.15
     */
    function lastIndexOf(target, begin) {
        /*jshint newcap:false*/
        var elements, length, index;

        // convert elements to object
        elements = Object(this);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // calculate begin index, if is set
        if (undefined !== begin) {

            // convert to integer
            begin = toInteger(begin);

            // handle -begin, begin > length - 1
            index = 0 > begin ? length - Math.abs(begin) : Math.min(begin, length - 1);
        } else {
            // default value
            index = length - 1;
        }
        // iterate over elements backwards
        for (; -1 < index; --index) {

            // current index exists, target element is equal to current element
            if (index in elements && target === elements[index]) {

                // break loop, target element found
                return index;
            }
        }
        // target element not found
        return -1;
    }

    /**
     * Executes a provided function once per array element.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18
     */
    function forEach(callback, scope) {
        /*jshint newcap:false*/
        var elements, length, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = 0; index < length; ++index) {

            // current index exists
            if (index in elements) {

                // execute callback
                callback.call(scope, elements[index], index, elements);
            }
        }
    }

    /**
     * Tests whether all elements in the array pass the test
     * implemented by the provided function.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.16
     */
    function every(callback, scope) {
        /*jshint newcap:false*/
        var elements, length, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = 0; index < length; ++index) {

            // current index exists
            if (index in elements &&

            // callback returns false
            !callback.call(scope, elements[index], index, elements)) {

                // break loop, test failed
                return false;
            }
        }
        // test passed, controversy began..
        return true;
    }

    /**
     * Tests whether some element in the array passes the test
     * implemented by the provided function.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.17
     */
    function some(callback, scope) {
        /*jshint newcap:false*/
        var elements, length, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = 0; index < length; ++index) {

            // current index exists
            if (index in elements &&

            // callback returns true
            callback.call(scope, elements[index], index, elements)) {

                // break loop, test passed
                return true;
            }
        }
        // test failed
        return false;
    }

    /**
     * Creates a new array with all elements that pass the test
     * implemented by the provided function.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.20
     */
    function filter(callback, scope) {
        /*jshint newcap:false*/
        var result = [], elements, length, index, count;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = count = 0; index < length; ++index) {

            // current index exists
            if (index in elements &&

            // callback returns true
            callback.call(scope, elements[index], index, elements)) {

                // copy current element to result array
                result[count++] = elements[index];
            }
        }
        return result;
    }

    /**
     * Creates a new array with the results of calling a provided function
     * on every element in this array.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.19
     */
    function map(callback, scope) {
        /*jshint newcap:false*/
        var result = [], elements, length, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = 0; index < length; ++index) {

            // current index exists
            if (index in elements) {

                // copy a return value of callback to result array
                result[index] = callback.call(scope, elements[index], index, elements);
            }
        }
        return result;
    }

    /**
     * Apply a function against values of the array (from left-to-right)
     * as to reduce it to a single value.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21
     */
    function reduce(callback, value) {
        /*jshint newcap:false*/
        var elements, isset, length, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // status of the initial value
        isset = undefined !== value;

        // convert length to unsigned 32 bit integer
        length = elements.length >>> 0;

        // iterate over elements
        for (index = 0; index < length; ++index) {

            // current index exists
            if (index in elements) {

                // initial value is set
                if (isset) {

                    // replace initial value with a return value of callback
                    value = callback(value, elements[index], index, elements);
                } else {
                    // current element becomes initial value
                    value = elements[index];

                    // status of the initial value
                    isset = true;
                }
            }
        }
        // make sure the initial value exists after iteration
        requireValue(isset);
        return value;
    }

    /**
     * Apply a function against values of the array (from right-to-left)
     * as to reduce it to a single value.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.22
     */
    function reduceRight(callback, value) {
        /*jshint newcap:false*/
        var elements, isset, index;

        // convert elements to object
        elements = Object(this);

        // make sure callback is a function
        requireFunction(callback);

        // status of the initial value
        isset = undefined !== value;

        // index of the last element
        index = (elements.length >>> 0) - 1;

        // iterate over elements backwards
        for (; -1 < index; --index) {

            // current index exists
            if (index in elements) {

                // initial value is set
                if (isset) {

                    // replace initial value with a return value of callback
                    value = callback(value, elements[index], index, elements);
                } else {
                    // current element becomes initial value
                    value = elements[index];

                    // status of the initial value
                    isset = true;
                }
            }
        }
        // make sure the initial value exists after iteration
        requireValue(isset);
        return value;
    }

    /**
     * Returns true if an argument is an array, false if it is not.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.3.2
     */
    function isArray(value) {
        return "[object Array]" === Object.prototype.toString.call(value);
    }

    /**
     * Tests if an argument is callable and throws an error if it is not.
     * @private
     */
    function requireFunction(value) {
        if ("[object Function]" !== Object.prototype.toString.call(value)) {
            throw new Error(value + " is not a function");
        }
    }

    /**
     * Throws an error if an argument can be converted to true.
     * @private
     */
    function requireValue(isset) {
        if (!isset) {
            throw new Error("reduce of empty array with no initial value");
        }
    }

    /**
     * Tests implementation of standard Array method.
     * @private
     */
    function supportsStandard(key) {
        var support = true;

        // a method exists
        if (Array.prototype[key]) {
            try {
                // apply dummy arguments
                Array.prototype[key].call(undefined, /test/, null);

                // passed? implemented wrong
                support = false;
            } catch (e) {
                // do nothing
            }
        } else {
            support = false;
        }
        return support;
    }

    /**
     * Tests implementation of generic Array method.
     * @private
     */
    function supportsGeneric(key) {
        var support = true;

        // a method exists
        if (Array[key]) {
            try {
                // apply dummy arguments
                Array[key](undefined, /test/, null);

                // passed? implemented wrong
                support = false;
            } catch (e) {
                // do nothing
            }
        } else {
            support = false;
        }
        return support;
    }

    /**
     * Assigns method to Array constructor.
     * @private
     */
    function extendArray(key) {
        if (!supportsGeneric(key)) {
            Array[key] = createGeneric(key);
        }
    }

    /**
     * Creates generic method from an instance method.
     * @private
     */
    function createGeneric(key) {
        /** @public */
        return function (elements) {
            var list;

            if (undefined === elements || null === elements) {
                throw new Error("Array.prototype." + key + " called on " + elements);
            }
            list = Array.prototype.slice.call(arguments, 1);
            return Array.prototype[key].apply(elements, list);
        };
    }

    /**
     * Assign ECMAScript-5 methods to Array constructor,
     * and Array prototype.
     */
    var ES5 = {
        "indexOf": indexOf,
        "lastIndexOf": lastIndexOf,
        "forEach": forEach,
        "every": every,
        "some": some,
        "filter": filter,
        "map": map,
        "reduce": reduce,
        "reduceRight": reduceRight
    };
    for (var key in ES5) {
        if (ES5.hasOwnProperty(key)) {

            if (!supportsStandard(key)) {
                Array.prototype[key] = ES5[key];
            }
            extendArray(key);
        }
    }
    Array.isArray = Array.isArray || isArray;

    /**
     * Assign ECMAScript-3 methods to Array constructor.
     * The toString method is omitted.
     */
    [
        "concat",
        "join",
        "slice",
        "pop",
        "push",
        "reverse",
        "shift",
        "sort",
        "splice",
        "unshift"

    ].forEach(extendArray);

    /**
     * Test the slice method on DOM NodeList.
     * Support: IE < 9
     */
    /*jshint browser:true*/
    if (document) {
        try {
            Array.slice(document.childNodes);
        } catch (e) {
            Array.prototype.slice = slice;
        }
    }

}(this, 1 / 0));

/*globals define*/
// Assumes all supplied String instance methods already present
// (one may use shims for these if not available)
(function() {
  'use strict';

  var i,
    // We could also build the array of methods with the following, but the
    //   getOwnPropertyNames() method is non-shimable:
    // Object.getOwnPropertyNames(String).filter(function(methodName) {
    //   return typeof String[methodName] === 'function';
    // });
    methods = [
      'quote', 'substring', 'toLowerCase', 'toUpperCase', 'charAt',
      'charCodeAt', 'indexOf', 'lastIndexOf', 'startsWith', 'endsWith',
      'trim', 'trimLeft', 'trimRight', 'toLocaleLowerCase',
      'toLocaleUpperCase', 'localeCompare', 'match', 'search',
      'replace', 'split', 'substr', 'concat', 'slice'
    ],
    methodCount = methods.length,
    assignStringGeneric = function(methodName) {
      var method = String.prototype[methodName];
      String[methodName] = function(arg1) {
        return method.apply(arg1, Array.prototype.slice.call(arguments, 1));
      };
    };

  for (i = 0; i < methodCount; i++) {
    assignStringGeneric(methods[i]);
  }
}());

参考

于 2016-10-03T19:58:07.810 回答