ES6 里的变量和作用域

在本文将有大量的例子介绍在 ES6 中作用域和变量的使用方法

1. 块级作用域的let和const

letconst创造块级作用域,他仅仅存在于包裹他们的最内层的块。下面代码演示了使用let修饰的tmp变量仅仅存在于最里层的if申明里。

function func() {
    if (true) {
        let tmp = 123;
    }
    console.log(tmp); // tmp未定义
}

相比之下,用var申明的变量在函数级作用域

function func() {
    if (true) {
        var tmp = 123;
    }
    console.log(tmp); // 123
}

块级作用域意味着你在函数里只要是两个不同的块,那么变量名称可以重复。(原文为影子变量

function func() {
    let foo = 5;
    if (···) {
        let foo = 10; // shadows outer `foo`
        console.log(foo); // 10
    }
    console.log(foo); // 5
}

2. const创建不可变的变量

let创建的变量是可变的

let foo = 'abc';
foo = 'def';
console.log(foo); // def

const创建变量是不可变的

const foo = 'abc';
foo = 'def'; // TypeError

注意,const并不影响所赋的值是否可变,如果所赋的值是一个对象,那么并不能保证该对象不变。他只是保存一个对象的引用。

const obj = {};
obj.prop = 123;
console.log(obj.prop); // 123

obj = {}; // TypeError

如果你想改变量是真正不可变的,那么直接冻结他的值

const obj = Object.freeze({});
obj.prop = 123; // TypeError

2.1 循环体内的const

一旦const变量创建,那么他就不能改变。但这并不意味着你不能重新声明一个新值,比如在循环体内。

function logArgs(...args) {
    for (let [index, elem] of args.entries()) {
        const message = index + '. ' + elem;
        console.log(message);
    }
}
logArgs('Hello', 'everyone');

// Output:
// 0. Hello
// 1. everyone

2.2 什么时候我该使用let,什么时候该使用const?

const foo = 1;
foo++; // TypeError

如果你想创建的可变变量为基本类型,则,不能使用const。

不过你可以使用const修饰引用类型的变量。

const bar = [];
bar.push('abc'); // array是可变的

按照最佳实践,一般会把常量(真正不变的)使用大写来表示。

const EMPTY_ARRAY = Object.freeze([]);

3. 临时禁区(TDZ)

constlet修饰的变量我叫做它是临时禁区 (TDZ)。当进入这个作用域,外界就无法访问这些被修饰的变量知道运行结束。

使用 var 修饰的变量没有 TDZ。

  • 当进入有var修饰的变量的作用域中,会在内存中立即创建空间,立即初始化变量,并且设置成undifined
  • 在执行过程中如遇到赋值关键字则给变量赋值,否则还是为undifined

使用let关键字的拥有TDZ,这意味着它的生命周期如下:

  • 当进入有let修饰的变量的作用域中,会在内存中立即创建这个变量,不会初始化这个变量。
  • 获取或设置未初始化的变量会导致引用错误(ReferenceError).
  • 在执行过程中如遇到声明处则初始化且给变量赋值,如果不赋值则为undefined。

const的机制与let相似,但他必须赋一个值且不能被改变。

在 TDZ 中,如果获取或者设置一个未初始化会抛出异常。

if (true) { // 一个新的作用域, TDZ 开始
    //tmp未初始化
    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError

    let tmp; // TDZ 结束, `tmp` 被初始化为 `undefined`
    console.log(tmp); // undefined

    tmp = 123;
    console.log(tmp); // 123
}

下面例子演示了 TDZ 是临时的(基于时间)的而不是基于位置的:

if (true) { // 一个新的作用域, TDZ 开始
    const func = function () {
        console.log(myVar); // OK!
    };

    // 在这里已经进入了TDZ,访问 `myVar` 会导致 ReferenceError

    let myVar = 3; TDZ 结束
    func(); // called outside TDZ
}

3.1 TDZ的类型检查

一个变量不能再TDZ里访问意味着你也不能在该变量使用typeof

if (true) {
    console.log(typeof tmp); // ReferenceError
    let tmp;
}

我不认为这将在实践中是一个问题。因为你不能有条件的给某一个作用域加上let修饰符。事实上你仍然可以使用var修饰符创建全局变量

if (typeof myVarVariable === 'undefined') {
    // `myVarVariable`不存在,则创建它
    window.myVarVariable = 'abc';
}

4. 在循环体的头部中使用let修饰符

在循环体中,你每次迭代重新绑定用let修饰的变量。允许你这样做的循环:for, for-infor-of

if (typeof myVarVariable === 'undefined') {
    let arr = [];
    for (let i=0; i < 3; i++) {
        arr.push(() => i);
    }
    console.log(arr.map(x => x())); // [0,1,2]
}

相比之下,用var声明的循环体中,,每次迭代室友一个单一的值

if (typeof myVarVariable === 'undefined') {
    let arr = [];
    for (var i=0; i < 3; i++) {
        arr.push(() => i);
    }
    console.log(arr.map(x => x())); // [3,3,3]
}

为每次迭代得到一个新的绑定似乎有些奇怪,但当你使用循环创建函数(例如回调事件处理)它是非常有用。

5. 形参

5.1 形参和局部变量

如果你声明的变量名正好与形参一致,那么会爆出一个静态错误

function func(arg) {
    let arg; // static error: duplicate declaration of `arg`
}

在函数里面再嵌套一个块则会避免这个问题

function func(arg) {
    {
        let arg; // 影子参数 `arg`
    }
}

相比之下,用var修饰的与形参同名的变量不会出现错误,表现的形式是覆盖了形参。

function func(arg) {
    var arg; // does nothing
}
function func(arg) {
    {
        var arg; // does nothing
    }
}

5.2 默认形参与TDZ

如果形参有默认值,他们被当做一个序列

// OK: 声明之后访问x
function foo(x=1, y=x) {
    return [x, y];
}
foo(); // [1,1]

// 异常,在YDZ里试图访问y
function bar(x=y, y=2) {
    return [x, y];
}
bar(); // ReferenceError

形参默认值的范围是独立于body的作用域(前者围绕后者)。这意味着“inside”定义的方法或函数参数的默认值不知道body的局部变量。

// OK: 在x已经声明后y访问x
function foo(x=1, y=x) {
    return [x, y];
}
foo(); // [1,1]

// 异常: `x` 试图在TDZ访问 `y`
function bar(x=y, y=2) {
    return [x, y];
}
bar(); // ReferenceError

6. 全局对象

JS中的全局对象(浏览器是windows,Node.js是global)的bug比特性还要多,尤其在性能这一块,这也就是不奇怪ES6有以下描述:

  • 全局对象的属性都是全局变量。在全局范围,varfunction 声明创建这些属性
  • 是全局变量但不是全局对象的属性。在全局范围,letconst, Class 声明创建这些属性

7. 函数的声明和类的声明

函数声明:

  • 块级作用域,像let
  • 在全局对象创建属性(在全局范围),像var。
  • 声明提升:独立的一个函数声明中提到它的范围,它总是创建之初的范围

下面代码解释了声明提升

{ // Enter a new scope

    console.log(foo()); // OK, due to hoisting
    function foo() {
        return 'hello';
    }
}

类的声明:

  • 块级作用域
  • 不会再全局对象上创建属性
  • 不会声明提升

类不升起可能令人惊讶,因为他们创建函数。这种行为的理由是,他们继承条款定义的值通过表达式,表达式必须在适当的时间执行。

{ // 进入新的作用域
    
    const identity = x => x;

    //这儿是`MyClass`的TDZ
    let inst = new MyClass(); // ReferenceError

    //注意 `extends`
    class MyClass extends identity(Object) {
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容