勇宝趣学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"}

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

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

相关推荐
2分钟速写快排16 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu5 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端