勇宝趣学JavaScript第一章(let和const)

哈喽呀!我是勇宝,过了个年有段时间没有更新了,真是怠慢了。大家新年都过得怎么样呀?最近也是在上线自己的网站,因为涉及到备案所以暂时还访问不了。不过域名已经申请好啦!

这个我的网站地址: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引用报错

所以还是那句话,一定要养成良好的编程习惯,变量一定要在声明之后进行使用。否则就会像上边一样进行报错。

小结

通过上边的一些案例,我们可以看出varlet的区别,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"}

大家注意我们这样只会冻结对象第一层,大家试想一下我们想要彻底冻结一个对象应该怎么做呢?这里给大家留一下小挑战:

编写一个彻底冻结对象的方法,评论区见?

相关推荐
吃杠碰小鸡15 分钟前
commitlint校验git提交信息
前端
虾球xz1 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇1 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪2 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背2 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript