乐趣区

重学前端学习笔记六JavaScript类型有哪些你不知道的细节

笔记说明

重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天 10 分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的可以加入 winter 的专栏学习【原文有 winter 的语音】,如有侵权请联系我,邮箱:kaimo313@foxmail.com。

JavaScript 类型有哪些你不知道的细节?

winter 提了几个问题测试:能回答对几个?

  • 1、为什么有的编程规范要求用 void 0 代替 undefined?
  • 2、字符串有最大长度吗?
  • 3、0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
  • 4、ES6 新加入的 Symbol 是个什么东西?
  • 5、为什么给对象添加的方法能用在基本类型上?

如果有点犹豫,不妨看看下面的介绍,或者找找资料温习一下。

类型

  1. Undefined
  2. Null
  3. Boolean
  4. String
  5. Number
  6. Symbol
  7. Object

1、Undefined、Null

Undefined:

  • Undefined 类型表示未定义,它的类型只有一个值,就是 undefined
  • 任何变量在赋值前是 Undefined 类型、值为 undefined
  • JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一
  • 为了避免无意中被篡改,可以使用 void 0 来获取 undefined 值。

Null:

  • Null 类型也只有一个值,就是 null,它的语义表示空值
  • 与 undefined 不同,null 是 JavaScript 关键字
  • 在任何代码中,都可以用 null 关键字来获取 null 值

2、String

  • String 用于表示文本数据
  • String 有最大长度是 2^53 – 1
  • 字符串的最大长度,实际上是受字符串的编码长度影响的。

Note: 现行的字符集国际标准,字符是以 Unicode 的方式表示的,每一个 Unicode 的码点表示一个字符,理论上,Unicode 的范围是无限的。UTF 是 Unicode 的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8。Unicode 的码点通常用 U+??? 来表示,其中 ??? 是十六进制的码点值。0-65536(U+0000 – U+FFFF)的码点被称为基本字符区域(BMP)。

3、Number

  • JavaScript 中的 Number 类型有 18437736874454810627(即 2^64-2^53+3) 个值
  • NaN,占用了 9007199254740990,这原本是符合 IEEE 规则的数字
  • Infinity,无穷大
  • -Infinity,负无穷大
  • 根据双精度浮点数的定义,Number 类型中有效的整数范围是 -0x1fffffffffffff 至 0x1fffffffffffff,所以 Number 无法精确表示此范围外的整数
  • 根据浮点数的定义,非整数的 Number 类型无法用 ==(=== 也不行)来比较

关于 javaScript 中 0.1 + 0.2 == 0.3 ? 这个问题的解释:

console.log(0.1 + 0.2 == 0.3);
>> false

输出结果为false,说明两边不相等,这是浮点运算特点导致的,实际上,这里错误的不是结论,而是比较的方法,正确的比较方法是使用 javaScript 提供的最小精度值:

我们可以查找 MDN 文档的 Number 可以找到属性EPSILON

Number.EPSILON:两个可表示数字之间的最小间隔

console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
>> true

这样的比较输出结果为 true 检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法

4、Symbol

关于 Symbol 的介绍,我准备用 ES6 文档 - 阮一峰来做一些介绍,具体的可以参考文档

4.1、ES6 引入 Symbol 的原因

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。从根本上防止属性名的冲突。

4.2、介绍

凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

4.2.1、Symbol 值通过 Symbol 函数生成,先来一段代码:

let s = Symbol();
typeof s
>> "symbol"

上面代码中,变量 s 就是一个独一无二的值。sSymbol 数据类型。

4.2.2、Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1
>> Symbol(foo)
s2
>> Symbol(bar)

s1.toString()
>> "Symbol(foo)"

s2.toString()
>> "Symbol(bar)"

上面代码中,s1s2 是两个 Symbol 值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

4.2.3、如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。

const obj = {
    a: '123123',
    toString() {return 'iuoisigud';}
};
const sym = Symbol(obj);

sym // Symbol(iuoisigud)

4.2.4、Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的 Symbol 函数的返回值是不相等的。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

4.2.5、Symbol 值不能与其他类型的值进行运算,会报错。

let sym = Symbol('My symbol');

"your symbol is" + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string

4.2.6、Symbol 值可以显式转为字符串,也可以转为布尔值,但是不能转为数值。

let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

let sym = Symbol();
Boolean(sym) // true
!sym  // false

Number(sym) // TypeError
sym + 2 // TypeError

4.2.7、其他的一些属性可以去看 ES6 文档 - 阮一峰

4.3、注意

Symbol函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于 字符串的数据类型

5、Object

Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。

为什么给对象添加的方法能用在基本类型上?

回答:“运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。”

比如原型上添加方法,也可以应用于基本类型:

Symbol.prototype.hello = () => console.log("hello");

var a = Symbol("a");
console.log(typeof a); //symbol,a 并非对象
a.hello(); //hello,有效

6、类型转换

6.1、臭名昭著的“==”运算

  • 因为 JS 是弱类型语言,所以类型转换发生非常频繁
  • “==”试图实现跨类型的比较,它的规则复杂到几乎没人可以记住。

6.2、转换规则

6.3、StringToNumber

字符串到数字的类型转换,存在一个语法结构,类型转换支持十进制、二进制、八进制和十六进制

比如:

Number('0xFF')
>> 255

6.4、装箱转换

装箱(boxing):值类型实例到对象的转换,它暗示在运行时实例将携带完整的类型信息,并在堆中分配。

每一种基本类型 NumberStringBooleanSymbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

例子:利用一个函数的 call 方法来强迫产生 Symbol 装箱

var symbolObject = (function() {return this;}).call(Symbol("a"));

console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true

例子:使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。

var symbolObject = Object(Symbol("a"));

console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true

每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取:

var symbolObject = Object(Symbol("a"));

console.log(Object.prototype.toString.call(symbolObject));
>> [object Symbol]

6.5、拆箱转换

拆箱(unboxing):是将引用类型转换为值类型

6.5.1、在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换

toPrimitive(input, preferedType)

input 是输入的值,preferedType 是期望转换的类型,它可以是字符串,也可以是数字。

inputType Result
Undefined input argument
Null input argument
Boolean input argument
Number input argument
String input argument
Object 忽略 第二个参数 hint PreferredType 直接调用内置方法 [[DefaultValue]]

6.5.2、如果转换的类型是 number,会执行以下步骤:参考博客

  1. 如果 input 是原始值,直接返回这个值;
  2. 否则,如果 input 是对象,调用input.valueOf(),如果结果是原始值,返回结果;
  3. 否则,调用input.toString()。如果结果是原始值,返回结果;
  4. 否则,抛出错误。

6.5.3、如果转换的类型是 String,2 和 3 会交换执行,即先执行 toString() 方法。

例子 1:先将两个操作数转换为 string,然后进行拼接

[] + []
>> ""[] ----->''
[] -----> ''[] + [] =''

例子 2:先将两个操作数转换为 string,然后进行拼接

[] + {}
>> "[object Object]"

// 解释
[] -----> ''{} ----->'[object Object]'[] + {} ='[object Object]'

例子 3:js 解释器会将开头的 {} 看作一个代码块,而不是一个 js 对象

{} + []
>> 0

// 真正参与运算的是 + []
// {} + [] 等价于 + []

7、规范类型

  • List 和 Record:用于描述函数传参过程。
  • Set:主要用于解释字符集等。
  • Completion Record:用于描述异常、跳出等语句执行过程。
  • Reference:用于描述对象属性访问、delete 等。
  • Property Descriptor:用于描述对象的属性。
  • Lexical Environment 和 Environment Record:用于描述变量和作用域。
  • Data Block:用于描述二进制数据。

8、补充阅读

typeof 的运算结果,与运行时类型的规定有很多不一致的地方(typeof 的设计是有缺陷的)

个人总结

在整理知识点的时候,我就发现,我可能是真的划水酱_(:3」∠)_,里面大部分的东西很模糊,有点都不清不楚的,还有的没有听过,看来要好好打打基础了,现在前端的发展过于太快了,而自己的基础又不牢固,能跟着 winter 学习是我的幸运,不过在这里要感谢一个大佬的推荐,stormzhang,公众号也是这个,我的学习榜样来的,哈哈哈哈哈。

退出移动版