数据类型和操作流程
基本数据类型
Undefined type
Undefined type
- 表示未定义值;
- 表示变量声明,但未初始化的状态;
- 用于可选函数;
typeof 的操作
// typeof 对无声明和未初始化的变量 返回 undefined
let message;
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"
Null type
Null type
- 表示没有值;
- 通常用于初始化对象,表示一个空的对象;
与 undefined 的关系
// 两者使用判等操作符相等;
// 使用严格判等操作符不等;
console.log(null == undefined); // true
console.log(null === undefined); // false
Boolean type
布尔类型
- true + false;
布尔类型与数字的关系
- true 不等于 1;
- false 不等于 0;
- 两者通过 Number() 可转换为 1 和 0;
任意类型的布尔值
- 任意类型都具有布尔值;
- 可通过 Boolean() 函数转换;
数据类型 | 真值 | 假值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | 空字符串 |
Number | 任何非 0 数字 | 0, NaN |
Object | 任意对象 | null |
Undefined | n/a | undefined |
Number Type
基础
格式
- 使用 IEEE–754 表示整数和浮点数;
- 64 位双浮点数精度;
进制
// 十进制
let intNum = 55; // integer
// 二进制
let bNum = 0b10;
// 八进制
let octalNum = 070; // octal for 56
// 十六进制
let hexNum = 0xa;
浮点数
语法格式
let floatNum1 = 1.1;
float 到 int 的隐式转换
// 若小数点后无数字或为 0, 将其转换为 int
let floatNum1 = 1; // 等效于 int 1
let floatNum2 = 10.0; // 等效于 int 10
科学计数法
let floatNum = 3.125e7; // equal to 31250000
let floatNum = 3e-7; // equal to 0.0000003
浮点数的精度损失
// 不要用于 if 语句中测试具体值
let a = 0.1 + 0.2; // equal to 0.30000000000000004
数字的范围
常量
- Number.MIN_VALUE:5e–324;
- Number.MAX_VALUE:1.7976931348623157e+308;
- Number.NEGATIVE_INFINITY:-Infinity;
- Number.POSITIVE_INFINITY:Infinity;
计算机制
- 若计算范围超过 JavaScript 范围;
- 结果为 Infinity/-Infinity;
整数和浮点数的内存耗费
整数
- 数值为整数,存储在栈上,耗费 8 字节;
浮点数
- 数值为浮点数,存储在堆上,栈存储堆上的内存地址,耗费 16 字节;
NaN
NaN
- 表明无效值;
特性
- NaN 与任何值计算结果皆为 NaN;
- NaN 不等于任何值,包括 NaN;
- 只能通过 isNaN() 方法判断是否为 NaN,其余方法一直为 false;
console.log(typeof NaN); // 输出 "number"
let x = NaN;
console.log(x == NaN); // 输出 "false"
console.log(x === NaN); // 输出 "false"
console.log(isNaN(x)); // 输出 "true"
BigInt
- 任意精度格式的整数;
The String Type
基本概念
string
- 16-bit Unicode;
- immutable;
- 字符序列;
转义字符
转义字符 | 意义 | 转义字符 | 意义 |
---|---|---|---|
\0 | Null Byte | \' | 单引号 |
\b | 退格 | \" | 双引号 |
\n | 换行符 | \ | \ |
\f | 换页符 | \XXX | 八进制 |
\r | 回车键 | \xnn | 二位十六进制 |
\t | 水平制表符 | \unnnn | 四位十六进制 |
length 属性
- 字符串长度;
- 当字符串中包含双字节字符 (如汉字,emoji);
- length 不一定准确;
模板字面量
基本语法
let name = "kxh";
console.log(`My name is ${name}.`); // My name is kxh;
标签函数
// 若 Template Literal 具有 n 个 Interpolation
// 第一个参数以 Template Literal 中 n 个 Interpolation 作为分隔点, 构成具有 n + 1 个字符串片段的数组
// 其余 n 个参数依次为 Template Literal 中的 n 个 Interpolation
let a = 6;
let b = 9;
function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
console.log(strings);
console.log(aValExpression);
console.log(bValExpression);
console.log(sumExpression);
return "foobar";
}
let taggedResult = simpleTag`${a} + ${b} = ${a + b}`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(taggedResult); // "foobar"
原始字符串
console.log(`first line\nsecond line`);
// first line
// second line
console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"
Symbol 类型
基础
Symbol
- 原子类型;
- Symbol 实例是唯一,不可变的;
let sym = Symbol();
// 字符串是对 sym 的描述, 与符号标识无关
let sym = Symbol("test");
符号作为属性
let s1 = Symbol("foo");
s2 = Symbol("bar");
s3 = Symbol("baz");
s4 = Symbol("qux");
// 使用计算属性语法
let o = {
[s1]: "foo val";
};
// 使用 Object.defineProperty()/Object.defineProperties() 方法
Object.defineProperty(o, s2, { value: "bar val" });
Object.defineProperties(o, {
[s3]: { value: "baz val" };
[s4]: { value: "qux val" };
});
方法
for()
// Symbol.for(key)
// 使用 for() 全局注册方法
// 执行 for() 方法, 若全局注册表中不存在 key 则生成一个 Symbol 实例, 反之返回已存在的对应实例
let fooGlobalSymbol = Symbol.for("foo"); // 创建新符号
let otherFooGlobalSymbol = Symbol.for("foo"); // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
keyFor()
// Symbol.keyFor(sym)
// 通过 Symbol 实例查询其在全局注册表中的 key 值
let s = Symbol.for("foo");
console.log(Symbol.keyFor(s)); // foo
内置符号
Symbol.asyncIterator
// 表示一个方法, 该方法返回对象的 AsyncIterator, 用于 for-await-of 语句
class Emitter {
constructor(max) {
this.max = max;
this.asyncIdx = 0;
}
async *[Symbol.asyncIterator]() {
while (this.asyncIdx < this.max) {
yield new Promise((resolve) => resolve(this.asyncIdx++));
}
}
}
async function asyncCount() {
let emitter = new Emitter(5);
for await (const x of emitter) {
console.log(x);
}
}
asyncCount();
// 0
// 1
// 2
// 3
// 4
Symbol.hasInstance
// 表示一个方法, 该方法确定一个对象是否使其实例, 用于 instanceof 操作符
// 该方法定义在 Function 原型上
class Bar {}
class Baz extends Bar {
static [Symbol.hasInstance]() {
return false;
}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
Symbol.isConcatSpreadable
// 表示一个布尔值, 表示该对象使用 Array.prototype.concat() 是否打平其数组元素, 默认为 true
let initial = ["foo"];
let array = ["bar"];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)
Symbol.iterator
// 表示一个方法, 该方法返回对象的 Iterator, 用于 for-of 语句
class Emitter {
constructor(max) {
this.max = max;
this.idx = 0;
}
*[Symbol.iterator]() {
while (this.idx < this.max) {
yield this.idx++;
}
}
}
let emitter = new Emitter(5);
for (const x of emitter) {
console.log(x);
}
Symbol.match
// 表示一个正则表达式方法, 该方法确定如何使用正则表达式匹配字符串, String.prototype.match() 会使用 Symbol.match 为键的函数对正则表达式求值
// RegExp 默认具有该属性
console.log(RegExp.prototype[Symbol.match]); // ƒ [Symbol.match]() { [native code] }
console.log("foobar".match(/bar/));
// 自定义行为
class FooMatcher {
static [Symbol.match](target) {
return target.includes("foo");
}
}
console.log("foobar".match(FooMatcher)); // true
console.log("barbaz".match(FooMatcher)); // false
// Symbol.replace 用于 String.prototype.replace(), 同 Symbol.match
// Symbol.search 用于 String.prototype.search(), 同 Symbol.match
// Symbol.split 用于 String.prototype.split(), 同 Symbol.match
// Symbol.toStringTag 用于 String.prototype.toString(), 同 Symbol.match
Symbol.species
// 表示一个函数, 该函数返回值作为实例的原型定义
class Baz extends Array {
static get [Symbol.species]() {
return Array;
}
}
let baz = new Baz();
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // true
baz = baz.concat("baz");
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // false
Symbol.toPrimitive
// 表示一个方法, 该方法将对象转换为对应的原始值
class Bar {
constructor() {
this[Symbol.toPrimitive] = function (hint) {
switch (hint) {
case "number":
return 3;
case "string":
return "string bar";
case "default":
default:
return "default bar";
}
};
}
}
let bar = new Bar();
console.log(3 + bar); // "3default bar"
console.log(3 - bar); // 0
console.log(String(bar)); // "string bar"
Symbol.unscopables
// 表示一个对象, 该对象所有的属性都会在其关联对象的 with 环境中排除
// 强烈不推荐使用
let o = { foo: "bar" };
with (o) {
console.log(foo); // bar
}
o[Symbol.unscopables] = {
foo: true;
};
with (o) {
console.log(foo); // ReferenceError
}
操作流程
if 语句
// 依次判断 if block 或 else if block 中的 condition 表达式是否为真
// 若为真则执行对应块
// 若都不为真, 执行 else block
if (condition1) {
statement1;
} else if (condition2) {
statement2;
} else {
statement3;
}
switch 语句
// 首先执行 expression, 获得其值
// 使用 == 寻找 case 子句中与 expression 值匹配的 value
// 执行对应块
// 若任一 label 都不匹配, 执行 default 块
// 推荐总是写 break 和 default, 避免 fall through
switch (i) {
case 25:
/* falls through */
case 35:
console.log("25 or 35");
break;
case 45:
console.log("45");
break;
default:
console.log("Other");
}
while 语句
// 1. 执行 condition;
// 2. 若为真, 执行 statement, 反之结束循环;
// 3. 执行 statement 后, 返回步骤 1;
while (condition) {
statement;
}
do-while 语句
// statement 必定执行一次
// 1. 执行一次 statement;
// 2. 执行 statement 后, 执行 condition;
// 2.1 若为真, 执行 statement;
// 2.2 反之结束循环;
// 3. 返回 步骤 2;
do {
statement;
} while (expression);
for 语句
// 1. 首先执行 initialExpression;
// 2. 执行 conditionExpression;
// 2.1 若为真, 执行 statement;
// 2.2 反之结束循环;
// 3. 执行 statement 后, 执行 incrementExpression;
// 4. 返回步骤 2;
for (initialization; expression; incrementExpression) {
statement;
}
等效的 while 语句
initialization;
while (expression) {
console.log(i);
statement;
incrementExpression;
}
for-in 语句
语法格式
// 1. property 依次取 expression 所有可枚举的属性;
// 1.1 若 expression 未取完, 执行 statement 语句;
// 1.2 反之结束循环;
// 2. 执行 suite 语句后, 返回步骤 1;
for (property in expression) {
statement;
}
原型链
- for in 会遍历原型链上所有可枚举属性;
- 需要判断属性是否为对象自身属性;
for (let prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
console.log(`${prop}: ${obj[prop]}`);
}
}
for-of 语句
语法格式
// 1. property 依次取 expression 内可迭代对象定义的值;
// 1.1 若 expression 未取完, 执行 statement 语句;
// 1.2 反之结束循环;
// 2. 执行 suite 语句后, 返回步骤 1;
for (property of expression) {
statement;
}
for...in 和 for...of 的区别
- for in 一般用来遍历对象的 key;
- for of 一般用来遍历数组的 value;
Labeled 语句
// 标识 statement
// 用于 continue 和 break
start: for (let i = 0; i < count; i++) {
console.log(i);
}
break 和 continue 语句
break
// 跳出最近的 loop 或 switch
// 跳出指定 labeled statement
let num = 0;
outermost: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++;
}
}
console.log(num); // 55
continue
// 结束当前循环, 跳转至最近的 loop 下一循环起始步骤
// 跳转至指定 labeled statement
let num = 0;
outermost: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
continue outermost;
}
num++;
}
}
console.log(num); // 95
with 语句
机制
// 简化多次调用同一对象的操作
// 将 with 对应代码作用域设置到 location 中
// 并将 location 对应的 execution context 移动至 scope chain 最前端
let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;
with (location) {
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}
strict mode
- 在 strict mode 中;
- with 语句被看作是语法错误;
- 生产环境中最好不要使用 with 语句;
最佳实践
null 和 undefined
null 和 undefined 的区别
- undefined 强调变量定义但未初始化;
- null 强调一个空的状态,常用于 object;
最佳实践
- 基本所有的编程语言的空值都是对应的 js 中的 null;
- js 中除变量未初始化和可选函数之外;
- 推荐全部使用 null;
- 但是判断空值时两者都要判断;
- 可使用
if(value == null)
判断;
- 可使用
解决浮点数精度问题
Number.EPSILON
- ES6 提供 Number.EPSILON 属性;
- 只要误差小于其,即可判断相等;
浮点数转换为整数
- 根据实际情况,将浮点数转换为整数;
数学库
- 使用数学库;
判断数据类型的方法
不同方法优劣
typeof | instancof | constructor | Object.prototype.toString.call() | |
---|---|---|---|---|
优点 | 简单 | 可判断引用数据类型 | 检查除 null/undefined 的一切类型 | 所有类型 |
缺点 | 无法判断引用数据类型和 null | 不能检查基本数据类型, 无法跨 iframe | 不能跨 iframe, constor 可被修改 | 无法在 IE 使用 |
// 判断一个值是否为字符串
var str = "Hello, World!";
var strType = Object.prototype.toString.call(str); // 输出: [object String]
// 判断一个值是否为数字
var num = 42;
var numType = Object.prototype.toString.call(num); // 输出: [object Number]
// 判断一个值是否为布尔值
var bool = true;
var boolType = Object.prototype.toString.call(bool); // 输出: [object Boolean]
// 判断一个值是否为数组
var arr = [1, 2, 3];
var arrType = Object.prototype.toString.call(arr); // 输出: [object Array]
// 判断一个值是否为对象
var obj = { name: "John", age: 30 };
var objType = Object.prototype.toString.call(obj); // 输出: [object Object]
// 判断一个值是否为函数
var func = function () {
console.log("Hello, World!");
};
var funcType = Object.prototype.toString.call(func); // 输出: [object Function]
// 判断一个值是否为null
var funcType = Object.prototype.toString.call(null); // [object Null]
// 判断一个值是否为undefined
var funcType = Object.prototype.toString.call(undefined); // [object Undefined]
判断空对象
- 使用 Object.keys() 方法;
- 使用 for in;
- 使用 JSON.stringify() 方法;
Object.keys(obj).length === 0;
function isEmpty(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
return false;
}
}
return true;
}
JSON.stringify(obj) === "{}";
typeof 历史遗留问题
typeof null; // object
typeof NaN; // number
判断是否为数组
- Array.isArray();
- Object.prototype.toString.call();
- instaceof;
let myVar = [1, 2, 3];
Array.isArray(myVar);
Object.prototype.toString.call(myVar) === "[object Array]";
myVar instanceof Array;
自定义类型判断方法
function typeOf(obj) {
const type = typeof obj;
if (type !== "object") {
return type;
}
return Object.prototype.toString
.call(obj)
.replace(/^\[object (\S+)\]$/, "$1");
}
数字千分位分隔
- 数字转换为字符串数组,三位添加逗号;
- 使用正则表达式;
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
- 使用 Intl.NumberFormat() AP
var number = 1234567.8911111;
var formattedNumber = new Intl.NumberFormat("en-US", {}).format(number);
console.log(formattedNumber); // 1,234,567.891
- 使用 String.toLocaleString() API;
- 实际调用 Intl.NumberFormat() API;
console.log((1234567.8911111).toLocaleString("en-US")); // 1,234,567.891