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()
.