系统地过一遍 JavaScript 基础语法及各种知识点。



声明变量

  • ES 5: var
  • ES 6: let 声明变量,const 声明常量。

变量提升 (hoisting)

JavaScript 引擎会先解析代码并获取所有使用 var 声明的变量并完成声明操作,再按顺序逐行运行。

注意 所有使用 var 声明的变量 包括了各种块级作用域的变量,如 for 语句或使用一对花括号声明的代码块 {}。很明显这导致了这些本应只在局部生效的变量变成了全局变量。

在浏览器的 console 中运行以下代码 (example 1-1) 观察该问题:

1
2
3
4
5
6
7
8
9
10
11
// example 1-1

if (true) {
console.log(`output1: ${x}`);
var x = 'hello';
console.log(`output2: ${x}`);
}
console.log(`output3: ${x}`);
// output1: undefined
// output2: hello
// output3: hello

运行结果解释:变量 var x声明操作被提升到脚本开头(但没完成初始化/赋值操作),因此第一次输出为 undefined, 第二次和第三次输出为正常内容。

代码的实际运行过程等同于:

1
2
3
4
5
6
7
var x; // undefined, 注意不是 null
if (true) {
console.log(`output1: ${x}`); // undefined
x = 'hello';
console.log(`output2: ${x}`); // hello
}
console.log(`output3: ${x}`); // hello

总的来说,这段代码 (example 1-1) 包含了两个问题:使用未声明的变量没有报错,违反了先声明后使用的原则、局部变量在作用域以外调用没有报错。而这两个问题都是由 var 变量的提升导致的。

使用 let 关键字声明变量,而不是 var 关键字,则可以避免该问题:

1
2
3
4
5
6
if (true) {
console.log(`output1: ${x}`); // ReferenceError
let x = 'hello';
console.log(`output2: ${x}`);
}
console.log(`output3: ${x}`); // ReferenceError

改为 let 声明后,变量必须先声明才能使用,且作用域仅为当前代码块,结果符合预期。

全面使用 let 替代 var 关键字。

参考来源: 变量提升
参考来源:let 取代 var

运算符

  • == 相等运算符,不同类型的元素会进行类型转换。
  • === 严格相等运算符,元素进行比较时不会进行类型转换。

// TODO ES 6 补充

数据类型

  • 数值 Number
  • 字符串 String
  • 布尔值 Boolean
  • 空值 null
  • 未定义 undefined
  • 对象 Object
  • Symbol (ES 6 新增)

其中 number, string, boolean 为基础数据类型,object 类型为复合数据类型。

Object 类型可以细分为三种子类:

  • OOP 中的对象(狭义的对象)
  • 数组 Array
  • 函数 Function

类型判断

  • typeof 一元运算符,返回变量的类型 (String)
  • instanceof 二元运算符: a instanceof Type, 返回的结果为 true/false // TODO
  • Object.prototype.toString // TODO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 0;
typeof a // "number"

let b = true;
typeof b // "boolean"

let c = 'hello';
typeof c // "string"

let d = function () {}
typeof d // "function"

typeof null // "object"
typeof undefined // "undefined"

instanceof 运算符可以区别普通对象和数组:

1
2
3
4
5
6
7
8
9
10
11
12
let a = {};
a instanceof Object // true
a instanceof Array // false

let b = [];
b instanceof Object // true
b instanceof Array // true

let c = () => {} // 箭头函数
typeof c // function
c instanceof Object // true
c instanceof Function // true

历史遗留问题

typeof null 结果为 Object

在变量的定义中,null 是与 Object 不同的数据类型。但在实际测试中发现 typeof null 返回的类型名为 Object。

这是因为在 JS 设计之初,作者没有深入考虑 null 的定义, 只把它作为 Object 的特殊形式。在 JS 的后续版本中 null 独立为单独的数据类型后,为了向下兼容,typeof null 仍为 object.

null 与 undefined 的问题

在 JS 语言设计之初,由于 C 语言的传统,null 可以自动转为 0:

1
2
3
let n = Number(null);
n // 0
n + 1 // 1

这可能会导致难以察觉的问题。因此 JS 作者又设计了 undefined

1
2
3
let n = Number(undefined);
n // NaN
n + 1 // NaN

undefined 的一些场景:

  1. 只声明但没有赋值的变量;
  2. 调用函数时没有传递相应的参数,则函数内部的参数为 undefined;
  3. 对象中不存在的属性;
1
2
3
4
5
6
7
8
9
10
11
12
13
// case1
let param;
console.log(param); // undefined

// case2
function f(x) {
console.log(x); // undefined
}
f();

// case3
let obj = {};
console.log(obj.param); // undefined

布尔值 boolean

在需要布尔值的位置,非 boolean 的对象会被转换为 boolean. 默认为 false 的类型:

undefined / null / 0 / NaN / ‘’ (空字符串)

数值 Number

JavaScript 内部使用 64 位浮点数形式存储数值类型。

1
2
3
4
5
6
7
let a = 1;
let b = 1.00;

if (a === b) {
console.log('the same!');
}
// the same!

当需要以整数的方式进行计算时(如位运算),64 位的浮点数会被转换为 32 位的整数。

// TODO ‘位运算’章节补充

对于浮点数的处理,尤其需要注意精度。

1
2
3
4
5
0.01 + 0.02 === 0.03 // true
0.1 + 0.2 === 0.3 // false

0.3 / 0.1 // 2.9999999999999996
0.3 - 0.2 === 0.2 - 0.1 // false

建议尽量避免直接基于浮点数(数值类型)进行判断,而转化为字符串。

参考:数值的精度与范围

数值的进制

  • 二进制:以 0b0B 为前缀 (b for binary);
  • 八进制:以 0o0O 为前缀(第二个字符为字母 o, 即 octonary);
  • 十六进制:以 0x0X 为前缀 (x for hexidecimal);
1
2
3
4
5
6
7
8
9
10
11
// 二进制形式
let x = 0b11
console.log(x); // 3

// 八进制形式
let x = 0o11
console.log(x); // 9

// 十六进制形式
let x = 0x11
console.log(x); // 17

特殊数值

正零和负零

只有符号位的区别。只有以正零或负零为分母时才会有区别(JS 允许分母为零):结果为 Infinity 或 -Infinity:

1
2
1/+0 // Infinity
1/-0 // -Infinity

NaN

Not a Number 的缩写,主要出现在字符串转为数值,或数学运算结果。NaN 不是独立的数据类型,而是 Number 中的一个特殊值。

1
2
3
0 / 0 // NaN

typeof NaN // "number"

NaN 的运算规则:

  • NaN 不等于它自身
  • NaN 与任何数的运算结果均为 NaN
1
NaN === NaN // false

数值相关全局方法

  • isNaN
  • parseInt
  • parseFloat

isNaN(val): 判断参数是否为 NaN

1
2
3
4
5
6
7
isNaN(); // true
isNaN('') // false
isNaN('1') // false
isNaN('1a') // true

isNaN(undefined)// true
isNaN(null) // false

parstInt(string, radix)

将参数转换为十进制整数。第一个参数为待转换的元素(如 "1.00"),第二个参数为第一个参数的进制,如果不传则默认第一个参数为十进制。

第二个参数的范围为 2-36, 最大值 36 由 0-9 及 a-z 按顺序组成。

1
2
3
4
5
6
7
8
9
10
parseInt(10) // 10
parseInt('10') // 10

parseInt(10, 2) // 2
parseInt(10, 8) // 8
parseInt('a') // NaN
parseInt('a', 16) // 10

parseInt('h', 16) // NaN
parseInt('h', 36) // 17

数值处理的异常情况:

1
2
3
4
5
6
7
8
9
// 开头的空格部分会被自动忽略
parseInt(' 1') // 1

// 结尾包含无法转换的字符则保留转换成功的部分
parseInt('11.99') // 11
parseInt('11xx') // 11

// 开头为非法字符,则会返回 NaN
parseInt('xx11') // NaN

parseFloat(string, radix)

除了结果为浮点数,均与 parseInt 相同。

1
2
parseFloat('15.0') // 15
parseFloat('15.01') // 15.01

字符串

长度属性:str.length;以数组的方式获取单个字符:str[0]

转码:

  • btoa(asciiStr): 将 ascii 字符串转为 base64 编码
  • atob(base64Str): 将 base64 编码内容转为 ascii 字符串

参考内容