..

1. 原始值

JS 有七种原始值:

  • string
  • number
  • bigint
  • boolean
  • null
  • undefined
  • symbol

其中 bigint 不是那么的"原始"。它更像是一个对象值。

string

JS 的 string 采用 UTF-16 编码。与其他动态语言类似,其语法相对比较松散。很多其它类型的值在一些场合都需要被隐式的转成字符串。比如数组的数字下标其实也是 string。

基本语法

字符串可以是单引号对, 也可以是双引号对。一些语言,单引号对一般是用来表示单个字符的。如果经常跨语言写代码,建议用下面的双引号对。

const singleQuoteStr = 'hello';
console.log(singleQuoteStr);

const quoteStr = "hello";
console.log(quoteStr);

字符串模板是反引号对,用 ${} 嵌入表达式,但是一般只写很简单的表达式,如变量。其它类型的值会自动转成 string。

// pg-title: String Template
const name = 'world';
const age = 17;
const strTemplate = `Hi, my name is ${name}, my age is ${age}`;
console.log(strTemplate);

字符串模板语法出现之前,拼接字符串基本会使用操作符 +,现在就用得比较少了。

常用 API

下面是一些 string 常用的函数。

length 属性

字符串的 length 属性并不是我们所见的字符个数,它是字符串的 UTF-16 编码长度。像表情字符经常是需要两个 UTF-16 编码来表示。所以下面的字符串的 length 是 9 而不是字符个数 5

// pg-title: string.length example
console.log("😊☀️☁️🌧雪".length);

trim

trim 系列方法处理字符串两边的空白字符。

// pg-title: string.trim example
console.log("  \n\t1 2\n".trim());
console.log("  \n\t1 2\n".trimStart());
console.log("  \n\t1 2\n".trimEnd());

split

split 方法来分割字符串。当然了,这也是以 UTF-16 编码为基本单元进行分割。

// pg-title: string.split example

// split 用指定分隔符,分割字符串
const subStrList1 = "name|age|".split("|");
console.log(JSON.stringify(subStrList1));

// 可以用空字符串直接分割每个字符
const subStrList2 = "abc".split('');
console.log(JSON.stringify(subStrList2));

// 分隔符也可以是多个字符
const subStrList3 = "hello__world".split("__");
console.log(JSON.stringify(subStrList3));

// 奇怪的切割结果~ 这还是因为 JS 字符串是 UTF-16 编码
// 所以其 API 大多基于这个为前提的结果。
const subStrList4 = "😊☀️☁️🌧雪".split('');
console.log(JSON.stringify(subStrList4));

字符串匹配

// pg-title: string match

// 判断前缀、后缀、以及正则表达式匹配
console.log('lang-'.startsWith('la'));
console.log('lang-'.endsWith('la'));
const matchRslt = 'lang-javascript'.match(/^lang-(\w+)$/);
console.log(JSON.stringify(matchRslt));

number

JS 的 number 实际是一个64位的双精度浮点数。大多数场景,我们都是用 number 来表示整数的。但因其实际是一个双精度浮点数,所以一些场景下会出现意料之外的"问题"。 如果你遇到诡异的情况,多往这个方向想想。

基本语法

// pg-title: 基本表示方式
// 除了十进制面量,number 也支持其它进制的面量。下面三个数字都是数字16。
const num1 = 16;
console.log(num1)
const num2 = 0b10000;  // 二进制
console.log(num2)
const num3 = 0o20;     // 八进制
console.log(num3)
const num4 = 0x10;     // 十六进制
console.log(num4)

整数

JS 严格意义上的整数应该是 bigint。但是大部分谈到 JS 整数的时候,其实都是指小数部分为零的 number (双精度浮点数)。所以重复一遍定义:

JS 的整数指的是小数部分为零的 number

// pg-title: JS 的整数

// 可以看到,只要小数部分是零的 number,都会"被当做"整数。
const n1 = 1.0;
console.log(n1.toString() === '1');
console.log(1.0 === 1);
console.log(0.1e1 === 1);  // 0.1e1这种形式是number的科学计数法表示方式。


console.log(Number.parseFloat('1.0') === 1);

// 下面是一些相对常用的获取整数的方法
console.log(Number.parseInt('111', 2));  // 通过字符串转换,可以指定进制。
console.log(Math.ceil(3.3));
console.log(Math.floor(3.3));
console.log(Math.round(3.3));

NaN

NaN 是一个特殊的 number,其含义是 “Not a Number”。其出现的原因是 JS 的值在运算过程中会发生隐式转换,经常发生在其它类型的值跟 number 进行运算的时候。

console.log('a' / 8);
console.log('a' * 8);
console.log('a' + 8);
console.log(Number.parseFloat('abc'));

NaN 不等于它自身。这个行为看起来很怪,不过这也不是 JS 的发明。所幸,我们有办法判断它。

console.log(NaN !== NaN);
console.log(Number.isNaN(NaN));

0.1 + 0.2 !== 0.3

这个问题同样不是 JS 独有的,这是所有双精度浮点数都会遇到的问题。其问题的本质是浮点数是通过二进制"模拟"出来的。二者进行转换的时候会有精度丢失。

0.1 + 0.2 的运算过程大概如下:

  1. 把 0.1 与 0.2 分别转为双精度浮点数的二进制。
  2. 进行运算。具体细节未进行深究,估计可以从这里查到。反正得到的结果也是二进制。
  3. 再把双精度浮点数二进制转成 number。
// pg-title: 精度丢失
console.log(0.1 + 0.2 !== 0.3)
console.log(0.1 + 0.2)

为什么二进制与整数互相转换不会发生精度丢失?因为限制范围内的整数是可以穷举的,而小数本质上是无限的。以有限去表(模)示(拟)无限,势必会发生精度丢失。

其它常用 API

// pg-title: 其它 API
// Number.MAX_SAFE_INTEGER 最大安全整数。
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MAX_SAFE_INTEGER + 1);  // 但是很奇怪,+1 却不会溢出。
console.log(Number.MAX_SAFE_INTEGER + 2);  // +2 开始溢出了
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);

// 无限 Infinity
console.log(Infinity > Number.MAX_VALUE);
console.log(Infinity > Infinity);  // false
console.log(Infinity === Infinity); // true
console.log(Number.isFinite(Infinity)); // 无论是 -Infinity 还是 Infinity

// 转换成其它进制的字符串
console.log(0.1.toString(2));  // 把 0.1 转成二进制表示
console.log((100).toString(16));  // 把 100 转成十六进制表示
console.log((999).toString(36));  // 最高支持到三十六进制

// 保留 x 位小数
console.log(3.1415.toFixed(2)); // 但是会被转成 string 

bigint

bigint 还是比较新的特性。它其实很难直接代替 number 里的整数。仅在后者会溢出的时候,替代。

其面量表示方式是在整数后面加一个字母 n,如: 3n

boolean

JS 的布尔值是 truefalse。可以通过逻辑运算符把其它类型的值显示转换为布尔值,在条件语句里的表达式会被隐式转换为布尔值。

列举一些隐式转换

// pg-title: 布尔值隐式转换

// 空对象、空数组都是 true
const emptyObj = {};
console.log(Boolean(emptyObj));  // true
const emptyArr = [];
console.log(Boolean(emptyArr));  // true

// number 0/NaN 为 false,其余皆为 true。
console.log(Boolean(0));  // false

// 空字符串为 false,其余皆为 true
console.log(Boolean('')); // false
console.log(Boolean('0')); // true

// null 与 undefined 皆为 false
console.log(Boolean(null));
console.log(Boolean(undefined));

null 与 undefined

null 是一个特殊的 object 值,其含义为

undefined 语义上虽然好像表示"变量未定义",实际上更多的时候表示的是"值未定义"。

// pg-title: 默认值为 undefined 的场景

// 未初始化的变量,其值为 undefined
let a;
console.log(a === undefined);

// 对象中未定义的key,其值为 undefined。这里是唯一有 "变量未定义" 的感觉的地方。
const obj = {};
console.log(obj.xyz);

// 函数的参数没有传,其值为 undefined。
function hello(name) {
  console.log(name);
}
hello();

// 函数没有 return,或者 return 为空,其返回值也是 undefined
const result = hello();
console.log(result);

undefined 可以触发函数的参数默认值,即便这个 undefined 是你有意传进去的。

// pg-title: 触发函数参数默认值
function hello(name = 'world') {
  console.log(`hello, ${name}`);
}

// 未传参数,会触发默认值
hello();

// 主动传入 undefined 也会触发默认值
hello(undefined);

symbol

symbol 的作用,估计是为了在 JS 的对象里实现"私有属性"。

symbol 的特性就是每次创建出来的值都是不同的。

// weight 只是作为 debug 识别辅助,对 Symbol 的值不起任何作用。
Symbol("weight") === Symbol("weight")  // false

因此,如果外界没有某个 Symbol 值的引用,那么一般情况下就访问不到对象里的这个 symbol 属性。当然了,那是一般情况。有个特殊的API可以获取:

// pg-title: 获取对象的 Symbol 属性。
const obj = {};
obj[Symbol()] = 1;
obj[Symbol()] = 2;
obj[Symbol()] = 'name';

console.log(Object.getOwnPropertySymbols(obj));

大部分访问对象属性的 API,默认都是不会访问到 symbol 属性的,这大概也是其能充当"私有属性"的原因。

总体而言,用起来还是比较麻烦。不是很特殊的情况,基本用不太到。