资讯专栏INFORMATION COLUMN

ES6系列---块级作用域

chinafgj / 1067人阅读

摘要:准确的说,之前,不存在语法级的块级作用域支持,开发者往往以创建一个立即执行的函数来隔离外部世界对函数内部变量的访问权。块级声明提供了和标识符,用于声明块级作用域的变量。全局块作用域和与的另外一个区别是它们在全局作用域中的行为。

var声明的提升

先看下面这段代码:

function getValue(condition) {
    if(condition) {
        var value = "blue";
        return value;
    } else {
        // 此处可访问变量value, 其值为undefined
        return null;
    }
    // 此处可访问变量value, 其值为undefined
}

如果你不熟悉JavaScript,可能会认为只有当condition的值为true时才会创建变量value。事实上,在预编译阶段,JavaScript引擎会将上面的函数修改成下面这样:

function getValue(condition) {
    var value;
    if(condition) {
        value = "blue";
        return value;
    } else {
        return null;
    }
}

变量value的声明被提升至函数顶部,而初始化操作留在原处执行。

再看一段代码:

for(var i=0;i<10;i++){

}
console.log(i);  // 10

这段for循环结束后,循环外的i变量并非undefined。同样也是由于i变量声明提升所致。
准确的说,ES6之前,不存在语法级的块级作用域支持,开发者往往以创建一个立即执行的函数来隔离外部世界对函数内部变量的访问权。

(function(){ ... })()
块级声明

ES6提供了let和const标识符,用于声明块级作用域的变量。
块级作用域存在于:

函数内部

块中(字符{和}之间的区域)

let声明
function getValue(condition) {
    if(condition) {
        let value = "blue";
        return value;
    } else {
        // 变量value在此处不存在
        return null;
    }
    // 变量value在此处不存在
}
禁止重复声明

同一作用域内,不能用let去重复声明已声明过的变量:

var count = 30;
let count = 40; // 抛出语法错误

这样子是可以的:

var count = 30; // if块外的变量
if(condition) {
    let count = 40; // 声明的是if块中的新变量
}
const声明

与let声明的区别是,const声明的是常量,其值一旦被设定后不可更改。因此声明时就必须进行初始化:

const maxItems = 30;
maxItems = 40; // 语法错误,常量不可修改
const name;  // 语法错误,未初始化
const声明的对象

const声明不允许修改绑定,但允许修改值。意味着const声明对象后,可以修改该对象的属性值。有Java后端经验的同学很容易理解,这就是Java的值传递:

const person = {
    name: "Nicholas"
};

person.name = "Greg";  // 可以修改对象属性的值

// 抛出语法错误
person = { // 不允许修改引用
    name: "Greg";
};
临时性死区(Temporal Dead Zone)

ECMAScript标准并没有明确提到TDZ,但人们常用它来描述let和const的不提升效果。
JavaScript引擎在扫码代码发现变量声明时,要么将它们提升至作用域顶部(遇到var声明),要么将它们放到TDZ中(遇到let和const声明)。访问TDZ中的变量会触发运行时错误:

if(condition) {
    console.log(typeof value);  // 引用错误, 不允许访问TDZ中的变量
    let value = "blue";  // 只有执行过声明语句后,变量才从TDZ中移除
}

可见,即便是相对不易出错的typeof操作符也无法阻挡引擎抛出错误。
在let声明的作用域外对该变量使用typeof则不会报错:

console.log(typeof value);  // "undefined"

if(condition) {
    let value = "blue";
}

typeof是在声明变量value的代码块外执行的,此时value不在TDZ中。

循环中的块级作用域 在循环中创建函数

长久以来,var声明让开发者在循环中创建函数变得异常困难,因为变量到了循环之外仍能访问:

var funcs = [];

for(var i=0; i<10; i++) {
    funcs.push(function(){
        console.log(i);
    });
}

funcs.forEach(function(func) {
    func();  // 输出10次数字10
});

不是预期的输出0~9,而是输出10次10。因为循环中的i变量声明提升到外部了,循环内创建的函数全部保留了对相同变量i的引用。

以往,为解决这个问题,开发者们往往使用立即调用函数表达式(IIFE):

var funcs = [];

for(var i=0; i<10; i++) {
    funcs.push((function(value) {
        return function() {
            console.log(value);
        }
    }(i)));
}

funcs.forEach(function(func) {
    func();  // 输出0、然后是1、2,直到9
});

ES6提供的let和const让我们再也无需这么折腾了,直接把var换成let就搞定:

var funcs = [];

for(let i=0; i<10; i++) {
    funcs.push(function(){
        console.log(i);
    });
}

funcs.forEach(function(func) {
    func();  // 输出0、然后是1、2,直到9
});
循环中使用let和const的说明

标准的for循环,每次循环后会修改变量值,因此必须使用let:

for(let i=0; i<10; i++) {}

ES6的for-in和for-of循环,由于每次迭代不会修改已有的绑定,因此可以使用const代替:

for(const key in object) {}
let values = [1, 2, 3];
for(const value of values) {}
块级绑定最佳实践

对JavaScript开发者而言,直接用let代替var也符合逻辑。这种情况下,只需注意对需要写保护的变量则使用const。随着更多的开发者迁移到ES6,另一种做法一日普及,默认使用const,只有确实需要改变变量的值时使用let。因为大部分变量的值在初始化后不应再改变,而变量值预料之外的改变是很多bug的源头。这一理念获得了很多人的支持,你不妨试试。

全局块作用域

let和const与var的另外一个区别是它们在全局作用域中的行为。当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。这意味着用var很可能会无意中覆盖一个已经存在的全局变量:

var RegExp = "Hello!";
console.log(window.RegExp);  // "Hello!"

var ncz = "Hi!";
console.log(window.ncz);  //"Hi!"

即使全局对象RegExp定义在window上,也不能幸免于被var声明覆盖。同样,ncz被定义为一个全局变量,并且立即成为window的属性。JavaScript过去一直这样。

使用let或const则不会:

let RegExp = "Hello!";
console.log(RegExp);  // "Hello!"
console.log(window.RegExp === RegExp);  // false

const ncz = "Hi!";
console.log(ncz);              // "Hi!"
console.log("ncz" in window);  // false

如果希望在全局对象下定义变量,仍然可以用var。这种情况常见于在浏览器中跨frame或跨window访问代码。

总而言之,跨越到ES6后,大家可以把var忘了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87377.html

相关文章

  • ES6系列文章 块级作用

    摘要:声明之函数作用域和全局作用域。块级作用域不能重复声明临时性死区等特性用来解决变量存在的种种问题。块级作用域终于在外面访问不到了。一些常量声明使用声明的变量名全部大写。 ES5之前javascript语言只有函数作用域和全局作用域,使用var来声明变量,var声明的变量还存在变量提升使人困惑不已。我们先来复习一下ES5的var声明,再对比学习let和const 。 var var声明之函...

    赵连江 评论0 收藏0
  • ES6系列】变量与块级作用

    摘要:不允许在相同作用域内,重复声明同一个变量。如但是在中则不再必要了,我们可以通过块级作用域就能够实现本次主要针对中的变量和块级作用域进行了梳理学习,并且通过与的实现方式进行了对比,从而看出其变化以及快捷与便利。 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可...

    PascalXie 评论0 收藏0
  • ES6 系列之 let 和 const

    摘要:块级作用域存在于函数内部块中字符和之间的区域和块级声明用于声明在指定块的作用域之外无法访问的变量。和都是块级声明的一种。值得一提的是声明不允许修改绑定,但允许修改值。这意味着当用声明对象时没有问题报错临时死区临时死区,简写为。 块级作用域的出现 通过 var 声明的变量存在变量提升的特性: if (condition) { var value = 1; } console.lo...

    PascalXie 评论0 收藏0
  • JavaScript基础系列---变量及其值类型

    摘要:但对于引用类型的数据主要是对象和数组,变量指向的内存地址,保存的只是一个引用地址指针,只能保证这个引用地址指针是固定的,至于它指向的堆内存中的存储的值是不是可变的,就完全不能控制了。 基础概念 变量是存储信息的容器,这里需要区分一下:变量不是指存储的信息本身,而是指这个用于存储信息的容器,可以把变量想象成一个个用来装东西的纸箱子 变量需要声明,并且建议在声明的同时进行初始化,如下所...

    sugarmo 评论0 收藏0
  • ES6系列之 let 和 const

    摘要:声明的变量不得改变值,这意味着,一旦声明变量,就必须立即初始化,不能留到以后赋值。这在语法上,称为暂时性死区,简称。这表明函数内部的变量与循环变量不在同一个作用域,有各自单独的作用域。系列文章系列文章地址 showImg(https://segmentfault.com/img/bVbrjjC); 为什么需要块级作用域 ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合...

    libxd 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<