JS(1)
前言
JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”(script language),指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序(比如浏览器)的“脚本”。
JavaScript 也是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多,只能用来做一些数学和逻辑运算。JavaScript 本身不提供任何与 I/O(输入/输出)相关的 API,都要靠宿主环境(host)提供,所以 JavaScript 只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。
目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器,另外还有服务器环境,也就是 Node 项目。
从语法角度看,JavaScript 语言是一种“对象模型”语言。各种宿主环境通过这个模型,描述自己的功能和操作接口,从而通过 JavaScript 控制这些功能。但是,JavaScript 并不是纯粹的“面向对象语言”,还支持其他编程范式(比如函数式编程)
基本语法
变量提升
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
标识符
JavaScript 语言的标识符对大小写敏感,所以a
和A
是两个不同的标识符
非法标识符:1
2
3
4
51a // 第一个字符不能是数字
23 // 同上
*** // 标识符不能包含星号
a+b // 标识符不能包含加号
-d // 标识符不能包含减号或连词线
中文是合法的标识符,可以用作变量名。1
var 临时变量 = 1;
三元运算符 ?:
1 | (条件) ? 表达式1 : 表达式2 |
上面代码中,如果“条件”为true
,则返回“表达式1”的值,否则返回“表达式2”的值。1
var even = (n % 2 === 0) ? true : false;
标签(label)
JavaScript 语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,标签的格式如下。
标签可以是任意的标识符,但不能是保留字,语句部分可以是任意语句。
标签通常与break语句和continue语句配合使用,跳出特定的循环。
1 | top: |
标签也可以用于跳出代码块。1
2
3
4
5
6
7
8foo: {
console.log(1);
break foo;
console.log('本行不会输出');
}
console.log(2);
// 1
// 2
数据类型
简介
- 数值(number):整数和小数(比如1和3.14)
- 字符串(string):文本(比如Hello World)。
- 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
- undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
- null:表示空值,即此处的值为空。
- 对象(object):各种值组成的集合。
数值、字符串、布尔值这三种类型,合称为原始类型(primitive type)的值.对象则称为合成类型(complex type)的值,因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。至于undefined
和null
,一般将它们看成两个特殊值。
对象又可以分为:
- 狭义的对象(object)
- 数组(array)
- 函数(function)
数据类型确定
JavaScript 有三种方法,可以确定一个值到底是什么类型。
typeof
运算符
instanceof
运算符
Object.prototype.toString
方法
数值
整数和浮点数
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1
与1.0
是相同的,是同一个数。
JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算1
2
3
4
5
6
7
80.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 内部实际的表示形式,精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示。1
2
3
4
5
6
7
8
9
10
11
12
13
14Math.pow(2, 53)
// 9007199254740992
Math.pow(2, 53) + 1
// 9007199254740992
Math.pow(2, 53) + 2
// 9007199254740994
Math.pow(2, 53) + 3
// 9007199254740996
Math.pow(2, 53) + 4
// 9007199254740996
JavaScript 对15位的十进制数都可以精确处理1
2
3
4
5
6Math.pow(2, 53)
// 9007199254740992
// 多出的三个有效数字,将无法保存
9007199254740992111
// 9007199254740992000
上面示例表明,大于2的53次方以后,多出来的有效数字(最后三位的111)都会无法保存,变成0。
数值范围
根据标准,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047
(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则 JavaScript 能够表示的数值范围为2^1024
到2^-1023
(开区间),超出这个范围的数无法表示。
如果一个数大于等于2
的1024
次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity
。1
Math.pow(2, 1024) // Infinity
如果一个数小于等于2
的-1075
次方(指数部分最小值-1023
,再加上小数部分的52
位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。1
Math.pow(2, -1075) // 0
下面是一个实际的例子。1
2
3
4
5
6
7var x = 0.5;
for(var i = 0; i < 25; i++) {
x = x * x;
}
x // 0
上面代码中,对0.5
连续做25
次平方,由于最后结果太接近0
,超出了可表示的范围,JavaScript 就直接将其转为0。
JavaScript 提供Number
对象的MAX_VALUE
和MIN_VALUE
属性,返回可以表示的具体的最大值和最小值。1
2Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
数值表示
除了一般表示法以外,也可以用科学记数法1
2123e3 // 123000
123e-3 // 0.123
科学计数法允许字母e
或E
的后面,跟着一个整数,表示这个数值的指数部分。
以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
- 小数点前的数字多于21位。
- 小数点后的零多于5个。
正零和负零
1 | -0 === +0 // true |
唯一有区别的场合是,+0
或-0
当作分母,返回的值是不相等的。1
(1 / +0) === (1 / -0) // false
infinity
NaN
与数值相关的全局方法
parseInt()
parseFloat()
isNaN()
isFinite()
字符串
字符
由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号
Base64
JavaScript 原生提供两个 Base64 相关的方法。
btoa():
任意值转为 Base64 编码
atob():
Base64 编码转为原来的值
字符集
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。
每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JavaScript 的单位字符长度固定为16位长度,即2个字节。
但是,UTF-16 有两种长度:对于码点在U+0000
到U+FFFF
之间的字符,长度为16位(即2个字节);对于码点在U+10000
到U+10FFFF
之间的字符,长度为32位(即4个字节),而且前两个字节在0xD800
到0xDBFF
之间,后两个字节在0xDC00
到0xDFFF
之间。举例来说,码点U+1D306
对应的字符为𝌆,它写成 UTF-16 就是0xD834 0xDF06
。
对于码点在U+10000
到U+10FFFF
之间的字符,JavaScript 总是认为它们是两个字符(length
属性为2)。所以处理的时候,必须把这一点考虑在内,也就是说,JavaScript 返回的字符串长度可能是不正确的。
对象
概述
简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
如果键名是数值,会被自动转为字符串。
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用1
2
3
4
5
6
7var obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
如果属性的值还是一个对象,就形成了链式引用。1
2
3
4
5var = {};
var 'hello' }; = { bar:
.foo = ;
// "hello" .foo.bar
属性可以动态创建,不必在对象声明时就指定。
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。1
2
3
4
5
6
7
8var = {};
var = ;
1; .a =
// 1 .a
2; .b =
// 2 .b
上面代码中,o1
和o2
指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。
此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。1
2
3
4
5var = {};
var = ;
1; =
// {}
上面代码中,o1
和o2
指向同一个对象,然后o1
的值变为1,这时不会对o2
产生影响,o2
还是指向原来的那个对象。
但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
如果行首是一个大括号,它到底是表达式还是语句?
为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。1
2({ foo: 123 }) // 正确
({ console.log(123) }) // 报错
属性操作
读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
查看一个对象本身的所有属性,可以使用Object.keys
方法。
delete
命令用于删除对象的属性,删除成功后返回true
。
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。1
2
3var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
可以使用对象的hasOwnProperty
方法判断一下,是否为对象自身的属性
with
语句(不推荐)
函数
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同1
2
3
4
5
6
7
8
9
10
11
12
13function add(x, y) {
return x + y;
}
// 将函数赋值给一个变量
var operator = add;
// 将函数作为参数和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2
如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。1
2
3
4
5
6
7
8
9var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
Author: damn1t
Link: http://microvorld.com/2019/10/28/JS挖坑/js(1)/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.