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 是一个特殊的值,表示“无定义”。
代码语言:javascript复制var a;
a; // undefined
JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。
代码语言:javascript复制var a = 1;
a = "hello";
如果使用 var 重新声明一个已经存在的变量,是无效的。
代码语言:javascript复制var x = 1;
var x;
x; // 1
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
代码语言:javascript复制console.log(a);
var a = 1;
// 在控制台(console)显示变量a的值。这时变量a还没有声明和赋值,所以这是一种错误的做法,但是实际上不会报错。因为存在变量提升,真正运行的是下面的代码。
var a;
console.log(a);
a = 1;
标识符命名规则如下:
- 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。
- 第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字 0-9。
对于 var 命令来说,JavaScript 的区块不构成单独的作用域(scope)。
代码语言:javascript复制{
var a = 1;
}
a; // 1
else 代码块总是与离自己最近的那个 if 语句配对。
代码语言:javascript复制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 语句后面的表示式比较运行结果时,采用的是严格相等运算符(===),而不是相等运算符(==),这意味着比较时不会发生类型转换。
代码语言:javascript复制var x = 1;
switch (x) {
case true:
console.log("x 发生类型转换");
break;
default:
console.log("x 没有发生类型转换");
}
// x 没有发生类型转换
for 语句的三个部分(initialize、test、increment),可以省略任何一个,也可以全部省略。
代码语言:javascript复制for (;;) {
console.log("Hello World");
}
上面代码省略了 for 语句表达式的三个部分,结果就导致了一个无限循环。
JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,标签的格式如下。
标签通常与 break 语句和 continue 语句配合使用,跳出特定的循环。
代码语言:javascript复制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 可以用来检查一个没有声明的变量,而不报错。
代码语言:javascript复制v;
// ReferenceError: v is not defined
typeof v;
// "undefined"
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
代码语言:javascript复制typeof window; // "object"
typeof {}; // "object"
typeof []; // "object"
上面代码中,空数组([])的类型也是 object,这表示在 JavaScript 内部,数组本质上只是一种特殊的对象。instanceof 运算符可以区分数组和对象。
代码语言:javascript复制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 就没法改变了。
代码语言:javascript复制typeof null; // "object"
null, undefined 和布尔值
null 与 undefined 都可以表示“没有”,含义非常相似。将一个变量赋值为 undefined 或 null,语法效果几乎没区别。
代码语言:javascript复制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。
代码语言:javascript复制Number(null); // 0
5 null; // 5
但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。首先,第一版的 JavaScript 里面,null 就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示“无”的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果 null 自动转为 0,很不容易发现错误。
因此,他又设计了一个 undefined。区别是这样的:null 是一个表示“空”的对象,转为数值时为 0;undefined 是一个表示”此处无定义”的原始值,转为数值时为 NaN。
代码语言:javascript复制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复制容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把 64 位浮点数,转成 32 位整数,然后再进行运算。
1 === 1.0; // true
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
代码语言:javascript复制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 位):小数部分(即有效数字)
符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
代码语言:javascript复制Math.pow(2, 53);
// 9007199254740992
// 多出的三个有效数字,将无法保存
9007199254740992111;
// 9007199254740992000
代码语言:javascript复制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,则该数值被视为十进制。
代码语言:javascript复制0888; // 888
0777; // 511
JavaScript 的 64 位浮点数之中,有一个二进制位是符号位。这意味着,任何一个数都有一个对应的负值,就连 0 也不例外。
JavaScript 内部实际上存在 2 个 0:一个是 0,一个是 -0,区别就是 64 位浮点数表示法的符号位不同。它们是等价的。
代码语言:javascript复制-0 === 0; // true
0 === -0; // true
0 === 0; // true
0; // 0
-0; // 0
(-0).toString(); // '0'
( 0).toString(); // '0'
代码语言:javascript复制1 / 0 === 1 / -0; // false
// Infinity !== -Infinity
NaN 是 JavaScript 的特殊值,表示“非数字” Not a Number,主要出现在将字符串解析成数字出错的场合。
代码语言:javascript复制5 - "x"; // NaN
Math.acos(2); // NaN
Math.log(-1); // NaN
Math.sqrt(-1); // NaN
0 / 0; // NaN
NaN 不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于 Number。
代码语言:javascript复制typeof NaN; // 'number'
代码语言:javascript复制NaN === NaN; // false
[NaN].indexOf(NaN); // -1
Boolean(NaN); // false
NaN 32; // NaN
NaN - 32; // NaN
NaN * 32; // NaN
NaN / 32; // NaN
代码语言:javascript复制// 场景一
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
与数值相关的全局方法
代码语言:javascript复制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
代码语言:javascript复制// 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。
parseInt(1000000000000000000000.5); // 1
// 等同于
parseInt("1e 21"); // 1
parseInt(0.0000008); // 8
// 等同于
parseInt("8e-7"); // 8
代码语言:javascript复制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
- # javascript