哈喽呀!我是
勇宝,过了个年有段时间没有更新了,真是怠慢了。大家新年都过得怎么样呀?最近也是在上线自己的网站,因为涉及到备案所以暂时还访问不了。不过域名已经申请好啦!这个我的网站地址:www.iyongbao.cn,花了勇宝
很多钱。如果大家感兴趣的话,以后会教给大家怎么
部署自己的网站。

一、前言
新的一年就要有新的气象,过年这段时间我有在想,过去一年自己学到了什么,无外乎就是前端的一些框架(Vue、React)。当和别人谈论起技术来,感觉是力不从心,首先就是对底层不够了解,也试着去看源码,但是说实在的,我还真没到那个水平,对于那'又臭又长'的源码,勇宝无从下手。总结到最后认定是基础不够牢固,隧从现在开始,打算出一个系列文章。重拾JavaScript基础。
二、let命令
说起let,大家都不陌生,它和const一样,都是ES6新增的语法。我认为let解决的最关键的问题,就是让我们写代码更像Java了,让我们渐渐的有了块级作用域的概念。
2.1 基本用法
我先给大家上两道例子,大家可以试着运行运行。
js
{
var a = 1;
}
console.log(a); // 输出 1
js
{
let a = 1;
}
console.log(a); // ReferenceError: a is not defined
我来给大家解释一下,为什么会是这种结果,我们先来说var,这个var只有两种作用域,一种是全局作用域,另一种是函数作用域,后面我们会讲到。var没有块级作用域的概念。所有我们在{}之间通过var声明的变量,最终会挂载到全局也就是我们的window上。
let就很好理解了,块级作用域内声明的变量不会泄露到全局,也就是let声明的变量只在它所属的代码块中起作用。
2.2 变量提升
如果我们在var声明变量前使用,会存在变量提升。
js
console.log(a);
var a = 1;
此时,程序并不会报错,打印结果为undefined。这就是变量提升,在实际开发中,var会给我们带来很多麻烦。所以要养成良好的编程习惯,变量应该在声明语句之后在去使用。
同理如果把var改为let,结果就大不相同(引用错误)。
js
console.log(b); // ReferenceError: Cannot access 'a' before initialization
let b = 2;
2.3 暂时性死区(TDZ)
首先给大家说一下概念:当一个块级作用域中存在let声明变量,在let声明之前去使用都不不可以的,会报错,这种情况在语法上称之为暂时性死区(TDZ:temporal dead zone)。
js
// 这是一个代码块
{
// TDZ开始
iyongbao = '爱勇宝';
console.log(iyongbao); // ReferenceError
let iyongbao; // TDZ结束
console.log(iyongbao); // undefined
}
从案例可以看出,在let声明之前,都属于暂时性死区,我们进行console.log会引用报错。
所以还是那句话,一定要养成良好的编程习惯,变量一定要在声明之后进行使用。否则就会像上边一样进行报错。
小结
通过上边的一些案例,我们可以看出var和let的区别,var不会进行报错,但是从实际的角度来说,会增大我们后期的维护成本。
2.4 不能重复声明
在同一个代码块中,不能多次使用let声明同一个变量。
js
{
// 报错
let a = 2;
let a = 3; // SyntaxError: Identifier 'a' has already been declared
// 报错
let a = 2;
var a = 3; // SyntaxError: Identifier 'a' has already been declared
// 报错
var a = 2;
let a = 3; // SyntaxError: Identifier 'a' has already been declared
}
注意当我们定义函数的时候,有时也会不经意间进行报错。
js
// 报错
function foo (str) {
let str = 'iyongbao'; // SyntaxError: Identifier 'str' has already been declared
}
// 不报错
function foo (str) {
{
let str = 'iyongbao';
}
}
不报错的原因就是,函数内部存在一个代码块。
三、块级作用域
在我们学习ES5的时候,没有块级作用域的概念。它只有全局作用域和函数作用域。这其实导致在写代码的过程中很不方便,但是Java开发人员,就对块级作用域概念很强。
3.1 变量泄露和提升
请看第一个案例:
js
var arr = []
for (var i = 0; i < 5; i ++) {
arr[i] = function () {
console.log(i)
}
}
console.log(i); // 5
在上边的代码中,我们只是想i在循环体来使用,但是在循环结束的时候,我们发现它并没有被销毁,并且是暴露到了全局作用域。
请看第二个案例:
js
var a = 2
function foo () {
console.log(a);
if (false) {
var a = 3;
}
}
foo() // 2
我们可能预想的是打印2,其实打印的结果为undefined。这就是变量提升,可能有时候不明显,大家要多多留意。
当然函数声明 也是存在提升的
js
if (false) {
function foo () {
console.log('hello world');
}
}
foo() // TypeError: foo is not a function
如果我们单纯的去访问foo变量会报错ReferenceError: foo is not defined。但是我们发现打印的是foo is not a function所以说函数声明也是存在变量提升。
JavaScript实际编译如下:
js
var foo = undefined;
if (false) {
function foo () {
console.log('hello world');
}
}
foo() // TypeError: foo is not a function
我们尽量不要在块级作用域中声明函数。如果一定要写函数也尽量使用let的函数表达式形式声明函数。
3.2 ES6代码块
在ES6中,我们推荐使用let,因为let实际上是给我们的JavaScript新增加了块级作用域。
js
function foo () {
let a = 2;
if (true) {
let a = 5;
}
console.log(a);
}
foo() // 2
我来解析一下上边的代码结果,注意这里我使用的是let声明。因为let具有块级作用域,这里有两个代码块(foo函数和if)所以外层的变量不会受到if里层变量的影响。所有打印的结果为2。
但是如果我们使用的是var声明,结果就为5,因为var使用函数作用域和全局作用域。所以此时foo函数作用域中的a为5,所以打印结果显而易见是5。大家可以自行试一下。
四、const命令
4.1 基本用法
const一般用来声明一个常量,并且声明的常量必须有值,并且这个常量不能再次进行赋值。一般通过const声明的常量,我们习惯用大写。
js
const NAME = 'iyongbao';
NAME = 'zhangsan'; // TypeError: Assignment to constant variable.
// 只进行声明,不进行赋值会报错
const STATUS; // SyntaxError: Missing initializer in const declaration
const的作用域与我们前面提到的let命令相同:只在声明所在的块级作用域内有效。
js
if (true) {
const NAME = 'iyongbao';
}
console.log(NAME); // ReferenceError: NAME is not defined
const声明的常量,也不会存在变量提升,会有暂时性死区,所以我们要格外注意先声明在使用。
js
if (true) {
console.log(NAME); // ReferenceError: Cannot access 'NAME' before initialization
const NAME = 'iyongbao';
}
最后是const声明的常量,和let一样,不能重复声明。
js
const NAME = 'iyongbao';
const NAME = 'zhangsan'; // SyntaxError: Identifier 'NAME' has already been declared
4.2 原理分析
其实const声明的常量,不能改变的其实是标识符指向的内存地址的值。像我们案例中使用到的都是基本数据类型(字符串、布尔值、number),值就保存在变量指向的那个内存地址,因此等同于常量。但如果是引用数据类型就不一样了,此时const只能保证指向的内存地址是不可以重新赋值。但是内存地址所指向的堆中的值是可以改变的,这点我们要格外注意一下。
js
const obj = {
name: 'iyongbao'
}
obj.name = 'zhangsan'; // 这是可以的
console.log(obj); // {name: "zhangsan"}
obj = {
name: 'zhangsan' // 报错
}
console.log(obj); // TypeError: Assignment to constant variable.
类似的还有数组的操作:
js
const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]
arr[2] = 5;
console.log(arr); // [1, 2, 5, 4]
arr = [7, 8, 9]; // TypeError: Assignment to constant variable.
这里再给大家补充一下对象冻结Object.freeze方法。这个方法就是只读,不能写入,即使执行写入操作也是不生效的。
js
const obj = {
name: 'iyongbao'
}
Object.freeze(obj);
obj.name = 'zhangsan';
console.log(obj); // {name: "iyongbao"}
上边的代码,如果是在严格模式下会报错。
js
const obj = Object.freeze({
name: 'iyongbao'
})
obj.name = 'zhangsan';
console.log(obj); // {name: "iyongbao"}
大家注意我们这样只会冻结对象第一层,大家试想一下我们想要彻底冻结一个对象应该怎么做呢?这里给大家留一下小挑战:
编写一个彻底冻结对象的方法,评论区见?