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
的运算过程大概如下:
- 把 0.1 与 0.2 分别转为双精度浮点数的二进制。
- 进行运算。具体细节未进行深究,估计可以从这里查到。反正得到的结果也是二进制。
- 再把双精度浮点数二进制转成 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 的布尔值是 true
与 false
。可以通过逻辑运算符把其它类型的值显示转换为布尔值,在条件语句里的表达式会被隐式转换为布尔值。
列举一些隐式转换
// 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 属性的,这大概也是其能充当"私有属性"的原因。
总体而言,用起来还是比较麻烦。不是很特殊的情况,基本用不太到。