[TOC] >[success] # 词法声明(lexicalDecls)和变量声明(varDelcs) * 来自极客时间周爱民老师《JavaScript核心原理解析》读后一些整理总结 * [阮一峰老师的内存泄漏文章](http://www.ruanyifeng.com/blog/2017/04/memory-leak.html) ~~~ 1.变量声明(varDelcs) -- 没有绑定值的var所声明的标识符的,在js环境中创建,变量名(varName in varDecls)后, 会为它初始化绑定一个 undefined 值 2.词法声明(lexicalDecls) -- let/const 词法名字(lexicalNames)在创建之后它们在缺省情况下,就是'还没有绑定值' 的标识符,只有在运行阶段执行后才能使用。 3.'6 种声明'语句中的函数是按 varDecls 的规则声明的;类的内部是处于严格模中,它的名字是按 let 来处理的,而 import 导入的名字则是按 const 的规则来处理的。所以,所有的声明本质上只有三种处理模式:var 变量声明、let 变 量声明和 const 常量声明。 ~~~ >[info] ## 弄清一个概念 -- 非声明变量(隐式全局变量) / var / let、const [MDN -- var ](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/var) ~~~ 1.用'var'声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,也可以是声明在任何函数外的变量, 这句简单的理解:'var'声明的变量如果在函数中那么他的作用域就是在当前函数里,如果不在函数中它的作用域 就在全局里 2.'隐式全局变' -- 将赋值给未声明变量的值在执行赋值时将其隐式地创建为全局变量 下面将借用MDN中代码更好的说明这两点 ~~~ * 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的。 ~~~ function x() { y = 1; // 在严格模式(strict mode)下会抛出 ReferenceError 异常 var z = 2; } x(); console.log(y); // 打印 "1" console.log(z); // 抛出 ReferenceError: z 未在 x 外部声明 ~~~ * 声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作的时候才会被创建。 ~~~ // 非声明 变量隐式全局变量如果没有上来赋值就报错 console.log(a); // 抛出ReferenceError。 console.log('still going...'); // 永不执行。 // var 这中声明变量就不会有这个问题 var a; console.log(a); // 打印"undefined"或""(不同浏览器实现不同)。 console.log('still going...'); // 打印"still going..."。 ~~~ >[danger] ##### 根据上面知识扩展 varName 和 varDecls ~~~ 1.首先如果不使用变量声明的'隐式全局变量'会出现一个问题就是'变量泄漏',可以使用严格模式杜绝这种写法, 或者使用变量声明的写法 2.在ES6 出现后 js 全局就被分成了两种,因为我们向上面说的在特定情况下'var' 声明的也是全局变量,既然 全局变量不好为啥es6出现后不直接干掉全局变量这种概念呢,因为js 要做到向下兼容,之前支持所以现在 依然要支持,不过但是为了得到一个'尽可能'与其它变量环境相似的声明效果'(varDecls)',ECMAScript 规定 在这个全局对象之外再维护一个变量名列表'(varNames)' 3.下面代码我是在浏览器运行的因此用'getOwnPropertyDescriptor'属性比较的是'window' 存在情况,如果你在 'node' 你可以比较'global',通过下面代码利用'getOwnPropertyDescriptor' 方法也能证明'var' 在非函数内声明的 情况下也是个全局变量,但要注意了此时'var' 声明的全局变量其实存在'varNames'一个变量列表里的,他和 隐式全局变量不一样,算然他们都是全局变量 4.也可以理解 为什么'var' 声明的变量不能被'delete' 删除,你可以更抽象的理解在'varNames'列表内的变量声明 是不让删除的,但也不是全都着这样,如果'var'声明发生在 eval() 中的时候,虽然现在'var' 声明变量也在'varNames' 列表中但是可以删除 可以看第二段代码,仅仅是在这种情况下是特例 ~~~ ~~~ var a = 100 b = 100 console.log(Object.getOwnPropertyDescriptor(window, 'a')) // {value: 100, writable: true, enumerable: true, configurable: false} console.log(Object.getOwnPropertyDescriptor(window, 'b')) // {value: 100, writable: true, enumerable: true, configurable: true} // 不能删除 console.log(delete a) // false // 可以删除 console.log(delete b) // true ~~~ * 这里的代码是在node环境下运行的 ~~~ # 使用eval声明 > eval('var b = 300'); # 它的性质是可删除的 > Object.getOwnPropertyDescriptor(global, 'b').configurable; true # 检测与删除 > b 300 > delete b true > b ReferenceError: b is not define ~~~ >[danger] ##### 总结 ~~~ 1.站在es6的角度来说,'隐式全局变量'--'varDecls' 存着,'var' -- 'varNames'存着,let/const -- lexicalDecls 2.如果我们写了这样一行代码: let a = 100 console.log(Object.getOwnPropertyDescriptor(window, 'a')) // undefind 你会发现打印的是'undefind',虽然全局作用域里用let,const创建的变量,虽然也是全局可见,但它并没有创建在 'global' 或者'window'上,那'let,const' 和'var'在'global'这两个作用域是什么关系呢? 这里引用了'周爱民'老师的回答: 在JavaScript中,'全局环境'里面的'var与let/const'是'用了两个东西来管理'的,所以他们也确实是创建在不同的地方。 但是从'作用域链'的角度上来说,它们并没有级别高低(也就是parent没有相互指向)。使得它们存取的效果有差别 的,是因为'全局环境'采用了词法环境优先(也就是let/const声明)的顺序。 ~~~