背景
在javaScript中,定义变量是一个非常常见的操作。 在Es5中,var一般用来定义和声明变量。 在Es6中,增加了let和const关键字,它们也用于声明和定义变量。
你在什么情况下使用它们,并解决了你自己开发过程中定义变量的一些困惑?
为什么使用Let、const定义变量可以节省显存?
javaScript中有多少种定义和声明变量的方法?
ES5中声明变量的方式只有两种,var和function关键字,而Es6增加了let和const,还有另外两种类型的import和class关键字
var 声明和变量改进
在Es5中,只有函数作用域和全局作用域,没有块级作用域。 由关键字 var 声明的变量,无论在何处声明,都将被视为在当前作用域底部声明的变量。 这就是我们常说的改进机制
这可能会导致一些问题
场景 1 - 函数外部变量可能会覆盖内部变量
var tmp = new Date();
function f(condition) {
// 此处可以访问变量tmp,其值为undefined
console.log(tmp);
if(condition) {
var tmp = "itclanCoder";
return tmp;
} else {
// 此处可访问变量value,其值为undefined
console.log(tmp);
return null;
}
}
f(false); // undefined
在里面的代码中,你可能会认为变量tmp只有在条件为真时才能创建。 事实上,无论函数 f 是什么,变量 tmp 都会被创建。 在预编译阶段,javaScript引擎会将里面的f函数更改为以下内容
函数f执行后,输出结果未定义。 原因是函数声明时,变量会被提升到运行环境的底部,导致外层tmp变量覆盖内层tmp变量
它会看起来像这样
function f(condition) {
var tmp; // 先定义变量
if(condition) {
tmp = "itclanCoder";
return tmp;
}else {
return null;
}
}
var tmp = new Date();
f(false)
变量tmp的声明被提升到函数的底部,并且初始化操作仍然在原来的地方执行,这意味着在else中也可以访问变量tmp。 由于此时变量还没有初始化javascript 全局变量 定义,所以只有定义,没有形参。 所以该值是未定义的
场景 2 - 将计数循环变量泄漏到全局变量中
循环javaScript字符串,输出并复制每个字符
var str = "javaScript";
for(var i = 0;i<str.length;i++) {
console.log(i,str[i]);
}
console.log(i);
//0 'j'
1 'a'
2 'v'
3 'a'
4 'S'
5 'c'
6 'r'
7 'i'
8 'p'
9 't'
10
里面的i变量只是用来控制循环,循环结束后并没有消失释放,而是泄漏到全局变量中,会造成全局变量污染
解决方案:
如果使用let定义一个变量,该变量不会被提升到作用域的底部,它只会在它定义的块级作用域内生效
防范措施
使用let和const来定义变量。 由于里面没有对变量进行改进,所以变量必须在声明后使用,否则会报错
console.log(tmp); // ReferenceError
let tmp = 2;
里面的i变量只是用来控制循环,循环结束后并没有消失释放,而是泄漏到全局变量中,会造成全局变量污染
解决方案:
如果使用let定义变量,则for循环的计数器变量i仅在for循环中有效
如下例所示
var arr = [];
for(var i = 0;i<10;i++) {
arr[i] = function() {
console.log(i);
return arr.push(i)
}
}
console.log(arr[8]()); // 10, 11
前面的代码中,使用var的声明在全局范围内有效,所以每次循环,新的i值都会覆盖旧值,导致最终输出的是上一轮的i值
如果使用let,声明的变量只在块级作用域内有效,最后会输出8
块级声明和块级(词法)范围
正因为Es5中用var声明的变量没有块级作用域,会污染全局变量,如果使用不当,会造成一些疗效达不到你的预期,所以Es6中就有了块级作用域
块级作用域:用于声明在指定块作用域之外无法访问的变量
函数内部
在块中(字符 {} 之间的区域)
块级与块级之间的代码块是相互隔离的,互不影响,如下图
示例代码:
function fn() {
let n = 12;
if(true) {
let n = 20;
}
console.log(n); // 12
}
函数里面有两个代码块,都声明了变量n,运行后输出12
防范措施
Es6允许块级作用域任意嵌套,内部作用域很难读取外部作用域的变量
{{{{let name = 'itclanCoder'}}}}
外部作用域可以定义与内部作用域同名的变量,内部声明的函数不会影响作用域外部
{
let name = '随笔川迹'
{
let name = 'itclanCoder'
}
}
随着块级作用域的出现,不需要立即执行匿名函数
(function() {
var tmp = '';
}())
// 块级作用域
{
let tmp = '';
}
没有变量提升
let不会像var那样增加变量,所以变量在使用前必须声明,否则会报错
console.log(foo) // ReferenceError
let foo = 2222;
同一块作用域内不允许重复声明
// 报错
function () {
let a = 1;
var a = 2;
}
不能在函数内重新声明参数
function func(arg) {
let arg; // 报错
}
而且如果是这样的话,也不会报错
function func(arg) {
if(true) {
let arg; // 不报错
}
}
暂时死区
定义的变量只要在大括号{}中用let和const声明,就会被绑定在这个区域内,不会受到外界的影响。 它会生成自己的封闭作用域,只要在声明之前使用了定义的变量,就会报错
在代码块中,只有使用let和const命令声明变量后,该变量才可用。 这称为临时死区 (TDZ)。 也就是说需要提前声明,并且可以使用形参
if(true) {
// 暂时性死区开始
tmp = 'itclanCoder'; // ReferenceError,报错
console.log(tmp);
let tmp; // 暂时性死区结束
console.log(tmp); // undefined
tmp = "随笔川迹";
console.log(tmp);
}
在let命令声明变量tmp之前,属于变量tmp的死区
之所以定义临时死区,并且没有对变量进行改进,主要是为了减少运行时错误,避免在变量声明之前就使用这个变量,从而造成一些bug
临时死区的本质是:一旦进入当前作用域,所使用的变量就已经存在并且无法获取。 只有当声明变量的那行代码出现时javascript 全局变量 定义,才能获取并使用该变量
为什么使用let和const声明变量可以节省显存空间
如下代码
function f(condition) {
if(condition) {
let dateVal = new Date();
return dateVal;
}else {
// 变量dateVal在此处不存在
return null;
}
// 变量dateVal在此处不存在
}
使用let声明后,上述函数f中定义的dateVal变量不再提升到函数的底部。 当离开if语句块时,dateVal将立即被销毁
当condition的值为假时,dateVal永远不会被声明和初始化
const 声明命令
const 是 Es6 中的新关键字。 一旦声明,其值就不能修改,所以const声明的常量必须初始化,并且不能留在以后的形参中。
// 有效的常量
const maxLength = 10;
// 语法错误,常量未初始化
const name;
关于循环中的const声明
代码中经常使用for循环,需要对变量进行初始化。 对于for循环,初始化时可以使用const,但是如果修改变量,就会抛出错误
var arrs = [];
for(const i = 0; i< 10;i++) {
arrs.push(function() {
console.log(i);
})
}
在此代码中,变量 i 被声明为常量。 第一个循环中,i为0,迭代执行成功,然后执行i++。 因为这句话试图改变常量,所以会抛出错误。 如果后续循环不改变常量,那么就可以使用const声明
例如:在 for-in 或 for-of 循环中使用 const 的行为与使用 let 的行为一致。 如果使用const定义的常量,以后就不会改变,所以可以使用
var arrs = [];
var object = {
a: true,
b: true,
c: true
}
// 不会产生错误
for(const key in object) {
arrs.push(function() {
console.log(key);
})
}
arrs.forEach(function(arr) {
arr();
})
防范措施
对于复合类型的变量,变量名并不指向数据,而是指向数据所在的地址。 const命令只保证变量名指向的地址不变,并不能保证该地址处的数据不变。
因此,将对象声明为常量时必须特别小心
const foo = {};
foo.data = 123;
console.log(foo.data) // 123
foo = {}; // TypeError: 'foo' is read-only不起作用
在前面的代码中,常量 foo 存储一个地址,该地址指向一个对象。 只有这个地址是不可变的。 Foo 不能指向另一个地址,但对象本身是可变的,因此仍然可以向其添加新属性
关于全局块作用域的绑定
当var、function在全局作用域中使用时,会创建一个新的全局变量对象作为全局对象(浏览器环境中的window对象),使用var会覆盖已有的全局变量
let、const、class命令声明的全局变量不属于全局对象的属性,声明的变量不会得到提升,只能在声明此类变量的代码块中使用
在声明变量之前无法访问变量
如果不想为全局对象创建属性,使用let和const会安全得多
如果你想在全局对象下定义变量,你总是可以使用var。 在这些情况下,它通常用于在浏览器中跨 iram 或窗口访问代码
具体何时使用 var、let、const
对于需要保护的变量,使用const。 只有当确实需要改变变量的值时,才使用let,因为大多数变量的值在初始化后不应该改变,而变量值的意外改变会导致很多问题。 虫子
如果想在全局对象下定义变量,可以使用var
总结
块级绑定 let 和 const 给 javaScript 引入了词法作用域,使用它们来声明变量不会有任何改进,并且只能在声明此类变量的代码块中使用
使用let和const还可以节省显存空间,并且不会造成全局变量污染。 需要后声明形参,然后才能使用(暂时死区)
对于改变变量,使用let,定义不改变的变量,使用const声明,如:在for循环体中,使用const定义初始化值变量,这样会报错,因为常量不能改变
在for..in、for..of循环中,let、const会为每次迭代创建一个新的绑定,这样循环体中创建的函数就可以访问对应迭代的值,而不是上一次迭代之后的值价值
原文出处——使用Let/const定义变量的场景
发表评论