Статьи

Расширение основных объектов JavaScript

JavaScript определяет несколько объектов, которые являются частью его ядра: Array , Boolean , Date , Function , Math , Number , RegExp и String . Каждый объект расширяет Object , наследуя и определяя его собственные свойства и методы. Иногда мне нужно было дополнить эти основные объекты новыми свойствами и методами, и я создал библиотеку с этими улучшениями. В этой статье я представлю различные улучшения, которые я представил для объектов Array , Boolean , Date , Math , Number и String .

Я добавляю новые свойства непосредственно в основной объект. Например, если бы мне понадобилась Math константа для квадратного корня из 3, я бы указал Math.SQRT3 = 1.732050807; , Чтобы добавить новый метод, я сначала определяю, связан ли метод с основным объектом (метод объекта) или с экземплярами объекта (метод экземпляра). Если он ассоциируется с объектом, я добавляю его непосредственно к объекту (например, Math.factorial = function(n) { ... } ). Если он связан с экземплярами объекта, я добавляю его в прототип объекта (например, Number.prototype.abs = function() { ... } ).

Методы и ключевое слово this

В методе объекта this относится к самому объекту. В методе экземпляра this относится к экземпляру объекта. Например, в функции " remove leading and trailing whitespace ".trim() this относится к экземпляру " remove leading and trailing whitespace " " remove leading and trailing whitespace ".trim() " remove leading and trailing whitespace " объекта String методе trim() String .

Имя Коллизии

Вы должны быть осторожны с дополнением из-за возможности конфликтов имен. Например, предположим, что метод factorial() , реализация которого отличается от (и, возможно, более производительна, чем), ваш метод factorial() будет добавлен в Math в будущем. Вы, вероятно, не захотите использовать новый метод factorial() . Решение этой проблемы состоит в том, чтобы всегда проверять основной объект на наличие метода с тем же именем перед добавлением метода. Следующий фрагмент кода представляет собой демонстрацию:

  if (Math.factorial === undefined)
    Math.factorial = function (n)
                     {
                        // реализация
                     }
 оповещение (Math.factorial (6)); 

Конечно, это решение не является надежным. Можно добавить метод, список параметров которого отличается от списка параметров вашего метода. Чтобы быть абсолютно уверенным, что у вас не возникнет никаких проблем, добавьте уникальный префикс к имени вашего метода. Например, вы можете указать обратное имя вашего интернет-домена. Поскольку мое доменное имя — tutortutor.ca , я бы указал Math.ca_tutortutor_factorial . Хотя это громоздкое решение, оно должно дать некоторое спокойствие тем, кто обеспокоен конфликтами имен.

Увеличение массива

Объект Array позволяет создавать массивы и управлять ими. Два метода, которые сделали бы этот объект более полезным, это equals() , который сравнивает два массива на равенство, и fill() , который инициализирует каждый элемент массива указанным значением.

Реализация и тестирование equals()

Следующий фрагмент кода представляет реализацию метода equals() , который поверхностно сравнивает два массива — он не обрабатывает случай вложенных массивов:

  Array.prototype.equals =
    Функция (массив)
    {
       если (это === массив)
          вернуть истину;

       if (array === null || array === undefined)
          вернуть ложь;

       array = [] .concat (array);  // убедитесь, что это массив

       if (this.length! = array.length)
          вернуть ложь;

       для (var i = 0; i <this.length; ++ i) 
          если (это [я]! == массив [я]) 
             вернуть ложь;
       вернуть истину;
    }; 

equals() вызывается с аргументом array . Если текущий массив и array ссылаются на один и тот же массив ( === избегает преобразования типов; типы должны совпадать), этот метод возвращает true.

equals() следующий проверяет array на null или undefined . Когда передается любое из значений, этот метод возвращает false. Предполагая, что array содержит ни одного значения, equals() гарантирует, что он имеет дело с массивом, объединяя array в пустой массив.

equals() сравнивает длины массива, возвращая false, когда эти длины различаются. Затем он сравнивает каждый элемент массива с помощью !== (чтобы избежать преобразования типов), возвращая ложь в случае несовпадения. В этот момент массивы считаются равными и возвращают истинные значения.

Как всегда, важно тестировать код. В следующих тестовых примерах используется метод equals() , проверяющий различные возможности:

  массив var = [1, 2];
 alert ("array.equals (array):" + array.equals (array));

 alert ("['A', 'B']. equals (null):" + ['A', 'B']. equals (null));
 alert ("['A', 'B']. равно (не определено):" + ['A', 'B']. равно (не определено));

 alert ("[1] .equals (4.5):" + [1] .equals (4.5));

 alert ("[1] .equals ([1, 2]):" + [1] .equals ([1, 2]));

 var array1 = [1, 2, 3, 'X', false];
 var array2 = [1, 2, 3, 'X', false];
 var array3 = [3, 2, 1, 'X', false];
 alert ("array1.equals (array2):" + array1.equals (array2));
 alert ("array1.equals (array3):" + array1.equals (array3)); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод (через диалоговые окна оповещений):

  array.equals (array): true
 ['A', 'B']. Equals (null): false
 ['A', 'B']. Равно (не определено): false
 [1] .equals (4.5): нет
 [1] .equals ([1, 2]): неверно
 array1.equals (array2): true
 array1.equals (array3): false 

Внедрение и тестирование fill()

В следующем фрагменте кода представлена ​​реализация метода fill() , который заполняет все элементы массива, для которого этот метод вызывается, с одинаковым значением:

  Array.prototype.fill =
    Функция (пункт)
    {
       if (item === null || item === undefined)
          бросить "недопустимый аргумент:" + элемент;

       массив var = this;
       для (var i = 0; i <array.length; i ++)
          массив [i] = элемент;
       возвращаемый массив;
    }; 

fill() вызывается с аргументом item . Если передано значение null или undefined , этот метод генерирует исключение, которое идентифицирует любое значение. (Вы можете предпочесть заполнить массив null или undefined .) В противном случае он заполняет весь массив item и возвращает массив.

Я создал следующие тестовые случаи для проверки этого метода:

  пытаться
 {
    массив var = [0];
    array.fill (нуль);
 }
 поймать
 {
    alert («не может заполнить массив нулем»);
 }

 пытаться
 {
    массив var = [0];
    array.fill (не определено);
 }
 поймать
 {
    alert («не может заполнить массив неопределенным»);
 }

 var array = [];
 массив.длина = 10;
 array.fill ( 'X');
 alert ("array =" + array);

 alert ("[]. fill (10) =" + [] .fill (10)); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод:

  не может заполнить массив нулем
 не может заполнить массив неопределенным
 массив = X, X, X, X, X, X, X, X, X, X
 [] .fill (10) = 

Увеличивающий логический

Boolean объект — это объектная оболочка для логических значений true / false. Я добавил метод parse() для этого объекта, чтобы упростить разбор строк в значения true / false. Следующий фрагмент кода представляет этот метод:

  Boolean.parse =
    Функция (ы)
    {
       if (typeof s! = "string" || s == "")
          вернуть ложь;

       s = s.toLowerCase ();
       if (s == "true" || s == "yes")
          вернуть истину;
       вернуть ложь;
    }; 

Этот метод возвращает false для любого аргумента, который не является строкой, для пустой строки и для любого значения, отличного от "true" (регистр не имеет значения) или "yes" (регистр не имеет значения). Это возвращает истину для этих двух возможностей.

Следующие тесты используют этот метод:

  Оповещение (Boolean.parse (нуль));
 Оповещение (Boolean.parse (не определено));
 оповещение (Boolean.parse (4.5));
 Оповещение (Boolean.parse ( ""));
 Оповещение (Boolean.parse ( "Да"));
 Оповещение (Boolean.parse ( "TRUE"));
 не оповещение (Boolean.parse ( "нет"));
 Оповещение (Boolean.parse ( "ложь")); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод:

  ложный
 ложный
 ложный
 ложный
 правда
 правда
 ложный
 ложный 

Дата дополнения

Объект Date описывает один момент времени на основе значения времени, которое представляет собой количество миллисекунд с 1 января 1970 года по Гринвичу. Я добавил к этому объекту методы объекта и экземпляра isLeap() которые определяют, происходит ли конкретная дата в високосный год.

Реализация и тестирование isLeap() объекта isLeap()

Следующий фрагмент кода представляет реализацию метода объекта isLeap() , который определяет, представляет ли его аргумент date високосный год:

  Date.isLeap =
    Функция (дата)
    {
       if (Object.prototype.toString.call (date)! = '[дата объекта]')
          бросить "незаконный аргумент:" + дата;

       var year = date.getFullYear ();
       доход (год% 400 == 0) ||  (год% 4 == 0 && год% 100! = 0);
    }; 

Вместо использования date instanceof Date выражения date instanceof Date для определения, имеет ли аргумент date тип Date , этот метод использует более надежное выражение Object.prototype.toString.call(date) != '[object Date]' для проверки типа date instanceof Date будет возвращать false, когда date возникла из другого окна. При обнаружении аргумента без Date выдается исключение, которое идентифицирует аргумент.

После вызова метода Date getFullYear() для извлечения четырехзначного года из даты isLeap() определяет, является ли этот год високосным, или возвращает значение true для високосного года. Год является високосным, когда он делится на 400 или делится на 4, но не делится на 100.

Следующие тесты используют этот метод:

  пытаться
 {
    Оповещение (Date.isLeap (нуль));
 }
 поймать
 {
    alert («нулевые даты не поддерживаются.»);
 }

 пытаться
 {
    Оповещение (Date.isLeap (не определено));
 }
 поймать
 {
    alert («неопределенные даты не поддерживаются.»);
 }

 пытаться
 {
    Оповещение (Date.isLeap ( "ABC"));
 }
 поймать
 {
    alert («Строковые даты не поддерживаются.»);
 }

 переменная дата = новая дата ();
 оповещение (date + (Date.isLeap (date)? "Does": "not") +
       "представляет високосный год."); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать вывод, похожий на следующий:

  нулевые даты не поддерживаются.
 неопределенные даты не поддерживаются.
 Строковые даты не поддерживаются.
 Ср 23 октября 2013 19:30:24 GMT-0500 (Центральное стандартное время) не представляет високосный год. 

Реализация и тестирование isLeap() экземпляра isLeap()

Следующий фрагмент кода представляет реализацию метода экземпляра isLeap() , который определяет, представляет ли текущий экземпляр Date високосный год:

  Date.prototype.isLeap = 
    Функция ()
    {
       var year = this.getFullYear ();
       доход (год% 400 == 0) ||  (год% 4 == 0 && год% 100! = 0);
    }; 

Эта версия метода isLeap() похожа на его предшественницу, но не принимает аргумент date . Вместо этого он работает с текущим экземпляром Date , который представлен this .

Следующие тесты используют этот метод:

  дата = новая дата (2012, 0, 1);
 alert (date + ((date.isLeap ())? "do": "не") + 
       "представляет високосный год.");
 дата = новая дата (2013, 0, 1);
 alert (date + ((date.isLeap ())? "do": "не") + 
       "представляет високосный год."); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать вывод, похожий на следующий:

  Воскресенье 01 января 2012 00:00:00 GMT-0600 (центральное летнее время) представляет собой високосный год.
 Вт 01.01.2013 00:00:00 GMT-0600 (Центральное летнее время) не представляет високосный год. 

Увеличивая математику

Объект Math объявляет свойства и методы объекта, ориентированные на математику, и не может быть создан. Я добавил GOLDEN_RATIO объекта GOLDEN_RATIO и GOLDEN_RATIO объектов rnd() , toDegrees() , toRadians() и trunc() в Math .

О золотом сечении

Золотое сечение — это математическая константа, которая часто встречается в геометрии. Две величины находятся в золотом сечении, когда их отношение равно отношению их суммы к большей из двух величин. Другими словами, для больше чем b , a/b = (a+b)/a .

Внедрение и тестирование GOLDEN_RATIO и rnd()

Следующий фрагмент кода представляет реализацию константы GOLDEN_RATIO и функции rnd()
метод:

  Math.GOLDEN_RATIO = 1,61803398874;

 Math.rnd =
    функция (предел)
    {
       if (typeof limit! = "number")
          бросить "незаконный аргумент:" + предел;
  
       return Math.random () * limit |  0;
    }; 

После определения GOLDEN_RATIO объекта GOLDEN_RATIO этот фрагмент кода определяет метод объекта rnd() , который принимает аргумент limit . Этот аргумент должен быть числовым; если нет, выдается исключение.

Math.random() возвращает дробное значение от 0,0 до (почти) 1,0. После умножения на limit фракция остается. Эта дробь удаляется путем усечения, а усечение выполняется побитовым ИЛИ 0 с результатом.

Побитовое ИЛИ использует внутреннюю функцию ToInt32 для преобразования своих числовых операндов в 32-разрядные целые числа со ToInt32 . Эта операция исключает дробную часть числа и является более производительной, чем использование Math.floor() поскольку вызов метода не требуется.

Следующие тесты проверяют эти элементы:

  оповещение ("Math.GOLDEN_RATIO:" + Math.GOLDEN_RATIO);

 пытаться
 {
    alert ("Math.rnd (null):" + Math.rnd (null));
 }
 поймать
 {
    alert («нулевое значение не поддерживается.»);
 }
 alert ("Math.rnd (10):" + Math.rnd (10)); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать вывод, похожий на следующий:

  Math.GOLDEN_RATIO: 1.61803398874
 нулевое значение не поддерживается.
 Математика (10): 7 

Реализация и тестирование toDegrees() , toRadians() и trunc()

В следующем фрагменте кода представлена ​​реализация методов toDegrees() , toRadians() и trunc() :

  Math.toDegrees = 
    Функция (радианы)
    {
       if (typeof радианы! = "число")
          киньте «незаконный аргумент»: + радианы;

       обратные радианы * (180 / Math.PI);
    };

 Math.toRadians = 
    Функция (в градусах)
    {
       if (typeof градусов! = "число")
          бросить "незаконный аргумент:" + градусы;

       обратные градусы * (Math.PI / 180);
    };


 Math.trunc =
    Функция (п)
    {
       if (typeof n! = "number")
          бросить "недопустимый аргумент": + n;
  
       возврат (n> = 0)?  Math.floor (n): -Math.floor (-n);
    }; 

Каждый метод требует числового аргумента и выдает исключение, когда это не так. Первые два метода выполняют простые преобразования в градусы или радианы, а третий метод усекает аргумент с помощью метода floor() Math .

Зачем вводить метод trunc() когда floor() уже выполняет усечение? Когда он получает отрицательный нецелочисленный аргумент, floor() округляет это число до следующего наибольшего отрицательного целого числа. Например, floor() преобразует -4.1 в -5 вместо более желательного -4 .

Следующие тесты проверяют эти элементы:

  пытаться
 {
    alert ("Math.toDegrees (null):" + Math.toDegrees (null));
 }
 поймать
 {
    alert («нулевые градусы не поддерживаются»);
 }
 alert ("Math.toDegrees (Math.PI):" + Math.toDegrees (Math.PI));

 пытаться
 {
    alert ("Math.toRadians (null):" + Math.toRadians (null));
 }
 поймать
 {
    alert («нулевые радианы не поддерживаются»);
 }
 alert ("Math.toRadians (180):" + Math.toRadians (180));

 пытаться
 {
    alert ("Math.trunc (null):" + Math.trunc (null));
 }
 поймать
 {
    alert («нулевое значение не поддерживается.»);
 }
 alert ("Math.trunc (10.83):" + Math.trunc (10.83));
 alert ("Math.trunc (-10.83):" + Math.trunc (-10.83)); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод:

  нулевые градусы не поддерживаются.
 Math.toDegrees (Math.PI): 180
 нулевые радианы не поддерживаются.
 Math.toRadians (180): 3.141592653589793
 нулевое значение не поддерживается.
 Math.trunc (10,83): 10
 Math.trunc (-10,83): -10 

Увеличивая номер

Объект Number — это объектная оболочка для 64-битных чисел с плавающей запятой двойной точности. В следующем фрагменте кода представлена ​​реализация экземпляра метода trunc() , аналогичного его аналогу метода объекта в объекте Math :

  Number.prototype.trunc = 
    Функция ()
    {
       var num = this;
       возврат (число <0)?  -Math.floor (-num): Math.floor (num);
    }; 

Следующие тесты используют этот метод:

  alert ("(25.6) .trunc ():" + (25.6) .trunc ());
 alert ("(- 25,6) .trunc ():" + (-25,6) .trunc ());
 alert ("10..trunc ():" + 10..trunc ()); 

Две точки в 10..trunc() не позволяют синтаксическому анализатору JavaScript предполагать, что trunc является дробной частью (что можно предположить при обнаружении 10.trunc() ), и сообщать об ошибке. Чтобы быть более понятным, я мог бы поставить 10. в круглых скобках, как в (10.).trunc() .

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод:

  (25.6) .trunc (): 25
 (-25,6) .trunc (): -25
 10..trunc (): 10 

Увеличивающая строка

Объект String является оберткой для строк. Я добавил endsWith() , reverse() и startsWith() , которые похожи на их аналоги в языке Java для этого объекта.

Реализация и тестирование endsWith() и startsWith()

В следующем фрагменте кода представлена ​​реализация методов endsWith() и startsWith() которые выполняют сравнение с учетом регистра суффикса или префикса с концом или началом строки соответственно:

  String.prototype.endsWith = 
    Функция (суффикс) 
    {
       if (суффикс typeof! = "строка")
          бросить "недопустимый аргумент" + суффикс;

       if (суффикс == "")
          вернуть истину;

       var str = this;
       var index = str.length - суффикс.length;
       вернуть str.substring (индекс, индекс + суффикс.длина) == суффикс;
    };

 String.prototype.startsWith = 
    Функция (префикс)
    {
       if (префикс typeof! = "строка")
          бросить "недопустимый аргумент" + префикс;

       если (префикс == "")
          вернуть истину;

       var str = this;
       вернуть str.substring (0, prefix.length) == префикс;
    }; 

Каждый из endsWith() и startsWith() аналогичен тем, что сначала проверяет, является ли его аргумент строкой, и startsWith() исключение, если это не так. Затем он возвращает true, когда его аргумент является пустой строкой, потому что пустые строки всегда совпадают.

Каждый метод также использует метод substring() String для извлечения соответствующего суффикса или префикса из строки перед сравнением. Однако они отличаются в своих вычислениях начального и конечного индексов, которые передаются в substring() .

Следующие тесты используют эти методы:

  пытаться
 {      
    alert ("'abc'.endsWith (undefined):" + "abc" .endsWith (undefined));
 }
 поймать
 {
    оповещение («не строка»);
 }
 alert ("'abc'.endsWith (' '):" + "abc" .endsWith (""));
 alert ("'это тест" .endsWith ("тест"): "+
       "это тест" .endsWith ("тест"));
 alert ("'abc'.endsWith (' abc '):" + "abc" .endsWith ("abc"));
 alert ("'abc'.endsWith (' Abc '):" + "abc" .endsWith ("Abc"));
 alert ("'abc'.endsWith (' abcd '):" + "abc" .endsWith ("abcd"));

 пытаться
 {      
    alert ("'abc'.startsWith (undefined):" + "abc" .startsWith (undefined));
 }
 поймать
 {
    оповещение («не строка»);
 }
 alert ("'abc'.startsWith (' '):" + "abc" .startsWith (""));
 alert ("это тест" .startsWith ('this'): "+
       "это тест" .startsWith ("это"));
 alert ("'abc'.startsWith (' abc '):" + "abc" .startsWith ("abc"));
 alert ("abc.startsWith ('Abc'):" + "abc" .startsWith ("Abc"));
 alert ("'abc'.startsWith (' abcd '):" + "abc" .startsWith ("abcd")); 

Когда вы запускаете эти тестовые случаи, вы должны наблюдать следующий вывод:

  не строка
 'abc'.endsWith (' '): правда
 'это тест' .endsWith ('тест'): правда
 'abc'.endsWith (' abc '): правда
 'abc'.endsWith (' Abc '): false
 'abc'.endsWith (' abcd '): false
 не строка
 'abc'.startsWith (' '): правда
 'это тест' .startsWith ('это'): правда
 'abc'.startsWith (' abc '): правда
 'abc'.startsWith (' Abc '): false
 'abc'.startsWith (' abcd '): false 

Реализация и тестирование reverse()

В следующем фрагменте кода представлена ​​реализация метода reverse() который обращает символы строки, для которой вызывается этот метод, и возвращает полученную строку:

  String.prototype.reverse = 
    Функция ()
    {
       var str = this;
       var revStr = "";
       для (var i = str.length - 1; i> = 0; i--)
          revStr + = str.charAt (i);
       return revStr;
    }; 

reverse() перебирает строку назад и добавляет каждый символ во временную строковую переменную, которая возвращается. Поскольку конкатенация строк стоит дорого, вы можете предпочесть return this.split("").reverse().join(""); -ориентированное выражение, например return this.split("").reverse().join(""); ,

Следующий тест выполняет этот метод:

  alert ("'abc'.reverse ():" + "abc" .reverse ()); 

Когда вы запускаете этот тестовый пример, вы должны наблюдать следующий вывод:

  'abc'.reverse (): cba 

Вывод

JavaScript позволяет легко расширять свои основные объекты новыми возможностями, и вы, вероятно, можете придумать дополнительные примеры.

Я считаю, что проще всего поместить все новые определения свойств и методов базового объекта в отдельный файл (например, date.js ) и включить файл в заголовок страницы с помощью элемента <script> (например, <script type="text/javascript" src="date.js"><script> ).

Для домашней работы добавьте метод shuffle() к объекту Array чтобы перемешать массив элементов (например, объекты игральных карт). Используйте в этой статье метод rnd() .