ES6初识和ES6变量
ES6 简介
ECMAScript 是语言的标准;6 是版本号;ES6 = ECMAScript 这门语言的第 6 代标准
什么是ECMAScript
是欧洲计算机制造商协会,一个标准化组织,ECMAScript 是 由 ECMA 这个标准化组织制定的一个语言标准
具体是什么
ECMAScript规定了语法和API,ECMAScript = 语法 + API
历史版本
- ES1 ~ 3、ES5 ~ 6
- ES4 被废弃了(太激进)
- ES3:do while、switch、正则表达式等
- ES5:forEach、map、filter、Object.create、Object.defineProperty 等
命名方式
- ES6->ES7->ES8->… = ES6+
- ES2015->ES2016->ES2017->…
ES 与 JavaScript 的关系
- JavaScript(浏览器端) = ECMAScript(语法+API) + DOM + BOM
- 主流浏览器的最新版本几乎全部支持 ES6
- IE 老版本等不支持的浏览器,可以用 Babel 转码
ES6新增变量let 和 const
let 和 const 是什么
let用来声明可变变量。代替varconst用来声明常量(不可重新赋值的变量)。
let 和 const 的用法
let基本用法
let name = "Alice";name = "Bob"; // 可以重新赋值const基本用法
const PI = 3.14;什么是变量,什么是常量
- var、let声明的就是变量,变量一旦初始化之后,还可以重新赋值
- const声明的就是常量,一旦初始化,就不能重新赋值
const
为什么需要 const
避免变量被误修改(防止 bug);如果只用 let / var,变量很容易被不小心改掉:
let PI = 3.14;PI = 100; // ❌ 可能是误操作,但不会报错
const PI = 3.14;PI = 100; // ❌ 直接报错,防止 bug让代码语义更清晰(可读性更强);看到 const,别人就能立刻理解:这个值不会变
const API_URL = "https://api.example.com";const MAX_COUNT = 10;//相比letlet API_URL = "..."; // 不确定会不会变降低维护成本(大型项目很重要);在大项目中:一个变量被多处引用,如果可以随意修改,很容易出错
const config = {...};// 防止重新赋值
let config = {...};// 多个地方可能修改 config符合函数式编程思想;现代前端(React / Vue)倾向:数据尽量不可变(Immutable)
//不直接修改原数据,更安全const list = [1, 2, 3];const newList = [...list, 4];提升性能优化空间:虽然 JS 引擎会优化很多情况,但不可变数据更容易优化,更少副作用
关键误区
const ≠ “完全不可变”;const 只限制:不能重新赋值(引用不能变)
const obj = { name: "Tom" };obj.name = "Jerry"; // ✔ 可以const 的注意事项
- 一旦声明,就必须立即初始化,不能留到后面复制;
- 使用const声明的常量允许在不重新赋值的情况下修改他的值(关键误区)
什么时候用let,什么时候需要使用const
let和const的选择核心原则其实很简单:默认用 const,需要“重新赋值”时才用 let- 不会重新赋值的变量用const;引用类型(对象/数组)通常用 const,因为可以修改对象内容,不能整体重新赋值;配置、常量、固定数据用const; 函数 / 导入模块用const
- 什么时候用 let(必须会变才用):值会变化的变量;循环变量;需要重新赋值的状态;条件赋值
let、const 与 var 的区别
重复声明
var可以重复声明; let、const不允许重复声明
var a = 1;var a = 2; // ✔ 不报错;容易出 bugconsole.log(a); // 2
let a = 1;let a = 2; // ❌ 报错
const a = 1;const a = 2; // ❌ 报错
function fun(a){ let a = 1;//❌ 报错}变量提升
var存在变量提升,但值是 undefined;
console.log(a); // undefinedvar a = 10;//等价于var a;console.log(a);a = 10;let / const:也会提升,但不能访问;暂时性死区
console.log(a); // ❌ 报错let a = 10;console.log(b); // ❌ 报错const b = 20;这里的a已经被提升,但是未被初始化,此时处于暂时性死区,变量已经存在,但不能访问它的值,访问这些变量会导致ReferenceError错误
变量的声明周期:声明(在作用域内声明这个变量,此时变量已被标记为存在。)——- 初始化(在内存中为这个变量开辟一块空间,此时由于没有赋值,所以会初始化为undefined。) ——— 赋值(将值赋给这个变量。)
var: 在执行上下文执行之前的预解析阶段,会有声明提升,和初始化提升,这时js会把var存放在变量环境中(你可以简单理解为一个存放变量的地方,函数声明也会放在这个地方),且值为undefined,因此我们可以提前访问,结果为undefined。
let: 在执行上下文执行之前的预解析阶段,仅有声明提升,不会给它初始化,更不会给他赋值,此时存储中有这个变量,但我们无法访问。(let和const存放在词法环境中,你可以简单理解为另一个地方)这也就是所说的暂时性死区当执行到真正的赋值语句的时候,才会执行初始化操作并赋值。
const: 与let的创建顺序基本一致,仅有 声明提升,但非常重要的区别是const本身不存在赋值这一个概念,当执行到
const a = 2时,是直接将const初始化为2。也就是我们所说的const是常量。因为它根本无法赋值,也因此在声明时必须给它一个值。(相当于Promise内部状态只能修改一次,因为Promise源码里后续也没有为状态赋值的操作。) 而对于引用数据类型来说,const初始化的是一个栈内存里分配的指针。你虽然无法修改指针的指向,但可以修改指针指向的堆里面的内容,也就是我们所说的cosnt声明的引用数据类型可以更改。作者:孙泓旻 链接:https://juejin.cn/post/7209939789808812093 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
暂时性死区
只要作用域内存在let、const,它们所声明的变量或常量就自动”绑定”这个区域,不再受到外部作用域的影响
let a = 2;let b = 4;function fun() { //console.log(a);//应为函数作用域绑定了a,所以没法往外找,报错 console.log(b); //相反,函数作用域内没找到b,就往外找 //let a = 1;}fun();window 对象的属性和方法(全局作用域中)
var会挂到 window 上,全局污染问题
var a = 10;console.log(window.a); // 10let / const:不会挂到 window, 更安全,不污染全局对象
let a = 10;const b = 20;
console.log(window.a); // undefinedconsole.log(window.b); // undefined块级作用域
var没有块级作用域(只有函数作用域)
if (true) { var a = 10;}console.log(a); // 10,if / for 里的变量“泄露”到外面
for (var i = 0; i < 3; i++) { console.log(i);}console.log(i); //3let / const:有块级作用域
if (true) { let a = 10; const b = 20;}console.log(a); // ❌ 报错console.log(b); // ❌ 报错
for (let a = 0; a < 3; a++) { console.log(a);}console.log(a); //❌ 报错作用域链
let a = 1;function foo() { let b = 2; function bar() { let c = 3; console.log(a); // ?1 console.log(b); // ?2 console.log(c); // ?3 } bar();}
foo();查找过程(关键)
1️⃣ 找 c
在 bar 内部 → 直接找到
2️⃣ 找 b
bar 没有 → 去上一层 foo → 找到
3️⃣ 找 a
bar 没有 → foo 没有 → 去全局 → 找到
总结:函数定义时的作用域嵌套关系,而不是调用时决定的,访问变量时:先在当前作用域找,找不到 → 去上一级作用域,一直找到全局作用域,还找不到 → 报错 ReferenceError
常见误区:
- 作用域链是运行时决定的;错!作用域链在函数定义时就已经确定
- 跟 this 是一回事;完全不同:作用域链作用是找变量;this作用指向对象
总结
| 特性 | var | let | const |
|---|---|---|---|
| 重复声明 | ✔ 可以 | ❌ 不可以 | ❌ 不可以 |
| 变量提升 | ✔ 有(undefined) | ✔ 有(TDZ) | ✔ 有(TDZ) |
| 暂时性死区 | ❌ 没有 | ✔ 有 | ✔ 有 |
| 块级作用域 | ❌ 没有 | ✔ 有 | ✔ 有 |
| window属性 | ✔ 会挂载 | ❌ 不会 | ❌ 不会 |
let 和 const 的应用
示例:点击按钮1打印1,点击按钮2打印2,点击按钮3打印3
<button class="btn">1</button> <button class="btn">2</button> <button class="btn">3</button> <script> var btns = document.querySelectorAll('.btn'); for (var i = 0; i < btns.length; i++) { btns[i].addEventListener('click', function () { console.log(i); //都是3 }); } </script>为什么打印出来都是3?
因为点击的时候:for 循环已经执行完了,i 早就变成 3 了
拆解:
1️⃣ var 没有块级作用域
for (var i = 0; i < 3; i++) {}等价于:
var i; // 只有一个 i👉 只有一个共享的 i
2️⃣ addEventListener 里的函数“没有立即执行”
function () { console.log(i);}👉 它只是“注册”,不是执行
3️⃣ 点击发生时才执行函数
当你真正点击时:
👉 for 循环早就结束了
i === 34️⃣ 作用域链在找 i
点击时执行函数:
console.log(i);查找过程:
当前函数作用域 → 没有 i外层作用域(for 所在函数 / 全局)→ i = 3👉 所以打印 3
闭包方式解决:
<button class="btn">1</button> <button class="btn">2</button> <button class="btn">3</button> <script> var btns = document.querySelectorAll('.btn'); for (var i = 0; i < btns.length; i++) { (function (index) { btns[index].addEventListener('click', function () { console.log(index + 1); }); })(i); } </script>- 立即执行函数形成了函数作用域,这个函数作用域会保存这个index,当点击的时候触发点击事件,向外找这个index
正确示例:
<button class="btn">1</button> <button class="btn">2</button> <button class="btn">3</button> <script> var btns = document.querySelectorAll('.btn'); for (let i = 0; i < btns.length; i++) { btns[i].addEventListener('click', function () { console.log(i + 1); }); } </script>- let 在 for 循环中有一个特殊机制:每一轮循环,都会创建一个新的 i 绑定
- let 的本质:形成多个闭包环境;每个循环
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














