/

【JavaScript 教程 | 网道】笔记

JavaScript 教程 | 网道(WangDoc.com)

JavaScript 语言的历史

ECMAScript 只用来标准化 JavaScript 这种语言的基本语法结构,与部署环境相关的标准都由其他标准规定,比如 DOM 的标准就是由 W3C 组织(World Wide Web Consortium)制定的。

2011 年 6 月,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。到了 2012 年底,所有主要浏览器都支持 ECMAScript 5.1 版的全部功能。

2015 年 6 月,ECMAScript 6 正式发布,并且更名为“ECMAScript 2015”。这是因为 TC39 委员会计划,以后每年发布一个 ECMAScript 的版本,下一个版本在 2016 年发布,称为“ECMAScript 2016”,2017 年发布“ECMAScript 2017”,以此类推。

JavaScript 的基本语法

如果只是声明变量而没有赋值,则该变量的值是 undefined。undefined 是一个特殊的值,表示“无定义”。

var a;
a; // undefined

JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。

var a = 1;
a = "hello";

如果使用 var 重新声明一个已经存在的变量,是无效的。

var x = 1;
var x;
x; // 1

JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。

console.log(a);
var a = 1;

// 在控制台(console)显示变量a的值。这时变量a还没有声明和赋值,所以这是一种错误的做法,但是实际上不会报错。因为存在变量提升,真正运行的是下面的代码。

var a;
console.log(a);
a = 1;

标识符命名规则如下:

  • 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。
  • 第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字 0-9。

对于 var 命令来说,JavaScript 的区块不构成单独的作用域(scope)。

{
var a = 1;
}

a; // 1

else 代码块总是与离自己最近的那个 if 语句配对。

var m = 1;
var n = 2;

if (m !== 1)
if (n === 2) console.log("hello");
else console.log("world");

// 相当于

if (m !== 1) {
if (n === 2) {
console.log("hello");
} else {
console.log("world");
}
}

switch 语句后面的表达式,与 case 语句后面的表示式比较运行结果时,采用的是严格相等运算符(===),而不是相等运算符(==),这意味着比较时不会发生类型转换。

var x = 1;

switch (x) {
case true:
console.log("x 发生类型转换");
break;
default:
console.log("x 没有发生类型转换");
}
// x 没有发生类型转换

for 语句的三个部分(initialize、test、increment),可以省略任何一个,也可以全部省略。

for (;;) {
console.log("Hello World");
}

上面代码省略了 for 语句表达式的三个部分,结果就导致了一个无限循环。

JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,标签的格式如下。

标签通常与 break 语句和 continue 语句配合使用,跳出特定的循环。

top: for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (i === 1 && j === 1) break top;
console.log("i=" + i + ", j=" + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0

top: for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (i === 1 && j === 1) continue top;
console.log("i=" + i + ", j=" + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=2, j=0
// i=2, j=1
// i=2, j=2

数据类型概述

JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值,本教程不涉及。)

  • 数值(number):整数和小数(比如 1 和 3.14)。
  • 字符串(string):文本(比如 Hello World)。
  • 布尔值(boolean):表示真伪的两个特殊值,即 true(真)和 false(假)。
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值。
  • null:表示空值,即此处的值为空。
  • 对象(object):各种值组成的集合。

通常,数值、字符串、布尔值这三种类型,合称为原始类型(primitive type)的值,即它们是最基本的数据类型,不能再细分了。

对象则称为合成类型(complex type)的值,因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。

至于 undefined 和 null,一般将它们看成两个特殊值。

对象是最复杂的数据类型,又可以分成三个子类型。

  • 狭义的对象(object)
  • 数组(array)
  • 函数(function)

函数其实是处理数据的方法,JavaScript 把它当成一种数据类型,可以赋值给变量,这为编程带来了很大的灵活性,也为 JavaScript 的“函数式编程”奠定了基础。

JavaScript 有三种方法,可以确定一个值到底是什么类型。

  • typeof 运算符
  • instanceof 运算符
  • Object.prototype.toString 方法
typeof 123; // "number"
typeof "123"; // "string"
typeof false; // "boolean"

function f() {}
typeof f;
// "function"

typeof undefined;
// "undefined"

利用这一点,typeof 可以用来检查一个没有声明的变量,而不报错。

v;
// ReferenceError: v is not defined

typeof v;
// "undefined"

// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined

// 正确的写法
if (typeof v === "undefined") {
// ...
}
typeof window; // "object"
typeof {}; // "object"
typeof []; // "object"

上面代码中,空数组([])的类型也是 object,这表示在 JavaScript 内部,数组本质上只是一种特殊的对象。instanceof 运算符可以区分数组和对象。

var o = {};
var a = [];

o instanceof Array; // false
o instanceof Object; // true
a instanceof Array; // true
a instanceof Object; // false

null 的类型是 object,这是由于历史原因造成的。1995 年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑 null,只把它当作 object 的一种特殊值。后来 null 独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null 返回 object 就没法改变了。

typeof null; // "object"

null, undefined 和布尔值

null 与 undefined 都可以表示“没有”,含义非常相似。将一个变量赋值为 undefined 或 null,语法效果几乎没区别。

if (!undefined) {
console.log("undefined is false");
}
// undefined is false

if (!null) {
console.log("null is false");
}
// null is false

undefined == null;
// true

1995 年 JavaScript 诞生时,最初像 Java 一样,只设置了 null 表示”无”。根据 C 语言的传统,null 可以自动转为 0。

Number(null); // 0
5 + null; // 5

但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。首先,第一版的 JavaScript 里面,null 就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示“无”的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果 null 自动转为 0,很不容易发现错误。

因此,他又设计了一个 undefined。区别是这样的:null 是一个表示“空”的对象,转为数值时为 0;undefined 是一个表示”此处无定义”的原始值,转为数值时为 NaN。

Number(undefined); // NaN
5 + undefined; // NaN
  • null 表示空值,即该处的值现在为空。调用函数时,某个参数未设置任何值,这时就可以传入 null,表示该参数为空。比如,某个函数接受引擎抛出的错误作为参数,如果运行过程中未出错,那么这个参数就会传入 null,表示未发生错误。
  • undefined 表示“未定义”,下面是返回 undefined 的典型场景。
// 变量声明了,但没有赋值
var i;
i; // undefined

// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f(); // undefined

// 对象没有赋值的属性
var o = new Object();
o.p; // undefined

// 函数没有返回值时,默认返回 undefined
function f() {}
f(); // undefined

如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为 false,其他值都视为 true。

  • undefined
  • null
  • false
  • 0
  • NaN
  • “”或’’(空字符串)

注意,空数组([])和空对象({})对应的布尔值,都是 true。

数值

JavaScript 内部,所有数字都是以 64 位浮点数形式储存,即使整数也是如此。所以,1 与 1.0 是相同的,是同一个数。

这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64 位浮点数)。

容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把 64 位浮点数,转成 32 位整数,然后再进行运算。

1 === 1.0; // true

由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。

0.1 + 0.2 === 0.3;
// false

0.3 /
0.1(
// 2.9999999999999996

0.3 - 0.2
) ===
0.2 - 0.1;
// false

根据国际标准 IEEE 754,JavaScript 浮点数的 64 个二进制位,从最左边开始,是这样组成的。

  • 第 1 位:符号位,0 表示正数,1 表示负数
  • 第 2 位到第 12 位(共 11 位):指数部分
  • 第 13 位到第 64 位(共 52 位):小数部分(即有效数字)

符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。

Math.pow(2, 53);
// 9007199254740992

// 多出的三个有效数字,将无法保存
9007199254740992111;
// 9007199254740992000
Math.pow(2, 1024); // Infinity “正向溢出”

Math.pow(2, -1075); // 0

Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324

123e3; // 123000
123e-3; // 0.123
-3.1e12;
0.1e-23;

// 小数点前的数字多于21位。
1234567890123456789012;
// 1.2345678901234568e+21

123456789012345678901;
// 123456789012345680000

// 小数点后的零多于5个。
// 小数点后紧跟5个以上的零,就自动转为科学计数法
0.0000003; // 3e-7

// 否则,就保持原来的字面形式
0.000003; // 0.000003
  • 十进制:没有前导 0 的数值。
  • 八进制:有前缀 0o 或 0O 的数值,或者有前导 0、且只用到 0-7 的八个阿拉伯数字的数值。
  • 十六进制:有前缀 0x 或 0X 的数值。
  • 二进制:有前缀 0b 或 0B 的数值。

有前导 0 的数值会被视为八进制,但是如果前导 0 后面有数字 8 和 9,则该数值被视为十进制。

0888; // 888
0777; // 511

JavaScript 的 64 位浮点数之中,有一个二进制位是符号位。这意味着,任何一个数都有一个对应的负值,就连 0 也不例外。

JavaScript 内部实际上存在 2 个 0:一个是 +0,一个是 -0,区别就是 64 位浮点数表示法的符号位不同。它们是等价的。

-0 === +0; // true
0 === -0; // true
0 === +0; // true

+0; // 0
-0; // 0
(-0).toString(); // '0'
(+0).toString(); // '0'
1 / +0 === 1 / -0; // false

// +Infinity !== -Infinity

NaN 是 JavaScript 的特殊值,表示“非数字” Not a Number,主要出现在将字符串解析成数字出错的场合。

5 - "x"; // NaN

Math.acos(2); // NaN
Math.log(-1); // NaN
Math.sqrt(-1); // NaN

0 / 0; // NaN

NaN 不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于 Number。

typeof NaN; // 'number'
NaN === NaN; // false

[NaN].indexOf(NaN); // -1

Boolean(NaN); // false

NaN + 32; // NaN
NaN - 32; // NaN
NaN * 32; // NaN
NaN / 32; // NaN
// 场景一
Math.pow(2, 1024);
// Infinity

// 场景二
0 / 0; // NaN
1 / 0; // Infinity

Infinity === -Infinity; // false

1 / -0; // -Infinity
1 / -0; // Infinity

Infinity > 1000; // true
-Infinity < -1000; // true

Infinity > NaN; // false
-Infinity > NaN; // false

Infinity < NaN; // false
-Infinity < NaN; // false

5 * Infinity; // Infinity
5 - Infinity; // -Infinity
Infinity / 5; // Infinity
5 / Infinity; // 0

0 * Infinity; // NaN
0 / Infinity; // 0
Infinity / 0; // Infinity

Infinity + Infinity; // Infinity
Infinity * Infinity; // Infinity

Infinity - Infinity; // NaN
Infinity / Infinity; // NaN

null * Infinity; // NaN
null / Infinity; // 0
Infinity / null; // Infinity

undefined + Infinity; // NaN
undefined - Infinity; // NaN
undefined * Infinity; // NaN
undefined / Infinity; // NaN
Infinity / undefined; // NaN

与数值相关的全局方法

parseInt("123"); // 123

// 如果字符串头部有空格,空格会被自动去除。

parseInt(" 81"); // 81

// 如果parseInt的参数不是字符串,则会先转为字符串再转换。

parseInt(1.23); // 1
// 等同于
parseInt("1.23"); // 1

// 字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。
parseInt("8a"); // 8
parseInt("12**"); // 12
parseInt("12.34"); // 12
parseInt("15e2"); // 15
parseInt("15px"); // 15

// 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。
parseInt("abc"); // NaN
parseInt(".3"); // NaN
parseInt(""); // NaN
parseInt("+"); // NaN
parseInt("+1"); // 1

// parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN。

parseInt("0x10"); // 16

parseInt("011"); // 11
// 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。
parseInt(1000000000000000000000.5); // 1
// 等同于
parseInt("1e+21"); // 1

parseInt(0.0000008); // 8
// 等同于
parseInt("8e-7"); // 8
parseInt("1000", 2); // 8
parseInt("1000", 6); // 216
parseInt("1000", 8); // 512

// 第二个参数是0、undefined和null,则直接忽略。
parseInt("10", 37); // NaN
parseInt("10", 1); // NaN
parseInt("10", 0); // 10
parseInt("10", null); // 10
parseInt("10", undefined); // 10

parseInt("1546", 2); // 1
parseInt("546", 2); // NaN

// 如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。
parseInt(0x11, 36); // 43
parseInt(0x11, 2); // 1

// 等同于
parseInt(String(0x11), 36); // 43
parseInt(String(0x11), 2); // 1

// 等同于
parseInt("17", 36); // 43
parseInt("17", 2); // 1
// 上面代码中,十六进制的0x11会被先转为十进制的17,再转为字符串。然后,再用36进制或二进制解读字符串17,最后返回结果43和1

parseInt(011, 2); // NaN

// 等同于
parseInt(String(011), 2); // NaN

// 等同于
parseInt(String(9), 2); // NaN