Принуждение в JavaScript

внешний интерфейс алгоритм Безопасность JavaScript

Обобщите и проанализируйте обязательное преобразование типов в 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 не больше и не меньше любого другого значения.