目录
2.1.1 在 for 语句中声明的循环变量,会泄露为全局变量
3.2.1 用 var 定义 i 变量,循环后打印 i 的值
3.2.2 用 let 定义 i 变量,循环后打印 i 的值
3.3 使用闭包 + 函数作用域,实现 let 块级作用域的效果
对于每个执行上下文,都有三个重要属性:
当查找变量的时候:
这样,由 多个执行上下文的 变量对象 构成的链表,就叫做作用域链
通俗理解:可以访问变量的集合,不同的函数执行上下文,全局执行上下文 都可以访问变量
作用域最大的作用 —— 隔离同名变量,防止冲突(不同作用域下,同名变量不会有冲突)
全局作用域
函数作用域 —— 声明在函数内部的变量,函数作用域在 定义函数时 就决定了
块级作用域(ES6 新增)—— 由 {} 包裹,if 和 for 里面的 {},也属于块级作用域
函数的 创建 和 激活 两个时期,对应了 作用域链 的 创建 和 变化;
为什么说 函数的作用域在函数定义的时候就决定 了呢?
举个栗子~
- function foo() {
- function bar() {
- ...
- }
- }
函数创建时,他们的 [[scope]] 长这样:
- foo.[[scope]] = [
- globalContext.VO
- ];
-
- bar.[[scope]] = [
- fooContext.AO,
- globalContext.VO
- ];
当函数激活时,进入函数执行上下文,会将 活动对象(当前使用的函数执行上下文/作用域)添加到作用链的前端
将此时的 执行上下文的作用域链,命名为 Scope
Scope = [AO].concat([[Scope]]);
至此,作用域链创建完毕
- var scope = "global scope"; // 全局作用域
- function checkscope(){
- var scope2 = 'local scope'; // 函数作用域
- return scope2;
- }
- // 函数调用
- checkscope();
checkscope 函数被创建,保存作用域链到 内部属性 [[scope]](ps:此时保存的是根据词法所生成的作用域链)
- checkscope.[[scope]] = [
- globalContext.VO
- ];
执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
- ECStack = [
- checkscopeContext,
- globalContext
- ];
checkscope 函数并不立刻执行,开始做准备工作;
第一步:复制函数 [[scope]] 属性创建作用域链(checkscope 执行的时候,会复制最初的作用域链,作为自己作用域链的初始化)
- checkscopeContext = {
- Scope: checkscope.[[scope]],
- }
第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明(根据环境生成变量对象)
- checkscopeContext = {
- AO: {
- arguments: {
- length: 0
- },
- scope2: undefined
- },
- Scope: checkscope.[[scope]],
- }
第三步:将活动对象压入 checkscope 作用域链顶端(将变量对象,添加到复制的作用域链头部)
- checkscopeContext = {
- AO: {
- arguments: {
- length: 0
- },
- scope2: undefined
- },
- Scope: [AO, [[Scope]]]
- }
准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
- checkscopeContext = {
- AO: {
- arguments: {
- length: 0
- },
- scope2: 'local scope'
- },
- Scope: [AO, [[Scope]]]
- }
查找 scope2 的值,返回 scope2 的值,函数执行完毕,函数上下文 从执行栈中弹出
- ECStack = [
- globalContext
- ];
关于上述过程中的一些问题:
参考文章:
如下所示,循环完成后,我们仍然可以访问到变量 i,但是我们并不需要变量 i 在全局中生效
- for(var i = 0; i <= 5; i++) {
- console.log("hello");
- }
- console.log(i); // 5
由于变量提升,函数内部的 temp 变量会提升至全局,替代原来的 temp;
由于未走 false 语句,导致未进行变量赋值,因此输出 undefined;
- var temp = new Date();
- function f() {
- console.log(temp);
- if (false) {
- var temp = "hello";
- }
- }
- f(); // undefined
ES6之前,临时变量被封装在IIFE(立即执行函数)中,就不会污染上层函数;
在块级作用域中,可通过 let 和 const 声明变量,该变量在 除了当前块级作用域外 的范围内无法被访问;
其他特点:
var 定义变量 —— 没有块的概念,可以跨块访问, 可以变量提升
let 定义变量 —— 只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明
const 定义常量 —— 使用时必须初始化(即必须赋值),只能在块作用域里访问,不能修改,无变量提升,不可以重复声明
i 是 var 声明的,在全局范围内都有效,全局只有一个变量 i,输出的是最后一轮的 i,也就是 10
- var a = [];
- for (var i = 0; i < 10; i++) {
- a[i] = function() {
- console.log(i);
- };
- }
- a[0](); // 10
i 是 let 声明的,for循环体内部,每循环一次,都生成一个单独的块级作用域,相互独立,也就是有 10个 let i,在十个块级作用域里,不会相互影响覆盖
- var a = [];
- for (let i = 0; i < 10; i++) {
- a[i] = function() {
- console.log(i);
- };
- }
- a[0](); // 0
- var a = [];
-
- // 闭包,可以访问其他函数内部变量的函数
- var _loop = function _loop(i) {
- a[i] = function() {
- console.log(i);
- };
- };
-
- for (var i = 0; i < 10; i++) {
- _loop(i);
- }
- a[0](); // 0
参考文章:
js块级作用域和let,const,var区别 - 野生夜神月 - 博客园1. 块作用域{ } JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也https://www.cnblogs.com/moumoon/p/10985250.htmlES6 入门教程
https://es6.ruanyifeng.com/#docs/let