入门篇
6 条件语句
注意,if
后面的表达式之中,不要混淆赋值表达式(=
)、相等运算符(==
)和严格相等运算符(===
)。尤其是赋值表达式不具有比较作用。
var x = 1;
var y = 2;
if (x = y) {
console.log(x);
}
// "2"
上面代码的原意是,当x
等于y
的时候,才执行相关语句。但是,不小心将严格相等运算符写成赋值表达式,结果变成了将y
赋值给变量x
,再判断变量x
的值(等于2)的布尔值(结果为true
)。
这种错误可以正常生成一个布尔值,因而不会报错。为了避免这种情况,有些开发者习惯将常量写在运算符的左边,这样的话,一旦不小心将相等运算符写成赋值运算符,就会报错,因为常量不能被赋值。
if(x = 2) {
// 不报错
if(2 = x) {
// 报错 Uncaught SyntaxError: Invalid left-hand side in assignment
const c = 2
if(c = x) {
//报错 Uncaught TypeError: Assignment to constant variable.
数据类型
概述
- 1.简介
原始(基础)类型(primitive type):
- 数值(number)
- 字符串(string)
- 布尔值(boolean)
- symbol
- null
- undefined
- bigInt
2020.6 新出了一个新的数据类型bigInt
8种数据类型, 7种原始类型和object
对象是最复杂的数据类型,又可以分成三个子类型。
- 狭义的对象(object)
- 数组(array)
- 函数(function)
狭义的对象和数组是两种不同的数据组合方式,除非特别声明,本教程的“对象”都特指狭义的对象。函数其实是处理数据的方法,JavaScript 把它当成一种数据类型,可以赋值给变量,这为编程带来了很大的灵活性,也为 JavaScript 的“函数式编程”奠定了基础。
- 2.typeof 运算符
JavaScript 有三种方法,可以确定一个值到底是什么类型。
typeof
运算符
instanceof
运算符
Object.prototype.toString
方法
利用这一点,typeof可以用来检查一个没有声明的变量,而不报错。
v
// ReferenceError: v is not defined
typeof v
// "undefined"
实际编程中,这个特点通常用在判断语句, 检查一个没有声明的变量。
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
对象返回object
typeof window // "object"
typeof {
} // "object"
typeof [] // "object"
instanceof
运算符可以区分数组和对象,
var o = {
};
var a = [];
o instanceof Array // false
a instanceof Array // true
不能区分函数和对象。
function a(){
}
undefined
a instanceof Object // true
a instanceof Function // true
a instanceof Array // false
null
返回object
。
typeof null // "object"
null
的类型是object
,这是由于历史原因造成的。1995年的 JavaScript
语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null
,只把它当作object
的一种特殊值。后来null
独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null
返回object
就没法改变了。
null, undefined 和布尔值
1. null 和 undefined
老实说, null, undefined语法效果几乎没区别。
为什么JS有null和undefined?
这与历史原因有关。1995年 JavaScript 诞生时,最初像 Java 一样,只设置了null
表示"无"。根据 C 语言的传统,null
可以自动转为0。
Number(null) // 0
5 + null // 5
typeof(5 + null) // number
但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。
原因:
首先,第一版的 JavaScript 里面,null
就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示“无”的值最好不是对象。
其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果null自动转为0,很不容易发现错误。
因此,他又设计了一个undefined
。区别是这样的:null
是一个表示“空”的对象,转为数值时为0;undefined
是一个表示"此处无定义"的原始值,转为数值时为NaN
。
Number(undefined) // NaN
5 + undefined // NaN
typeof(5 + undefined) // NaN
typeof(NaN) // number
@@@
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
2. 布尔值
布尔: 纪念伟大的数学家布尔来命名的.
注意大小写: true, false
6个falsy值:
- false
- null
- undefined
- 0
- NaN
- ''或""(空字符串)
注意,空数组([]
)和空对象({}
)对应的布尔值,都是true
。
if ([]) {
console.log('true');
}
// true
if ({
}) {
console.log('true');
}
// true
参考原因:
这是因为 JavaScript 语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于obj1 && obj2
这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为true
。
数值
1 概述
- 1.1 整数和浮点数
JavaScript 内部,所有数字都是以64位浮点数形式储存(国际标准 IEEE 754),即使整数也是如此。所以,1
与1.0
是相同的,是同一个数。
1 === 1.0 // true
容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算,参见《运算符》一章的“位运算”部分。
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
- 1.2 数值精度
精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即 − 2 53 -2^{53} −253 到 2 53 2^{53} 253 ,都可以精确表示。
Math.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
上面代码中,大于2的53次方以后,整数运算的结果开始出现错误。所以,大于2的53次方的数值(小于-2的53次方的数值),都无法保持精度。由于2的53次方是一个16位的十进制数值,所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理。
- 1.3 数值的范围
@@@
为啥正向溢出是Math.pow(2, 1024) // Infinity
, 负向溢出是Math.pow(2, -1075) // 0
?
@@没搞定
2 数值的表示法
以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
- 小数点前的数字多于21位。
1234567890123456789012 // 1.2345678901234568e+21
123456789012345678901 // 123456789012345680000 (大于9...992 16位)
- 小数点后的零多于5个。
// 小数点后紧跟5个以上的零,
// 就自动转为科学计数法
0.0000003 // 3e-7
// 否则,就保持原来的字面形式
0.000003 // 0.000003
3 数值的进制
HEX(Hexadecimal )十六进制。
DEC(Decimal) 十进制。
OCT(Octal) 八进制。
BIN(Binary) ,二进制。
八进制:有前缀0o
或0O
的数值,或者有前导0
、且只用到0-7
的八个阿拉伯数字的数值。
JavaScript 不再允许将带有前缀0
的数字视为八进制数,而是要求忽略这个0
。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。
4 特殊数值
- 0
-0 === +0 // true
0 === -0 // true
0 === +0 // true
+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'
自测: -0 //-0
唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的。
(1 / +0) === (1 / -0) // false
//等价
+Infinity === -Infinity // false
- NaN(Not a Number)
主要出现在将字符串解析成数字出错的场合。
5 - 'x' // NaN
5 - '1' // 4
一些数学函数的运算结果会出现NaN。
Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
0除以0也会得到NaN。
0 / 0 // NaN
需要注意的是,NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。
typeof NaN // 'number'
讲故事: 原始社会打到猎物了, 有很多不知道怎么表示NaN. 然后就发明了正整数这样就可以描述今天打了1只猎物还是2只3只. 然后突然有一天没有打到猎物. 然后发现这不符合正整数. 然后不知道用啥表示, 那就先叫NaN吧. 后面商讨后决定用0表示没有.随着社会发展, 出现了1-5这种情况, 然后目前数学知识解决不了那就先叫NaN吧. 数学突破之后, 然后定义这个为负整数.
过了很久遇到了1/2 这种情况以前也没见过,先叫NaN,数学突破之后那规定就叫分数吧,或另一种表示形式0.5(有理数), 随着社会发展后面出现了圆的周长是直径的3.14159…倍先用NaN表示, 数学突破了规定交无理数.
1的平方=1 2的平方=4
然后什么的平方等于-1虚数(复数)
NaN不等于任何值,包括它本身(一个我不知道是啥的数, 和另一个我不知道是啥的数, 怎么可能相等呢?)。
NaN === NaN // false
数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。
[1, NaN, 2].indexOf(NaN) // -1
NaN与任何数(包括它自己)的运算,得到的都是NaN。
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
- Infinity
Math.pow(2, 1024) // Infinity
Infinity
大于一切数值(除了NaN
),-Infinity
小于一切数值(除了NaN
)。
Infinity与NaN比较,总是返回false。
Infinity > NaN // false
-Infinity > NaN // false
Infinity < NaN // false
-Infinity < NaN // false
0
乘以Infinity
,返回NaN
0
除以Infinity
,返回0
Infinity
除以0
,返回Infinity
0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity
Infinity
加上或乘以Infinity
,返回的还是Infinity
。Infinity
减去或除以Infinity
,得到NaN
。
Infinity
与null
计算时,null
会转成0
,等同于与0
的计算。
Infinity
与undefined
计算,返回的都是NaN
。
5 与数值相关的全局方法
parseInt
方法用于将字符串转为整数。
但是,isNaN
只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN
,所以最后返回true
,这一点要特别引起注意。也就是说,isNaN
为true
的值,有可能不是NaN
,而是一个字符串。出于同样的原因,对于对象和数组,isNaN
也返回true
。
判断NaN更可靠的方法是,利用
NaN`为唯一不等于自身的值的这个特点,进行判断。
function myIsNaN(value) {
return value !== value;
}
isFinite
方法返回一个布尔值,表示某个值是否为正常的数值。除了Infinity
、-Infinity
、NaN
和undefined
这几个值会返回false
,isFinite
对于其他的数值都会返回true
。
字符串
1. 概述
- 1.1定义
由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号,本教程遵守这个约定。
如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。
var longString = 'Long \
long \
long \
string';
longString
// "Long long long string"
上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行书写。但是,输出的时候还是单行,效果与写在同一行完全一样。注意,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。
如果想输出多行字符串,有一种利用多行注释的变通方法。
(function () {
/*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"
- 1.2 转义
(1)\HHH
反斜杠后面紧跟三个八进制数(000
到377
),代表一个字符。HHH对应该字符的 Unicode 码点,比如\251
表示版权符号。显然,这种方法只能输出256种字符。
(2)\xHH
\x
后面紧跟两个十六进制数(00
到FF
),代表一个字符。HH对应该字符的 Unicode 码点,比如\xA9
表示版权符号。这种方法也只能输出256种字符。
(3)\uXXXX
\u
后面紧跟四个十六进制数(0000
到FFFF
),代表一个字符。XXXX
对应该字符的 Unicode 码点,比如\u00A9
表示版权符号。
'\251' // "©"
'\xA9' // "©"
'\u00A9' // "©"
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
- 1.3 字符串与数组
如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined。
'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined
但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。
var s = 'hello';
delete s[0];
s // "hello"
s[1] = 'a';
s // "hello"
s[5] = '!';
s // "hello"
2. 字符集
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。
Unicode可以叫: 统一码, 万国码.
JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符
var s = '\u00A9';
s // "©"
var f\u006F\u006F = 'abc';
foo // "abc"
为什么一般都用4位16进制?
首先2个字节(16位), 用二进制写起来太慢了.
011 1100 0101 1010
的16进制3C5A
3. Base64 转码
有时,文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。
所谓 Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+
和/
这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。
JavaScript 原生提供两个 Base64 相关的方法:
btoa()
:任意值转为 Base64 编码
atob()
:Base64 编码转为原来的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
注意,这两个方法不适合非 ASCII 码的字符,会报错。
btoa('你好') // 报错
要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
对象
1 概述
- 1.2 键名
对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以(如果键名不符合标识名的条件, 就需要加引号)。
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。
对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。
- 1.4 表达式还是语句?
对象采用大括号表示,这导致了一个问题:如果行首是一个大括号,它到底是表达式还是语句?
{
foo: 123 }
JavaScript 引擎读到上面这行代码,会发现可能有两种含义。第一种可能是,这是一个表达式,表示一个包含foo
属性的对象;第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo
,指向表达式123
。
疑问: chrome直接输出{ foo: 123 }
, 是一个对象.
为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
{
console.log(123) } // 123
上面的语句是一个代码块,而且只有解释为代码块,才能执行。
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
({
foo: 123 }) // 正确
({
console.log(123) }) // 报错
这种差异在eval语句(作用是对字符串求值)中反映得最明显。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
2 属性的操作
- 2.1 属性的读取
读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理(数字键可以不加引号,因为会自动转成字符串)。
方括号运算符内部还可以使用表达式。
注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。eg: obj.123
- 2.3 属性的查看
查看一个对象本身的所有属性,可以使用Object.keys
方法。
- 2.4 属性的删除:delete 命令
delete
命令用于删除对象的属性,删除成功后返回true
。
var obj = {
p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
注意,删除一个不存在的属性,delete不报错,而且返回true。因此,不能根据delete命令的结果,认定某个属性是存在的。
var obj = {
};
delete obj.p // true
只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。
var obj = Object.defineProperty({
}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
上面代码之中,对象obj
的p
属性是不能删除的,所以delete
命令返回false
(关于Object.defineProperty
方法的介绍,请看《标准库》的 Object 对象一章)。
另外,需要注意的是,delete命令只能删除对象本身的属性,无法删除继承的属性(关于继承参见《面向对象编程》章节)。
var obj = {
};
delete obj.toString // true
obj.toString // function toString() { [native code] }
- 2.5 属性是否存在:in 运算符
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值)
in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。
var obj = {
p: 1 };
'p' in obj // true
'toString' in obj // true
可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性。
var obj = {
};