Обобщите и проанализируйте обязательное преобразование типов в JavaScript, в основном обратитесь к главе 4 «JavaScript, которого вы не знаете (том 2)».
Содержание статьи в основном разделено на пять частей.В первой части описываются 4 абстрактные операции преобразования в данные базового типа, во второй части описывается явное приведение, в третьей части описывается неявное приведение, а в четвертой части описывается равенство.Среди них: вам нужно освоить «алгоритм сравнения абстрактного равенства» в сочетании с четырьмя абстрактными операциями в первой части, вы можете в основном понять процесс преобразования типов в JS, а пятая часть описывает отношения сравнения.
1. Операции с абстрактными значениями
1.1 ToString
Абстрактная операция ToString обрабатывает приведения типов, отличных от строки к строке.
Правила стробирования для значений базового типа таковы: null преобразуется в «null», undefined преобразуется в «undefined», а true преобразуется в «true». Строкообразование чисел следует общим правилам, те самые маленькие и очень большие числа используют экспоненциальную форму:
var a = 1.07*1000*1000*1000*1000*1000*1000*1000
a.toString() //"1.07e21"
Для обычных объектов, если вы не определили его самостоятельно, toString() возвращает значение внутреннего свойства [[Class]], например "[object Object]".
Метод toString() по умолчанию для массивов был переопределен, чтобы преобразовать все элементы в строки, а затем соединить их с помощью ",":
var a = [1,2,3]
a.toString() //"1,2,3"
1.2 ToNumber
Абстрактная операция ToNumber преобразует нечисловое значение в числовое.
Среди них true преобразуется в 1, false — в 0, undefined — в NaN, а null — в 0.
Обработка строк ToNumber в основном следует соответствующим правилам числовых констант (строка содержит нечисловые символы и возвращает NaN).
Объекты (включая массивы) сначала преобразуются в соответствующее значение типа-примитива, и если возвращается нечисловое значение типа-примитива, оно затем приводится к числу в соответствии с приведенными выше правилами.
1.3 ToPrimitive
Абстрактный метод ToPrimitive преобразует значение объекта в соответствующее значение типа-примитива. Этот метод сначала проверит, есть ли у значения метод valueOf(). Если есть и вернет значение базового типа, используйте значение для приведения типа; если нет, используйте возвращаемое значение toString() (если оно существует) для привести тип. ; Если ни valueOf(), ни toString() не возвращают примитивное значение, будет выдано сообщение об ошибке TypeError.
Начиная с ES5, объекты, созданные с помощью Object.create(null), имеют свойство прототипа null и не имеют методов valueOf() и toString(), поэтому приведение типов невозможно.
1.4 ToBoolean
Абстрактная операция ToBoolean преобразует нелогическое значение в логическое значение.
1.4.1 Ложные значения
Логическое приведение ложного значения приводит к ложному.
Ниже приведены ложные значения:
- undefined
- null
- false
- +0, -0 и NaN
- ""
Логически все, кроме списка ложных значений, должно быть истинным, но спецификация JavaScript не определяет это явно, а просто дает несколько примеров, например, оговаривает, что все объекты истинны.
1.4.2 Ложные объекты
Например:
var a = new Boolean(false)
var b = new Boolean(0)
var c = new Boolean("")
var d = Boolean(a && b && c) //true
a, b и c — все объекты, которые инкапсулируют ложные значения, но d является истинным, указывая на то, что все a, b и c являются истинными. Таким образом, объект ложного значения не является объектом, который инкапсулирует ложное значение.
Ложные объекты выглядят точно так же, как обычные объекты, но приведение их к логическому значению приводит к ложному результату. Наиболее распространенным примером является document.all, который представляет собой массивоподобный объект, содержащий все элементы на странице. Раньше он был реальным объектом, и логическое преобразование было истинным, но теперь это ложный объект.
1.4.3 Значение истины
Истинное значение — это значение вне списка ложных значений. Список истинных значений может быть бесконечно длинным и не может быть пронумерован по одному, поэтому список ложных значений можно использовать только как справочный.
2. Отображение слепков
2.1 Преобразование отображения между строками и числами
Преобразование между строками и числами осуществляется двумя встроенными функциями String() и Number().Обратите внимание, что перед ними нет нового ключевого слова, и не создаются инкапсулированные объекты.
String() преобразует значение в примитивный тип строки в соответствии с упомянутыми ранее правилами ToString. Number() преобразует значение в числовой примитивный тип в соответствии с правилом ToNumber, описанным ранее.
В дополнение к String() и Number() существуют другие методы явного преобразования между строками и числами:
var a = 42
var b = a.toSting() //"42"
var c = "3.14"
var d = +c //3.14
a.toString() является явным, но задействованы неявные преобразования. Поскольку toString() не работает для примитивных значений, таких как 42, движок JavaScript автоматически создает объект-оболочку для 42, а затем вызывает toString() для этого объекта.
+c в приведенном выше примере является унарной формой оператора + (то есть имеет только один операнд). Оператор + явно преобразует c в число, а не в числовое сложение (или в конкатенацию строк).
Оператор NOARARY - делает то же самое, что и +, а также инвертирует знак знаки числа. Поскольку - рассматривается как оператор уменьшения, мы не можем использовать - чтобы отменить инверсию, но вместо этого добавить пространство, такое как - - «3.14».
Старайтесь не использовать унарный оператор + (и -) вместе с другими операторами. Кроме того, d = +c также легко спутать с d += c, что сильно отличается.
2.1.1 Отображение даты преобразовано в числа
Другим распространенным применением унарного оператора + является приведение объекта Date к числу с возвратом результата в виде метки времени Unix.
var time = new Date()
+time
2.1.2 Причудливый оператор ~
~ сначала преобразует значение в 32-битное число, затем выполняет битовую операцию «НЕТ» (инвертирует каждый бит слова).
Графическая инверсия — малоизвестная тема, и разработчикам JavaScript обычно редко приходится заботиться об уровне графемы.
Да, может быть и другая интерпретация: вернуть дополнение до 2!
Таким образом, ~x примерно эквивалентно -(x+1).
~42 //-(42+1) ==> -43
Единственное значение x, которое может получить 0 (или, строго говоря, -0) в -(x+1), равно -1, а некоторые функции в JavaScript используют -1 для представления сбоя выполнения и используют значения больше или равные 0 для представления выполнения функций успешно.
Например, метод indexOf() ищет в строке указанную строку и возвращает позицию подстроки, если она найдена, иначе возвращает -1.
var a = "Hello World"
if(a.indexOf("lo") != -1){
// 找到匹配
}
if(a.indexOf("ol") == -1){
// 没有找到匹配
}
~ и indexOf() вместе могут привести результат к истинному/ложному значению, если indexOf() возвращает -1, ~ преобразует его в ложное значение 0, в противном случае он преобразуется в истинное значение.
var a = "Hello World"
~a.indexOf("lo") // -4 ==>真值
if(~a.indexOf("lo")){
// 找到匹配
}
~a.indexOf("ol") // 0 ==>假值
if(!~a.indexOf("ol")){
// 没有找到匹配
}
2.1.3 Усечение битов слова
~~x может обрезать значение до целого числа 32, первый из ~~ выполняет ToInt32 и меняет местами биты слова, а затем второй ~ снова выполняет инверсию битов слова, то есть все биты слова меняются местами Вернуться к исходному значению , окончательный результат по-прежнему является результатом ToInt32.
Во-первых, ~~ работает только с 32-битными числами и, что более важно, обрабатывает отрицательные числа иначе, чем Math.floor().
Math.floor(-49.6) // -50
~~-49.6 //-49
2.2 Явный разбор числовых строк
Результатом синтаксического анализа числа в строке и приведения строки к числу являются все числа. Но есть четкая разница между синтаксическим анализом и преобразованием. Например:
var a = "42"
var b = "42px"
Number(a) //42
parseInt(a) //42
Number(b) //NaN
parseInt(b) //42
Синтаксический анализ разрешает нечисловые символы в строке слева направо и останавливается, если встречается нечисловой символ. Преобразование не допускает нечисловых символов, иначе оно завершится ошибкой и вернет NaN.
Чтобы проанализировать числа с плавающей запятой в строке, используйте функцию parseFloat(). Поскольку ES5 parseInt() по умолчанию преобразуется в десятичное число, если только второй аргумент не указан в виде системы счисления.
Не забывайте, что parseInt() работает со строками, передача чисел и других типов аргументов в parseInt() бесполезна. Нестроки сначала преобразуются в строки, и вам следует избегать передачи нестроковых аргументов в функцию parseInt().
parseInt(1/0,19) //18
Окончательный результат parseInt(1/0,19) равен 18, а не ошибка, потому что parseInt(1/0,19) на самом деле является parseInt("Infinity",19). Основание 19, его допустимый диапазон числовых символов 0-9 и ai (с учетом регистра), в основании 19 первый символ «I» равен 18, а второй символ «n» не является допустимым числовым символом, проанализированным до сих пор, то же, что "p" в "42px".
2.3 Явное преобразование в логическое значение
И тип +, упомянутый ранее, унарный оператор !, явно приводит значение к логическому типу. Но он также одновременно инвертирует истинное значение в ложное. Таким образом, наиболее распространенным способом явного приведения к логическому значению является !!.
В логическом контексте, таком как if(), рекомендуется использовать Boolean() и !! для явных преобразований, чтобы сделать код более читабельным.
3. Неявное принуждение
3.1 Неявное приведение типов между строками и числами
Определено в спецификации ES5: + будет конкатенировать, если операнд является строкой или может быть преобразован в строку с помощью следующих шагов. Если один из операндов является объектом (включая массив), сначала для него вызывается абстрактная операция ToPrimitive, которая затем вызывает [[DefaultValue]] с номером в качестве контекста.
Проще говоря, если один из операндов + является строкой (или строка может быть получена с помощью описанных выше шагов), то выполняется конкатенация строк, в противном случае выполняется числовое сложение.
var a = [1,2]
var b = [3,4]
a + b //"1,23,4"
Поскольку операция valueOf() массива не может получить значение простого базового типа, вызывается toString(), поэтому два массива становятся «1,2» и «3,4», + объединяются и возвращаются.
Следует отметить тонкую разницу между символом + "" (неявным) и предыдущей строкой (a) (явным). В соответствии с правилами абстрактной операции ToPrimitive + "" вызывает метод valueOf() для a, а затем преобразует возвращаемое значение в строку с помощью абстрактной операции ToString, в то время как String(a) напрямую вызывает метод toString().
3.2 Неявное приведение логического значения к числу
onlyOne() возвращает истину, если любой и только один из аргументов истинен.
function onlyOne() {
var sum = 0
for (var i=0;i<arguments.length;i++){
if(arguments[i]){
sum += arguments[i]
}
}
return sum == 1
}
var a = true
var b = false
onlyOne(b,a,b,b,b,b) // true
Независимо от того, используется ли неявное или явное преобразование, мы можем справиться с более сложными ситуациями, изменив onlyTwo() или onlyFive(), просто изменив окончательное условное суждение с 2 или 5. Это намного чище, чем добавление множества выражений && и ||.
3.3 Неявное приведение к логическому значению
Неявное принуждение в числовых и строковых операциях относительно очевидно по сравнению с булевыми значениями. Логическое неявное принуждение происходит в следующих случаях.
- Выражение условного суждения в операторе if()
- Условное выражение в операторе for(..; ..; ..)
- в то время как () и сделать .. пока ()
- ? : Выражение условного суждения в
- Операнды слева от логических операторов || и &&
3.4 || и &&
На самом деле называть их «логическими операторами» не очень хорошая идея, потому что это не очень точно. Правильнее было бы назвать их «операторами-селекторами» или «операторами-селекторами операндов».
Спецификация ES5 говорит, что возвращаемое значение операторов && и || не обязательно имеет логический тип, а является значением одного из двух операндов.
Для ||, если условие истинно, возвращается значение первого операнда, а если ложно, возвращается значение второго операнда.
Для && вернуть значение второго операнда, если условие истинно, и вернуть значение первого операнда, если оно ложно.
** Результат условной оценки здесь относится к левому операнду.
Ниже приведено очень распространенное использование ||, которое вы могли использовать, но не до конца поняли:
function foo(a,b) {
a = a||"hello"
b = b||"world"
console.log(a + '' + b)
}
foo() // "hello world"
Глядя на && снова, можно заметить, что разработчики нечасто замечают его использование, но обычно используются минимизаторы кода JavaScript. То есть, если первый операнд является истинным значением, оператор && выбирает второй операнд в качестве возвращаемого значения, который также называется «оператором-хранителем», то есть предыдущее выражение является привратником для последнего выражения.
function foo() {
console.log(a)
}
var a = 42
a && foo()
3.5 Соответствующие слепки
Соответствующие типы были введены в ES6, и его приведение имеет ямку. ES6 допускает явное приведение конформантов к строкам, однако неявное приведение приводит к ошибкам, таким как:
var s1 = Symbol("cool")
String(s1) // "Symbol(cool)"
var s2 = Symbol("not cool")
s2 + '' // TypeError
Соответствие не может быть приведено к числу (и явное, и неявное генерируют ошибку), но может быть приведено к логическому значению (явное и неявное оба являются истинными).
4. Свободное и строгое равенство
Распространенным недоразумением является «== проверяет равенство значений, === проверяет равенство значений и типов», правильная интерпретация: «== допускает приведения в сравнениях на равенство, а === — нет». На самом деле и ==, и === проверяют типы операндов, разница в том, что они обрабатываются по-разному, когда типы операндов разные.
4.1 Абстрактное равенство
Раздел 11.9.3 спецификации ES5 «Алгоритм сравнения абстрактного равенства» определяет поведение оператора ==. Алгоритм прост и всеобъемлющ, он охватывает все возможные комбинации типов и способы их приведения.
比较运算x==y, 其中x和 y是值,产生true或者false。这样的比较按如下方式进行:
1. 若Type(x)与Type(y)相同, 则
a. 若Type(x)为Undefined, 返回true。
b. 若Type(x)为Null, 返回true。
c. 若Type(x)为Number, 则
i. 若x为NaN, 返回false。
ii. 若y为NaN, 返回false。
iii. 若x与y为相等数值, 返回true。
iv. 若x 为 +0 且 y为−0, 返回true。
v. 若x 为 −0 且 y为+0, 返回true。
vi. 返回false。
d. 若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
e. 若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
f. 当x和y为引用同一对象时返回true。否则,返回false。
2. 若x为null且y为undefined, 返回true。
3. 若x为undefined且y为null, 返回true。
4. 若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
5. 若Type(x) 为 String 且 Type(y)为Number,返回比较ToNumber(x) == y的结果。
6. 若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
7. 若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
8. 若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
9. 若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
10. 返回false。
4.1.1 Сравнение равенства между строками и числами
var a = 42
var b = '42'
a == b //true
a==b — свободное равенство, т.е. если два значения имеют разные типы, приводить к одному или обоим из них. Как конвертировать конкретно? Это требует сопоставления с предыдущим «алгоритмом сравнения абстрактного равенства», чтобы найти подходящие правила преобразования.
Вернуть результат x == ToNumber(y) в соответствии с правилом 4.
4.1.2 Сравнение равенства между другими типами и булевыми типами
Одним из наиболее подверженных ошибкам мест для == является сравнение на равенство между true и false и другими типами.
var a = '42'
var b = true
a == b //false
Результат ложный, из-за чего легко попасть в яму. Если строго следовать «алгоритму сравнения абстрактного равенства», такой результат ожидается.
Согласно правилу 7, если Type(y) имеет значение Boolean, вернуть результат сравнения x == ToNumber(y), то есть вернуть '42' == 1, и результат будет ложным.
Странно, да? Поэтому ни при каких обстоятельствах не используйте == true и == false.
4.1.3 Сравнение равенства между null и undefined
В == null и undefined равны, а это означает, что в == null и undefined — одно и то же и могут быть неявно приведены друг к другу.
** Изучите «Алгоритм сравнения абстрактного равенства», читатели смогут понять, почему []==![] возвращает true.
4.2 Редкие случаи
if(a == 2 && a == 3){
//...
}
Вы можете подумать, что это невозможно, потому что а не равно 2 и 3 одновременно. Но если вы заставите a.valueOf() иметь побочные эффекты каждый раз, когда вы его вызываете, например, возвращает 2 в первый раз и 3 во второй раз, это произойдет.
var i = 2
Number.prototype.valueOf = function() {
return i++
}
var a = new Number(42)
if(a == 2 && a == 3){
console.log('Yeah, it happened!')
}
Есть еще одна яма, о которой часто упоминают:
0 == '\n' //true
Пустые строки, такие как "", "\n" (или другие комбинации пробелов, такие как " ") приводятся к 0 с помощью ToNumber.
4.3 Проверка целостности
Давайте посмотрим на эти «короткие» места:
"0" == false // true
false == 0 // true
false == "" // true
false == [] // true
"" == 0 // true
"" == [] // true
0 == [] // true
Есть 4 случая с участием == false, которых, как мы сказали, следует избегать ранее, поэтому остаются последние 3.
Эти особые случаи могут привести к различным проблемам, поэтому будьте осторожны при их использовании. Мы должны тщательно рассмотреть значения по обе стороны от ==.Следующие два принципа могут эффективно избежать ошибок.
- Никогда не используйте ==, если оба значения истинны или ложны
- Если в значениях с обеих сторон есть [], "", или 0, старайтесь не использовать ==
Неявное принуждение в некоторых случаях действительно опасно, используйте === на всякий случай.
5. Сравнение абстрактных отношений
Сравнение меньше чем (сравнение, где x
1. 如果 LeftFirst 标志是 true,那么
a. 让 px 为调用 ToPrimitive(x, hint Number) 的结果。
b. 让 py 为调用 ToPrimitive(y, hint Number) 的结果。
2. 否则解释执行的顺序需要反转,从而保证从左到右的执行顺序
a. 让 py 为调用 ToPrimitive(y, hint Number) 的结果。
b. 让 px 为调用 ToPrimitive(x, hint Number) 的结果。
3. 如果 Type(px) 和 Type(py) 得到的结果不都是 String 类型,那么
a. 让 nx 为调用 ToNumber(px) 的结果。因为 px 和 py 都已经是基本数据类型(primitive values 也作原始值),其执行顺序并不重要。
b. 让 ny 为调用 ToNumber(py) 的结果。
c. 如果 nx 是 NaN,返回 undefined
d. 如果 ny 是 NaN,返回 undefined
e. 如果 nx 和 ny 的数字值相同,返回 false
f. 如果 nx 是 +0 且 ny 是 -0,返回 flase
g. 如果 nx 是 -0 且 ny 是 +0,返回 false
h. 如果 nx 是 +∞,返回 fasle
i. 如果 ny 是 +∞,返回 true
j. 如果 ny 是 -∞,返回 flase
k. 如果 nx 是 -∞,返回 true
l. 如果 nx 数学上的值小于 ny 数学上的值(注意这些数学值都不能是无限的且不能都为 0),返回 ture。否则返回 false。
4. 否则,px 和 py 都是 Strings 类型
a. 如果 py 是 px 的一个前缀,返回 false。(当字符串 q 的值可以是字符串 p 和一个其他的字符串 r 拼接而成时,字符串 p 就是 q 的前缀。注意:任何字符串都是自己的前缀,因为 r 可能是空字符串。)
b. 如果 px 是 py 的前缀,返回 true。
c. 让 k 成为最小的非负整数,能使得在 px 字符串中位置 k 的字符与字符串 py 字符串中位置 k 的字符不相同。(这里必须有一个 k,使得互相都不是对方的前缀)
d. 让 m 成为字符串 px 中位置 k 的字符的编码单元值。
e. 让 n 成为字符串 py 中位置 k 的字符的编码单元值。
f.如果 n<m,返回 true。否则,返回 false。
Следующий пример немного странный:
var a = {b:42}
var b = {b:43}
a < b // false
a == b // false
a > b // false
a <= b // true
a >= b // true
Если a = b дают true?
Поскольку по норме a
Это может сильно отличаться от того, что мы себе представляли, что
** В спецификации указано, что NaN не больше и не меньше любого другого значения.