javascript 全局变量 定义-使用let/const定义变量的场景

背景

在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

块级声明和块级(词法)范围

javascript 全局变量 定义_全局变量定义在哪

正因为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定义变量的场景