哈喽呀!我是
勇宝
,过了个年有段时间
没有更新了,真是怠慢
了。大家新年都过得怎么样呀?最近也是在上线
自己的网站,因为涉及到备案
所以暂时还访问不了。不过域名
已经申请好啦!这个我的网站地址: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"}
大家注意我们这样只会
冻结对象第一层
,大家试想一下我们想要彻底冻结一个对象应该怎么做呢?这里给大家留一下小挑战:
编写一个彻底冻结对象的方法,评论区见?