红宝书第三章部分笔记
1 var、let、const的区别
let和const都是ES6引入的,下面是它们和var的区别。
- 使用
var声明的变量只有全局作用域和函数作用域,使用let和const声明的变量存在局部作用域。 - 在全局作用域中使用
var声明的变量会保存在window对象中,使用let和const声明的变量不会。 - 使用
var声明的变量具有变量提升,let和const没有。因此let和const必须先声明后使用(在let和const声明之前使用变量的这个瞬间称作暂时性死区,这时会抛出ReferenceError),var声明变量可以在使用变量之后。 - 同名变量可以使用
var声明多次,在变量提升后这些声明会合并。由于let和const没有变量提升,因此无法判断此前是否声明过,所以在同一作用域使用let和const重复声明变量会报错。
此外,在for循环中使用var和let会有不同的行为。
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 0);
}
// 5, 5, 5, 5, 5
for (let j = 0; j < 5; j++) {
setTimeout(() => console.log(j), 0);
}
// 0, 1, 2, 3, 4
出现这种情况的原因是var声明的变量是存在于全局的,而setTimeout是异步调用的,会在for循环结束后调用,此时i的值已经是5了,所以打印了5个5。而对于let来说,由于for循环的圆括号是一个局部作用域,每次循环时JavaScript引擎会在后台声明一个新的j,因此每次setTimeout触发时都是在不同的作用域,引用的是不同的j。为了证明是在不同作用域调用setTimeout,且声明了不同的j,我们可以将代码改造成这样:
for (let j = 0; j < 5; j++) {
let k = j;
setTimeout(() => console.log(k), 0);
}
// 0, 1, 2, 3, 4
上面代码输出同样的结果。这说明每次使用let声明k的时候都是在不同的作用域,否则根据let不能同一作用域重复声明变量的规定,就会报错。并且,我们可以推断JavaScript引擎会在后台记录下每次循环时j的值,在下次进入循环体前声明新的j,调用j++做完判断后再进入循环体。下面的代码打印不同的结果。
for (let j = 0; j < 5;) {
setTimeout(() => console.log(j), 0);
j++;
}
// 1, 2, 3, 4, 5
这说明圆括号里面的j++和上一次循环体是属于不同的作用域的。
const和let唯一的区别在于const必须在声明的同时初始化,否则会报错。因为const只能赋值一次。所以上面的代码如果改成const会报错(j++会改变j的值)。
由于let和const存在块级作用域,因此下面的做法是无意义的。
if (typeof num === undefined) {
let num; // 本意是想确保 num 被声明后再赋值,但是由于块级作用域的存在使得 num 是一个局部变量
}
num = 3; // 报错
2 数据类型
JavaScript的数据类型分为基本数据类型和复杂数据类型。基本数据类型共有6种,分别是Number、String、Boolean、Null、Undefined和Symbol(ES6新增)。复杂数据类型则指的对象(Object)。
2.1 基本数据类型
1、Null和Undefined
null表示空指针,undefined表示未定义。在ES3时只有null而没有undefined,undefined保存在window对象中。
null == undefined; // true
typeof null === "object"; // true
typeof undefined === "undefined"; // true
所有声明但未赋值的变量都有一个默认值为undefined,因此不必特意赋值为undefined。此外,由于这一点也无法使用typeof来判断一个变量是否被声明。
typeof num === "undefined"; // num 未声明,typeof也返回 undefined
因此,应该尽可能保证在赋值的时候初始化,这样可以使用typeof判断变量是否被声明。初始化时,首先使用const,如果确认值需要改变再使用let,这样有利于纠错,防止不知不觉间改变了变量的值。如果确认变量的类型是对象,应该将初始值赋值为null。
2、Boolean
Boolean只有两个值:true和false。在一些判断语句中,会将表达式自动转化成Boolean类型。比如if语句。除了下面这些转化为false,其余都为true。
- null
- undefined
- false
- 0
- 空字符串
- NaN
也可以使用Boolean强行转化其他类型的值:
Boolean('123'); // true
Boolean(NaN); // false
3、Number类型
Number常见的进制是十进制、二进制、八进制和十六进制。默认都是十进制,如果有特定前缀则可能为其余进制,如下:
123 // 十进制
0b0101 // 二进制
0o0037 // 八进制
0x0012 // 十六进制
这里面需要注意的是八进制,在浏览器中,如果开头为0,分为两种情况:
- 如果后面的数字中有大于7的数,则表示十进制
- 如果后面的数字中没有大于7的数,则为八进制
如下:
034 // 八进制,在 chrome 控制台显示为十进制 28
078 // 十进制 78
注:上述写法只对整数有效,如果是浮点数则会报错。如 00.89会报错。
为了使得表达更明确,我们应该始终避免用047这样的表达方式,而是使用0o47。
还有一点需要注意的是,为了节省内存,JavaScript会尽可能的省略掉无意义的0。如下:
00000089 // 显示为 89,前面的 0 被省略
89.000 // 显示为 89, 后面的 0 被省略,内部当做整数保存
89.01000 // 显示为 89.01
JavaScript内部所有数值都使用浮点数保存。在进行运算的时候再尝试转为整型。由于JavaScript使用IEEE754浮点数标准,因此会有精度问题:
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
JavaScript保存的数值的范围在Number.MIN_VALUE和Number.MAX_VALUE之间。如果超出Number.MAX_VALUE则显示为Infinity,最小值精度超出Number.MIN_VALUE则显示为0。
Number.MIN_VALUE // 5e-324
5e-325 // 0
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_VALUE * 2 // Infinity
-Number.MAX_VALUE * 2 // -Infinity
JavaScript最高精度为17位,超出部分会四舍五入。
0.12345678901234568 // 0.12345678901234568
0.123456789012345688 // 0.12345678901234569
0.123456789012345684 // 0.12345678901234568
0.123456789012345685 // 0.12345678901234569
1.12345678901234568 // 1.1234567890123457
123456789012345678 // 123456789012345680
我们可以使用Number()来强行将其他类型的值转化为Number类型。规则如下:
- Number类型的直接返回拷贝值(无意义的0会被忽略)
- Boolean类型的,true返回1,false返回0
- null返回0
- undefined返回NaN
- 字符串类型的,如果是空字符串则返回0,如果是符合数值格式的,则返回对应的十进制值,并忽略掉无意义的0(开头可以有正负号)。其余情况返回 NaN。
Number(23); // 23
Number(000023); // 19 八进制
Number(00099); // 99
Number(00098.8900); // 98.89
Number(true); // 1
Number(false); // 0
Number('0x0012'); // 18
Number('0x0012x'); // NaN
Number(''); // 0
- 如果是对象,则先调用对象的
valueOf()方法,对返回值进行转化,如果无法转化为数值,再调用toString()方法,对返回值进行转化。
parseInt和parseFloat用于将字符串转化为数值。它们按照一个字符一个字符进行判断。并且会忽略掉无意义的0.
parseInt接受两个参数,第一个为需要转化的字符串,第二个为进制。默认为十进制。规则如下:
- 如果第一个字符为非数字且非正号、非负号,则直接返回NaN
- 空字符串返回NaN
- 如果第一个字符为数字或正号、负号,则依次判断之后的字符是否为数字,直到碰到第一个非数字字符或者碰到字符串结尾,结束判断并将结果返回
- 如果字符串符合进制,如
0x12则当做对应进制转化为十进制。可以传第二个参数显式的指定进制。如果第一个参数不符合第二个参数指定的进制,则返回NaN
parseInt('a'); // NaN
parseInt('-9'); // -9
parseInt('+34'); // 34
parseInt('0098'); // 98
parseInt('23abc'); // 23
parseInt('0x0011'); // 17
parseInt('a', 16); // 10
parseFloat只存在十进制,和parseInt一样,依次判断每个字符。第一个字符可以为数字,正负号和小数点。第二个字符开始只能为数字和小数点,并且小数点只能出现一次。
parseFloat('8.3float'); // 8.3
parseFloat('.4'); // 0.4
parseFloat('0.44.5'); // 0.44
parseFloat('10.00'); // 10
parseFloat('00234.0'); // 234
这里有一道经典面试题
["1", "2", "3"].map(parseInt); // [1, NaN, NaN]
["1", "2", "3"].map(parseFloat); // [1, 2, 3]
这块考察3个知识点,一个是map的传参,另两个是关于parseInt和parseFloat。首先,map需要传入一个回调函数,这个函数可以接收三个参数,第一个参数是数组每一项的值,第二个参数为数组每项下标,第三个参数是数组本身。因此上面两个题可以简化为:
parseInt("1", 0); // 1
parseInt("2", 1); // NaN
parseInt("3", 2); // NaN
parseFloat("1"); // 1
parseFloat("2"); // 2
parseFloat("3"); // 3
4、String类型
在JavaScript中,可以有三种方式表示一个字符串。分别是"、'、`。几种表达方式没有太大区别。
'abc' === "abc"; // true
'abc' === `abc`; // true
"abc" === `abc`; // true
这几种引号可以同时使用。
"'abc'"; // 'abc'
'"abc"'; // "abc"
`'abc'`; // 'abc'
`"abc"`; // "abc"
但是以什么引号开头必须以相同类型引号结尾。
"abc'; // 报错
"abc`; // 报错
如果在引号里面需要使用同一类型的引号需要使用\转义。
"\"abc"; // "abc
'\'abc'; // 'abc
`\`abc`; // `abc
将其余类型转化为字符型主要有三种方法,分别是toString、String和使用+号。
// 除了 null 和 undefined 以外都可以用 toString,toString方法是从 Object 上继承过来的
/*
* 1. 字符类型使用 toString 直接返回自身拷贝
* 2. 数值、布尔值使用 toString 则先自动转为基本包装类型,再调用 toString
* 3. 对象调用 toString 时,如果没有重写 toString 方法,则普通对象返回 [object Object],数组则将每个元素使用toString方法转成字符串,再使用 , 连接成最后的字符串。其余类型各自使用自己内部重写的 toString 方法。
*/
// 使用 String() 转字符串时,规则如下
/*
* 1. 如果是 null 或者 undefined,返回 'null' 或 'undefined'
* 2. 其余类型调用 toString 方法
*/
// 使用 + 号
/*
* 只要加号两边有一个是字符串,则会对另一个使用 String() 转成字符串,再进行拼接
*/
注:toString()里面还可以传入一个数字,用来表示进制。可以使用这个方法将数字转化为特定的进制。如果不传则默认转为十进制。
2.2 模板字符串
模板字符串可以插入变量
let str = '你好';
console.log(`${str}, 世界!`); // 你好, 世界!
模板字符串会保留空格和换行符
`<table>
<thead>
<tr>
<td>单元格</td>
</tr>
</thead>
</table>`
// 输出
/*
<table>
<thead>
<tr>
<td>单元格</td>
</tr>
</thead>
</table>
*/
字符串里面的\n之类的转义字符如果需要原样打印,除了可以使用/再次转义外还可以使用String.raw
`first line\nlast line`;
// 输出
/*
first line
last line
*/
String.raw`first line\nlast line`;
//输出
// first line\nlast line
模板字符串还可以使用标签函数。
function fn(strings, ...args) {
console.log(strings, args);
}
let str1 = "str1",
str2 = "str2",
str3 = "str3";
fn`字符串1${str1}字符串2${str2}字符串3${str3}`;
// ["字符串1", "字符串2", "字符串3", "", raw: Array(4)] ["str1", "str2", "str3"]
1149

被折叠的 条评论
为什么被折叠?



