Глубокое понимание преобразования типов в JavaScript

JavaScript

предисловие

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

концепция

Преобразованием значения из одного типа в другой обычно называют преобразование типов, которое в основном происходит на этапе компиляции статических языков; приведение происходит на этапе выполнения динамических языков; JavaScript, как типичный динамический слаботипизированный язык, естественно, используется Casting (приведение типов). т.е. неявное приведение и явное приведение); приведения в js всегда возвращают скалярные значения базового типа, такие как строки, логические значения, числа, и не возвращают объекты и функции

var a = 42;
var b = a + '';//隐式强制类型转换
var c = String(a);//显式强制类型转化

Резюме

Прежде чем читать следующий контент, мы должны сначала понять следующие концепции, чтобы облегчить понимание последующего контента.

  • инкапсулированный объект: например: var a = new String('abc'), a называется инкапсулированным объектом, который инкапсулирует базовый тип, для восстановления значения инкапсулированного объекта можно вызвать метод valueOf;

Почти все методы базовых типов исходят не от самих себя, а от объекта-прототипа инкапсулированного объекта, как в следующем примере

const a = 1.2;
console.log(a.toFixed(0));//1

Для номеров базовых типов нет метода toFixed, но при обращении к этому методу js автоматически инкапсулирует базовый тип в соответствующий инкапсулированный объект, а затем обращается к соответствующему методу на прототипе инкапсулированного объекта, что эквивалентно следующему примеру

const a = 1.2;
console.log(new Number(a).__proto__.toFixed());//0
  • Абстрактная операция ToPrimitive: Эта операция в основном предназначена для преобразования типа объекта в базовый тип.Во-первых, проверьте, имеет ли объект атрибут valueOf, если да, верните значение valueOf объекта, в противном случае вызовите атрибут toString объекта и верните значение ( если valueOf возвращает not Для базовых типов вызывается метод toString, например, valueOf массива возвращает массив, и все ToPrimitives будут вызывать метод toString по умолчанию);

абстрактная манипуляция значениями

ToString отвечает за обработку приведения не строки к строке. Правила следующие:

  • 1.null преобразуется в 'null', undefined преобразуется в 'undefined', а другие базовые типы вызывают свойство объекта-оболочки toString() базового типа и возвращают значение.
const a = 123;
const _a = new Number(123);
console.log(String(a), _a.toString());//'123' '123'
const b = true;
const _b = new Boolean(true);
console.log(String(b), _b.toString());//'true' 'true'
  • 2. Строковое преобразование чисел следует общим правилам, но очень маленькие и очень большие числа используют экспоненциальную форму.
const a = 1.07*1000*1000*1000*1000*1000*1000*1000;
console.log(String(a));//'1.07e+21'
  • 3. Для обычных объектов, если они не определены сами по себе, toString() возвращает значение Object.prototype.toString(), а другие объекты имеют свои собственные методы toString() и вызывают свои собственные методы.
const b = {};
console.log(String(b));//[object object]
  • 4. Метод массива toString() по умолчанию был переопределен, и все единицы были преобразованы в строку, а затем связаны с ','
const a = [1, 2, 3];
console.log(String(a));//'1,2,3'
  • 5. Строковая обработка JSON

    • 5-1 Эффект строковой обработки JSON и toString в основном одинаков, за исключением того, что результатом сериализации всегда является строка.

    • 5-2.JSON игнорирует небезопасные значения (undefined, function(){}, symbol) и заполняет массив нулевым значением

    • 5-3 Циклическая ссылка на объект напрямую сообщает об ошибке, а регулярное выражение сериализуется как {}

ToNumber отвечает за обработку нечислового преобразования в числовое, правила следующие:

  • 1. true преобразуется в 1, false преобразуется в 0, undefined преобразуется в NaN, а null преобразуется в 0.
console.log(Number(null));//0
console.log(Number(undefined));//NaN
console.log(Number(true));//1
console.log(Number(false));//0
  • 2. Обработка строк следует соответствующим правилам/синтаксису числовых констант и возвращает NaN в случае сбоя обработки.
console.log(Number('123'));//123
console.log(Number('0b111'));//7
console.log(Number('0o123'));//83
console.log(Number('0x123'));//291
console.log(Number('123a'));//NaN
console.log(Number('a123'));//NaN
  • 3. Объекты (в том числе массивы) сначала будут преобразованы в соответствующие значения базового типа по абстрактной операции ToPrimitive, а затем обработаны по первым двум правилам, если у объекта нет ни метода valueOf, ни метода toString, возникает TypeError будет сгенерирован (например, Object.create(null) не имеет двух вышеуказанных методов)
const arr = [1, 2, 3];
console.log(Number(arr));//NaN
console.log(Number(arr.toString()));//NaN


const num = new Number(123);
console.log(Number(num));//123
console.log(Number(num.valueOf()));//123

const bool = new Boolean(true);
console.log(bool.valueOf());//true
console.log(Number(bool));//1
console.log(Number(bool.valueOf()));//1

const obj1 = {
  toString:()=>"21"
}

const obj2 = {
  valueOf:()=>"42",
  toString:()=>"21"
}

const obj3 = {
  a:1
}
console.log(Number(obj1));//21
console.log(Number(obj2));//42
console.log(obj3.toString());//[object Object]
console.log(Number(obj3));//NaN


const obj = Object.create(null);
console.log(Number(obj));//TypeError

Вышеупомянутые obj1 и obj2 вызывают методы toString и valueOf соответственно, а obj3 вызывает метод toString() для прототипа.

ToBoolean обрабатывает приведение небулевых значений к логическим по следующим правилам:

  • Значение, которое можно преобразовать в false (undefined, null, false, +0, -0 и NaN, '')

  • Все, кроме условия 1, конвертируются в true (помните: инкапсулированные объекты конвертируются в true)

явное приведение

Явное преобразование между строками и числами

  • Это реализовано встроенными функциями String() и Number() под окном (следуя приведенной выше операции с абстрактным значением)

  • Процесс явного преобразования TOSTRING () заключается в том, чтобы косвенно преобразовать основной тип в инкапсулированную объект, а затем вызовите метод ToString на объекте.

  • Унарные операторы + и - для преобразования, например, +a явно преобразует c в число

  • Явное преобразование побитового НЕ

    • Шаг 1. Битовые операции НЕ преобразуют нечисловые числа и числа в 32-битные числа.
    • Шаг 2: Отрицательное число и вычесть 1
    • IndexOf элегантно написан (феномен возврата -1 называется утечкой абстракции, то есть код раскрывает основные детали реализации)
    • Более надежно обрезать десятичную часть числа, чем Math.floor() (Math.floor перехватывает отрицательные числа, а ~ отличается)
const a = true;
console.log(~a === -Number(a)-1)//true

//indexOf优雅写法
const b = 'abc';
if(~b.indexOf('d')){
  console.log('存在d')
}else{
  console.log('不存在d')
}

//数字的小数部分截除
const d = 3.14;
const e = -3.14;
console.log(Math.floor(d), ~~d);//3 3
console.log(Math.floor(e), ~~e);//-4 -3

Явно разбирающий числовые строки

Результатами синтаксического анализа чисел в строках и принудительного преобразования строк в числа являются все числа, однако синтаксический анализ позволяет строкам содержать нечисловые значения, причем синтаксический анализ выполняется слева направо. Если встречается не число, синтаксический анализ останавливается; и Преобразование не допускает нечисловых символов, в противном случае оно завершается ошибкой и возвращает NaN.

  • parseInt() и parseFloat используются для анализа целых чисел и чисел с плавающей запятой соответственно. Входящее значение должно быть строкой. Если это не строка, она будет неявно преобразована в строку, а затем проанализирована.

Примечание. Прежде чем es5 parseInt() встретит строковый номер, начинающийся с 0 (например, time hour='09'), он будет обработан в соответствии с восьмеричным числом, и его необходимо решить, передав 10 во втором параметре, а энергию символ, начинающийся с 0 после es5 Если строка преобразована в восьмеричную, она может быть проанализирована в восьмеричном виде, но не может быть проанализирована в десятичном виде;

//现将a转为字符串'1,2,3'
const a = [1, 2, 3];
console.log(parseInt(a));//1
console.log(parseFloat(a));//1

//现将true转为字符串'true'
console.log(parseInt(true));//NaN
console.log(parseFloat(true));//NaN

//现将3.14转为字符串'3.14'
console.log(parseInt(3.14));//3
console.log(parseFloat(3.14));//3.14

console.log(String(new Date()));//'Wed Jun 12 2019 21:23:59 GMT+0800'
console.log(parseInt(new Date()));//NaN
console.log(parseFloat(new Date()));//NaN

//关于es6之前八进制写法的解析
console.log(parseInt(09));//9
console.log(parseFloat(09));//9
console.log(parseInt(010));//8
console.log(parseFloat(010));//8
  • Некоторые странности с parseInt()
parseInt(1/0, 19);//18
//其实相当于parseInt(Infinity, 19);其中Infinity的I在十九进制数中为18

parseInt(0.000008);//0
//字符串化为0.00008后进行解析

parseInt(0.0000008);//8
//字符化为8e-7后进行解析(详见抽象ToNumber)

parseInt(0x10);//16
String(0x10);//16
parseInt(0b10);//2
String(0b10);//2
parseInt(0o10);//8
String(0o10);//8
parseInt(012);//10
String(012);//10
//其实现在es6规定了二进制、八进制和十六进制的表示法
//以上三个字符串均先被String()化为字符串再进行解析

Явное преобразование в логическое значение

  • Приведение через глобальный метод Boolean(), следующий за ToBolean в операциях с абстрактными значениями

  • !! выполняет приведение, следуя за ToBolean в операциях с абстрактными значениями

Неявное принуждение

Неявное приведение к строке

  • Унарный оператор со знаком плюс (+) сначала преобразует неосновной тип в базовый тип с помощью абстрактной операции ToPrimitive.Если один из двух элементов со знаком плюс является строкой, а другой — операцией ToString, конкатенация строк выполняется. Чтобы добавить число к логическому значению, выполните операцию ToNumber для логического значения, а затем просуммируйте
const a = 1;
console.log(a + true);//2
//等同于
console.log(a + Number(true));//2

console.log([1, 2] + [1, 2]); //1,21,2
//等同于
console.log([1, 2].toString() + [1, 2].toString()); //1,21,2


console.log({} + []);//[object Object]
console.log([] + {});//[object Object]
console.log({}.toString());//[object Object]
console.log([].toString());//''
  • Неявно приводится к числу с помощью унарных операторов -, /, * в соответствии с правилами ToNumber для абстрактных значений.
console.log('3.14' - '0');//3.14
console.log([2] - [1]);//1
//等同于
console.log([2].toString() - [1].toString());//1

Неявное приведение к логическому значению

Следующее все следует за абстрактной операцией значения ToBolean

  • Условное выражение в операторе if(..)

  • Второе условное выражение оператора for(..;..;..)

  • Условное выражение для while(..) и do..while(..)

  • ?: Выражение условного суждения в

  • Операнды слева от логических операторов || и && (a||b эквивалентен a?a:b, а a&&b эквивалентен a?b:a)

принуждение символов

Символ не может быть приведен к числу (как явное, так и неявное приводит к ошибке), но может быть приведен к логическому значению (как явное, так и неявное приводит к истинному результату).

свободное равенство и строгое равенство

== допускает приведение в сравнениях на равенство, === нет, not == проверяет равенство значений, === проверяет равенство значений и типов

Два частных случая строгого равенства

  • NaN не равно NaN;

  • +0 равно -0;

Неявные преобразования между ослабленным равенством

1. Сравнение на равенство между строками и числами

  • (1) Если Type(x) — число, а Type(y) — строка, вернуть результат x == ToNumber(y).

  • (2) Если Type(x) — строка, а Type(y) — число, вернуть результат ToNumber(x) == y.

const [a, b] = ['42', 42];
console.log(a == b);//true
//等同于
console.log(Number(a) === b);//true

console.log(b == a);//true
//等同于
console.log(Number(a) === b);//true

2. Сравнение между другими типами и логическими типами

  • (1) Если Type(x) является логическим типом, вернуть результат ToNumber(x) == y.

  • (2) Если Type(y) является логическим, вернуть результат x == ToNumber(y).

const [a, b] = [true, 1];
console.log(a == b);//true
//等同于
console.log(Number(a) === b);//true

console.log(b == a);//true
//等同于
console.log(b === Number(a));//true

const [c, d] = [false, 0];
console.log(c == d);//true
//等同于
console.log(Number(c) === d);//true

console.log(d == c);//true
//等同于
console.log(d === Number(c));//true

console.log('true' == true);//false
//等同于
console.log('true' === 1);//false

3. Сравнение равенства между null и undefined, в спецификации указано, что null и undefined приблизительно равны.

console.log(null == undefined);//true

4. Сравнение на равенство между объектами и не-объектами (включая числа, строки; где логические значения следуют сравнениям между другими типами и логическими типами)

  • Если Type(x) — это строка или число, а Type(y) — это объект, вернуть результат x == ToPromitive(y);

  • Если Type(x) является объектом, а Type(y) является строкой или числом, вернуть результат ToPromitive(x) == y;

const [x, y] = [['42'], 42];
console.log(x == y);//true
//等同于
console.log(x.toString() == y);//true

const x1 = 'abc';
const y1 = new String(x1);
console.log(x1 == y1);//true
//等同于
console.log(x1 == y1.valueOf());//true

5. Некоторые частные случаи

const [x, y, z] = [undefined, null, NaN];
console.log(x == Object(x) );//false
console.log(y == Object(y) );//false
//等同于
console.log(x == Object() );//false
console.log(y == Object() );//false

console.log(z == Object(z) );//false
//等同于
console.log(z == new Number(z) );//false

//由于Objec(undefined)和Object(null)没有对应的封装对象,所以不能够被封装,
//Objec(undefined)和Object(null)均返回常规对象,等同于Object()
//Object(NaN)等同于new Number(NaN), NaN==NaN返回false

6. Сравнение ложных значений на равенство

null == '0';//false
null == false;//false
null == '';//false
null == 0;//false

undefined == '0';//false
undefined == false;//false
undefined == '';//false
undefined == 0;//false

null == undefined;//false
//null只会与undefined宽松相等


'0' == false;//true ---特殊
'0' == NaN;//false
'0' == 0;//true
'0' == '';//false

false == NaN;//false
false == 0;//true
false == '';//true ---特殊
false == [];//true ---特殊
false == {};//false

'' == NaN;//false
'' == 0;//true ---特殊
'' == [];//true
'' == {};//false

0 == NaN;//false
0 == [];//true ---特殊
0 == {};//false
0 == '\n';//true ---特殊

7. Сравнение абстрактных отношений >,

  • Если обе стороны являются строками, сравните их в алфавитном порядке.

  • Если обе стороны являются другими случаями, сначала вызовите преобразование TOPRIMITIVE в базовые типы.

  • Если результат преобразования не является строкой, он приводится к числу в соответствии с правилом ToNumber для сравнения

const a = [42];
const b = ['43'];

console.log(a < b);//true
//ToPrimite转换
console.log(a.toString() < b.toString());
//按照字母顺序判断
console.log('42' < '43');//true

const a1 = ['42'];
const a2 = ['043'];
console.log(a1 > a2);//true
  • Странные явления в объектно-реляционных сравнениях
var a = { b: 42 };
var b = { b: 43 };
a < b;  // false
a == b; // false
a > b;  // false
a <= b; // true
a >= b; // true

Разумно сказать, что объекты на обеих сторонах будут выполнять операцию абстрактного значения ToPrimitive, и преобразование в [объект-объект] должно быть одинаковым, но результат не один и тот же. Конкретный принцип см.Раздел 11.8 спецификации ECMAScript5

8. Принцип консолидации

  • Как сделать a==2&&a==3?
const a = new Number('something');
let i = 2;
Number.prototype.valueOf = ()=>i++;
console.log(a == 2 && a == 3);//true
  • Почему [] == ![] верно?

![] сначала преобразуется в false, [] == false равно указанному выше значению false

  • Почему '' == [null] верно?

[null] ToPrimitive приведено к ''

9. Безопасное использование неявных приведений

  • Никогда не используйте ==, если оба значения истинны или ложны

  • Если в значениях с обеих сторон есть [], '' или 0, старайтесь не использовать ==

  • Самый безопасный способ можно судить по typeof

Последняя практика, приветствуем всех, чтобы отметить нашуБлог фронтенд-команды Renrendai, все статьи также будут обновляться синхронно сЗнай колонкуа такжеСчет наггетс, мы еженедельно делимся несколькими высококачественными техническими статьями о внешнем интерфейсе. Если вам понравилась эта статья, я надеюсь, что вы можете поставить палец вверх.