重学前端01

类型转换

Posted by EWL on January 31, 2019

JavaScript的类型

  • Null
  • Undefined
  • Object(根据红宝书的说法,还包含了Array/Function/RegExp/Date)
  • String
  • Boolean
  • Number
  • Symbol

Undefined与Null

说到Undefined与Null,就要提到关键字[1]这个概念。

保留字(英文:Reserved word),有时也叫关键字(keyword),是编程语言中的一类语法结构。 保留字(关键字)本身是不能用作变量、函数名、过程和对象名的。

Null就是JavaScript的关键字,且Null这一数据类型仅有一个值,即Null,表示空值。我们可以借助Null关键字获得Null空值从而赋值给我们的变量,让该变量的值为Null,类型为Object[2]。(typeof Null返回Object并非意指Null是一个对象,它是一个primitive value,即原值,历史遗留问题不做赘述) Undefined却并非关键字,这就说明我们可以为Undefined赋值,但是这是JavaScript语言设计的一个失误。每当一个变量被声明但未被赋值时,该变量的值就是Undefined,类型也是Undefined,并且Undefined这一数据类型也只有一个值,为Undefined。

具体到代码层面表示为:

var foo;
typeof foo;       // "undefined"
console.log(foo); //  undefined
var boo = null;
typeof boo;       // "object"
console.log(boo); //  null

Boolean

布尔类型仅有两个值,true以及false,作为真假判断的依据,在此不做赘述。

String

string是字符串类型,用于表示文本数据,一旦创建就不能再修改内容,属于值类型。

The length property of a String object indicates the length of a string, in UTF-16 code units. String对象的length属性以UTF-16[3]代码单位表示字符串的长度。

UTF-16是一种变长的2或4字节编码模式。对于BMP内的字符使用2字节编码,其它的则使用4字节组成所谓的代理对来编码。

上述这段话来自MDN,但是这段话成立的前提是我们使用单个的16位字符单元来表示大部分常用文本。假设我们使

用了两位16位字符单元来表示生僻字(以汉字为例,可称之为超出BMP-basic multilingual plane的字符)时,length属性就可能会出现偏差。ECMAScript2016第7版本里建立了字符串的最大长度为2^53 - 1,在此版本之前没有明确过最大长度。

–补充–

Number

这里主要积累了几点JavaScript中较为特殊的几点:

  • NaN
  • -Infinity
  • Inifinity
(1) NaN解惑

NaN的有一些让人印象深刻的特点,NaN属于Number类型,并且NaN与任何数字不相等,两个NaN之间也不相等,那么什么时候会出现NaN呢?英文翻译中NaN指Not a Number,这并非意指NaN不是个数字,而是用来表示在数字类型的限制内无法表示的数字。所以按这种说法来讲,在数字类型限制之外的数字是非常多的,NaN数字的值是不能保证相等的,这为NaN === NaN返回false提供了依据。

此外需要提及的是,JavaScript中的Number类型基本IEEE754-2008中的双精度浮点数规则,范围在-0x1fffffffffffff到0x1fffffffffffff,在此范围之外的Number类型就无法再进行表示了,即NaN。(By the way负数开根号所得到的虚数也是无法正常显示的,也是NaN)

(2) 小数的对比准确性

入门前端的时候就时常在一些博客中看到0.1 + 0.2 === 0.3返回false的示例,但仅仅止步于知其然而不知其所以然的水平。

首先,我们看一下计算机是如何表示浮点数的:

value = sign * exponent * fraction

value部分表示数值,符号(sign部分,表示符号0正,1负数),指数(exponent部分,表示指数位),和尾数(fraction部分,表示有效数字,大于等于1,小于2)。对于JavaScript来说,双精度浮点数的sign占1位,exponent部分占11位,fraction部分占52位。

–补充–

回到我们之前提出的 0.1 + 0.2 与 0.3 的准确性问题,难道在JavaScript中我们就没有方法去准确比较浮点数了吗?答案当然是否定的,JavaScript为我们提供了一个最小精度值Math.EPSILON该,该值表示JavaScript中的最小精度,对比方法如下:

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

###### (3)正负零的区分判断

在JavaScript中有+0以及-0之分,这在加法类运算中并没有过多分别,但是在除法类运算中需要格外留意,当分母为+0出现错误时,返回的是+Infinity,分母为-0出现错误时,返回的是-Infinity,因此也可以通过判断Infinity的正负来判断是+0或者-0。

```JavaScript
var y1 = -0;
var y2 = 0;
var x1 = 1 / y1;
var x2 = 1 / y2;

console.log(x1);  //  Infinity
console.log(x2);  // -Infinity

Symbol

数据类型 “symbol” 是ES6新提出的一种原始数据类型,该类型的性质在于这个类型的值可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值——当你想让它是私有的时候。

类型特性:

(1) 可以将一个Symbol类型实例赋给一个左值变量,例如var foo = Symbol(); (2) 可以通过标识符检查类型

以上就是Symbol类型的全部特性,目的明确功能单一。

Symbol类型的小特点:

(1) Symbol类型不像其他Number类型可以使用其他操作符进行比较或者组合,对Symbol类型操作会报错。

var symb = Symbol('aSymbol');
typeof symb;   // "symbol"

var symb2 = Symbol('anotherSymbol');
symb2 + symb;  // Uncaught TypeError: Cannot convert a Symbol value to a number at <anonymous>:2:7

由上述代码可以看出,该表达式甚至还没有执行到symb部分就已经报错,足可以说明使用这样的组合操作对于Symbol来说是完全行不通的。

(2) 即使两个Symbol实例拥有相同的字符串描述,但它们之间仍不相等,并且该字符串描述并不是必须的,添加描述的目的仅供调试。

var a = Symbol('aaa'); 
var b = Symbol('aaa');

a === b; // false
a == b;  // false

(3) 从上述所有的代码中都能看出,创建一个Symbol类型的实例,并不像是其他类型一样通过new一个构造函数,而是直接调用一个Symbol函数创建。

一个具有数据类型“symbol”的值可以被称为“符号类型值”。在JavaScript运行时环境中,一个符号类型值可以通过调用函数Symbol()创建,这个函数动态地生成了一个匿名,唯一的值。

此外,在一些标准中我们其实已经已经接触过了Symbol,例如迭代器Symbol.iterator[4],Symbol.search[5] 我们可以使用Symbol.iterator为对象添加遍历接口,使用Symbol.search为字符串提供搜索方法。

–补充详细的遍历器和搜索器的示例代码–

Object

JavaScript的类仅仅是运行时对象(v8引擎?)的一个私有属性,在JavaScript中不能自定义类型的,这与C++和Java是有区别的。

JavaScript中的基本类型,在Object中都有着对应的类型,例如:

  • Number
  • String
  • Boolean
  • Symbol

代码示例:

var normalNum = 3;
var objNum = new Number(3);
typeof normalNum;  // "number"
typeof objNum;     // "object"

var normalString = '3';
var objString = new String('3');
typeof normalString; // "string"
typeof objString;    // "object"

var normalBool = false;
var objBool = new Boolean(false);
typeof normalBool;  // "boolean"
typeof objBool;     // "object"

var normalSymb = Symbol();
typeof normalSymb;  // "symbol"

要提及的一点是,Symbol类型与其他类型不同,并不能使用new来创建对象实例,Symbol()函数方法会直接构造一个Symbol实例,Symbol对象也仅仅是Symbol原始值的封装。那如果我们非得想做出一个Symbol对象怎么办呢?可以通过装箱的方式去包裹出一个Symbol对象来,后面会具体贴出代码。

–这里对于Symbol对象的使用仍旧存疑–

从以上代码中,我们可以尝试着去挖掘为什么基本类型可以去调用对象类型的方法。当我们在为基本类型调用对象类型的方法时.运算符为我们提供了一个临时对象,使得我们在基本类型上可以去调用对象类型的方法。

类型转换

对于类型转换中这一部分我不会总结太多细节部分,仅将平时开发需要注意的一些点罗列。

首先,日常开发的时候尽量多使用显式转换以及===来完成相等判断。(这一点并非是因为不熟悉隐式转换的坑而逃避使用==,而是大部分人实际上都不能熟练记住隐式转换的细节,这么做无非是保证代码的可读性以及健壮性,但如果是个人项目,单纯为了炫技,这就无可非议了) 在===以及==之外还存在一些会导致隐式转换的操作符,例如> < >= <= + - * /等等。但实际上这些隐式转换的规则不算复杂,就算记不住,也可以查表,具体内容详见链接[6]

文章总结

本节的学习内容主要是将重学前端专栏里JavaScript中的类型部分拿出来重点记录一下,其中当属Symbol以及类型转换,数字以及字符长等内容最为重要,在知其然的前提下去挖掘其所以然,这对于我后续的深入学习显得尤为重要。

[1] JavaScript全部保留字

[2] typeof Null为什么会返回object

[3] 关于编码的一些内容

[4] 关于Symbol.search

[5] 关于Symbol.iterator

[6] 关于类型转换