js неявное преобразование, которое вы игнорируете

JavaScript

Вы когда-нибудь сталкивались с особенно странным вопросом о невидимой конверсии js на собеседовании, как это может быть первой реакцией? Трудно быть уверенным.Как js вычисляет результат?Вы глубоко поняли его принцип? Принцип его реализации будет подробно объяснен ниже.

На самом деле, первый черновик этой статьи был написан три месяца назад, когда я читал некоторые репозитории с исходным кодом, я столкнулся с этими базовыми знаниями, и мне захотелось заархивировать и систематизировать эту статью. Поскольку я был занят и у меня нет времени на организацию, я недавно увидел эту горячую тему и решил организовать эту статью.

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('hello world!');
}

В Интернете есть много хороших процессов синтаксического анализа.Прочитав следующий контент, вы получите более глубокое понимание процесса его выполнения.

1. тип данных js

В js есть 7 типов данных, которые можно разделить на две категории: примитивные типы и объектные типы:

Базовый тип (примитивное значение):

Undefined、 Null、 String、 Number、 Boolean、 Symbol (es6新出的,本文不讨论这种类型)

Сложные типы (значения объекта):

object

2. Три неявных типа преобразования

Одной из сложностей в js является невидимое преобразование js, потому что js меняет свой тип под некоторыми операторами, поэтому js гибкий, подверженный ошибкам и сложный для понимания.

Двумя операторами, использующими наиболее неявные преобразования, являются + и ==.

Оператор + может добавлять как числа, так и строки. Поэтому конвертировать проблематично. == отличается от ===, поэтому существуют и неявные преобразования. - * / Эти операторы предназначены только для числового типа, поэтому результат преобразования может быть преобразован только в числовой тип.

Так как мы хотим конвертировать неявно, то как конвертировать?Должен быть набор правил конверсии, чтобы отслеживать, какова конечная конверсия.

В неявных преобразованиях участвуют в основном три преобразования:

1. Преобразуйте значение в исходное значение ToPrimitive().

2. Преобразуйте значение в число ToNumber().

3. Преобразуйте значение в строку, ToString().

2.1 Преобразование значения в исходное значение через ToPrimitive

Абстрактная операция ToPrimitive внутри движка js имеет следующую сигнатуру:

ToPrimitive(input, PreferredType?)

input — это значение для преобразования, а PreferredType — необязательный параметр, который может иметь тип Number или String. Это просто флаг преобразования.Преобразованный результат не обязательно является типом значения этого параметра, но результат преобразования должен быть примитивным значением (иначе сообщается об ошибке).

2.1.1. Если PreferredType помечен как Number, для преобразования входного значения будет выполнен следующий поток операций.

1、如果输入的值已经是一个原始值,则直接返回它
2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
   如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
4、否则,抛出TypeError异常。

2.1.2 Если PreferredType помечен как String, для преобразования входного значения будет выполнена следующая операция.

1、如果输入的值已经是一个原始值,则直接返回它
2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
   如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
4、否则,抛出TypeError异常。

Поскольку PreferredType является необязательным параметром, как его преобразовать, если такого параметра нет? Значение PreferredType устанавливается автоматически в соответствии со следующими правилами:

1、该对象为Date类型,则PreferredType被设置为String
2、否则,PreferredType被设置为Number

2.1.3, анализ методов valueOf и toString

Метод valueOf и метод toString в основном упомянуты выше.Обязательно ли эти два метода существуют в объекте? Ответ положительный. Выведите Object.prototype в консоль, вы обнаружите, что есть методы valueOf и toString, а Object.prototype является прототипом верхнего уровня всех цепочек прототипов объектов, все объекты будут наследовать методы этого прототипа, поэтому любой объект будет иметь Методы valueOf и toString.

Сначала посмотрите на функцию valueOf объекта, каков результат преобразования? Для общих встроенных объектов js:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function.

1. Объектная форма базового значения, сгенерированная тремя конструкторами Number, Boolean и String, станет соответствующим исходным значением после преобразования с помощью valueOf. как:

var num = new Number('123');
num.valueOf(); // 123

var str = new String('12df');
str.valueOf(); // '12df'

var bool = new Boolean('fd');
bool.valueOf(); // true

2. Дата — это специальный объект, встроенная функция valueOf в его прототипе Date.prototype преобразует дату в числовое значение в виде миллисекунд даты.

var a = new Date();
a.valueOf(); // 1515143895500

3. Кроме того, возвращается все это, то есть сам объект: (Если у вас есть какие-либо вопросы, сообщите нам об этом)

var a = new Array();
a.valueOf() === a; // true

var b = new Object({});
b.valueOf() === b; // true

Давайте посмотрим на функцию toString, каков результат преобразования? Для общих встроенных объектов js:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function.

1. Объекты, сгенерированные такими конструкторами, как Number, Boolean, String, Array, Date, RegExp и Function, будут преобразованы в соответствующие строки после преобразования toString, поскольку эти конструкторы инкапсулируют свои собственные методы toString. как:

Number.prototype.hasOwnProperty('toString'); // true
Boolean.prototype.hasOwnProperty('toString'); // true
String.prototype.hasOwnProperty('toString'); // true
Array.prototype.hasOwnProperty('toString'); // true
Date.prototype.hasOwnProperty('toString'); // true
RegExp.prototype.hasOwnProperty('toString'); // true
Function.prototype.hasOwnProperty('toString'); // true

var num = new Number('123sd');
num.toString(); // 'NaN'

var str = new String('12df');
str.toString(); // '12df'

var bool = new Boolean('fd');
bool.toString(); // 'true'

var arr = new Array(1,2);
arr.toString(); // '1,2'

var d = new Date();
d.toString(); // "Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)"

var func = function () {}
func.toString(); // "function () {}"

За исключением этих объектов и созданных ими объектов, другие объекты возвращают тип объекта (пожалуйста, сообщите нам, если у вас есть какие-либо вопросы), которые являются унаследованными методами Object.prototype.toString.

var obj = new Object({});
obj.toString(); // "[object Object]"

Math.toString(); // "[object Math]"

Из преобразования приведенных выше функций valueOf и toString в объекты мы можем понять, почему для ToPrimitive(input, PreferredType?), когда PreferredType не установлен, за исключением типа Date, PreferredType устанавливается в String, а другие — в Number.

Поскольку функция valueOf преобразует значение типа объекта базовых типов Number, String и Boolean в базовый тип, тип Date преобразует количество миллисекунд, а остальные возвращают сам объект, а метод toString преобразует все объекты в строки. . Очевидно, что для большинства преобразований объектов преобразование valueOf является более разумным, потому что здесь нет определенного типа преобразования, и исходное значение должно быть максимально сохранено, а не преобразовано в строку, как метод toString.

Поэтому, если тип PreferredType не указан, лучше сначала преобразовать метод valueOf, поэтому установите для PreferredType тип Number.

Для типа Date он преобразует valueOf в числовой тип миллисекунд. При выполнении неявного преобразования, очевидно, не имеет особого смысла преобразовывать его в значение типа number, если оно не указано для преобразования в число типа. (Будь то оператор + или оператор ==), он не так хорош, как преобразование даты в строковый формат, поэтому тип Date по умолчанию будет иметь приоритет для преобразования toString. Отсюда вышеизложенные правила:

Если параметр PreferredType не установлен, для объектов типа Date по умолчанию для PreferredType устанавливается значение String, а для PreferredType других типов объектов по умолчанию устанавливается значение Number.

2.2 Преобразование значения в число через ToNumber

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

параметр результат
undefined NaN
null +0
Логическое значение true преобразует в 1, false преобразует в +0
номер Преобразование не требуется
нить Есть строки, проанализированные как числа, например: «324» преобразуется в 324, а «qwer» преобразуется в NaN.
объект (объект) Сначала преобразуйте ToPrimitive(obj, Number), чтобы получить исходное значение, а затем преобразуйте ToNumber в число.

2.3 Преобразование значения в строку через ToString

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

параметр результат
undefined 'undefined'
null 'null'
Логическое значение Преобразование в «истина» или «ложь»
номер Строка преобразования чисел, например: 1,765 в «1,765»
нить Преобразование не требуется
объект (объект) Сначала преобразуйте ToPrimitive(obj, String), чтобы получить исходное значение, а затем преобразуйте ToString в строку.

После стольких разговоров, неужели все еще не очень понятно, давайте рассмотрим пример:

({} + {}) = ?
两个对象的值进行+运算符,肯定要先进行隐式转换为原始类型才能进行计算。
1、进行ToPrimitive转换,由于没有指定PreferredType类型,{}会使默认值为Number,进行ToPrimitive(input, Number)运算。
2、所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
3、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
故得到最终的结果,"[object Object]" + "[object Object]" = "[object Object][object Object]"

Другой пример указания типов:

2 * {} = ?
1、首先*运算符只能对number类型进行运算,故第一步就是对{}进行ToNumber类型转换。
2、由于{}是对象类型,故先进行原始类型转换,ToPrimitive(input, Number)运算。
3、所以会执行valueOf方法,({}).valueOf(),返回的还是{}对象,不是原始值。
4、继续执行toString方法,({}).toString(),返回"[object Object]",是原始值。
5、转换为原始值后再进行ToNumber运算,"[object Object]"就转换为NaN。
故最终的结果为 2 * NaN = NaN

3, == оператор неявного преобразования

Регулярность правил оператора == не так сильна, и он выполняется в соответствии со следующим процессом, документация es5

比较运算 x==y, 其中 x 和 y 是值,返回 true 或者 false。这样的比较按如下方式进行:

1、若 Type(x) 与 Type(y) 相同, 则

    1* 若 Type(x) 为 Undefined, 返回 true。
    2* 若 Type(x) 为 Null, 返回 true。
    3* 若 Type(x) 为 Number, 则
  
        (1)、若 x 为 NaN, 返回 false。
        (2)、若 y 为 NaN, 返回 false。
        (3)、若 x 与 y 为相等数值, 返回 true。
        (4)、若 x 为 +0 且 y 为 −0, 返回 true。
        (5)、若 x 为 −0 且 y 为 +0, 返回 true。
        (6)、返回 false。
        
    4* 若 Type(x) 为 String, 则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。 否则, 返回 false。
    5* 若 Type(x) 为 Boolean, 当 x 和 y 为同为 true 或者同为 false 时返回 true。 否则, 返回 false。
    6*  当 x 和 y 为引用同一对象时返回 true。否则,返回 false。
  
2、若 x 为 null 且 y 为 undefined, 返回 true。
3、若 x 为 undefined 且 y 为 null, 返回 true。
4、若 Type(x) 为 Number 且 Type(y) 为 String,返回比较 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。

Вышеупомянутое в основном делится на две категории, когда типы x и y одинаковы, и когда типы разные.

При одинаковых типах преобразования типов нет, главное учитывать, что NaN не равен никакому значению, в том числе и самому себе, т.е. NaN !== NaN.

Когда типы не совпадают,

1. x и y либо нулевые, либо неопределенные // возвращают true

2. Когда x и y имеют числовой и строковый типы, они преобразуются в числовой тип для сравнения.

3. При наличии логического типа логическое значение преобразуется в числовой тип для сравнения.

4. Тип объекта, тип строки или число, после исходного преобразования типа объекта исходное значение сравнивается в соответствии с описанным выше процессом.

3.1, == Пример анализа

Поэтому, когда типы не совпадают, можно выполнить вышеуказанные сравнения, например:

var a = {
  valueOf: function () {
     return 1;
  },
  toString: function () {
     return '123'
  }
}
true == a // true;
首先,x与y类型不同,x为boolean类型,则进行ToNumber转换为1,为number类型。
接着,x为number,y为object类型,对y进行原始转换,ToPrimitive(a, ?),没有指定转换类型,默认number类型。
而后,ToPrimitive(a, Number)首先调用valueOf方法,返回1,得到原始类型1。
最后 1 == 1, 返回true。

Давайте посмотрим на очень сложное сравнение, а именно:

[] == !{}
//
1、! 运算符优先级高于==,故先进行!运算。
2、!{}运算结果为false,结果变成 [] == false比较。
3、根据上面第7条,等式右边y = ToNumber(false) = 0。结果变成 [] == 0。
4、按照上面第9条,比较变成ToPrimitive([]) == 0。
    按照上面规则进行原始值转换,[]会先调用valueOf函数,返回this。
   不是原始值,继续调用toString方法,x = [].toString() = ''。
   故结果为 '' == 0比较。
5、根据上面第5条,等式左边x = ToNumber('') = 0。
   所以结果变为: 0 == 0,返回true,比较结束。

Наконец, давайте посмотрим на тему, упомянутую в начале статьи:

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('hello world!');
}

1. Когда выполняется a == 1 && a == 2 && a == 3, он будет пошагово анализироваться слева направо, сначала a == 1, будет выполнено преобразование на шаге 9 выше. ToPrimitive(a, Число) == 1.

2. ToPrimitive(a, Number), в соответствии с приведенными выше правилами преобразования примитивных типов, сначала будет вызываться метод valueOf, а метод valueOf объекта a наследуется от Object.prototype. Возвращает сам, а не исходный тип, поэтому вызывается метод toString.

3. Поскольку toString переписан, будет вызван переписанный метод toString, поэтому он возвращает 1. Обратите внимание, что это i++, а не ++i, он сначала вернет i, а затем добавит i+1. Итак, ToPrimitive(a, Number) = 1. То есть 1 == 1, а i = 1 + 1 = 2 в данный момент.

4. После выполнения a == 1 и возврата true будет выполнено a == 2. Аналогично будет вызван ToPrimitive(a, Number).Как и выше, сначала будет вызван метод valueOf, а затем метод toString. называется. Из-за первого шага i = 2. Когда , ToPrimitive(a, Number) = 2, то есть 2 == 2, в этот момент i = 2 + 1.

5. Как и выше, можно сделать вывод, что a == 3 также возвращает true. Таким образом, окончательный результат a == 1 && a == 2 && a == 3 возвращает true

На самом деле, после понимания принципов вышеупомянутых невидимых преобразований вы обнаружили, что эти неявные преобразования не так сложны, как вы думаете?

Справочная статья:документация es5