let、const与块级作用域

ES2015之前,只有全局作用于和函作用域

作用域是ECMAScript编程中非常重要的一个概念,良好的作用域控制技巧称为了ECMAScript开发者必备的技能。以往的ECMAScript标准中,只有全局作用域和函数作用域。ES2015引入了块级作用域。

示例1:

var foo = function() {
    var local = {};
};
foo();
console.log(local); //=> undefined
//local处于函数作用域内,外部无法访问

var bar = function() {
    local = {};
};
bar();
console.log(local); //=>{}
// local未标明var,默认提升为全局作用域的变量,函数外的全局作用域内可访问

示例2:

假设在页面中,有一组按钮(A、B、C),需求是,用户点击按钮组中的按钮时,输出点击按钮的标签值。当我们用一个统一的函数对事件进行响应时,我们可能会像下面这样去循环绑定这组按钮的事件处理

var buttons = document.querySelectorAll(".button"); // 获取按钮组
var output = document.querySelector("#output"); // 获取输出容器元素

for(var i = 0;  i< buttons.length; ++i) { // 循环为每个按钮绑定点击事件
    buttons[i].addEventListener('click', function(evt){
        output.innerText = buttons[i].innerText; // 期望显示按钮自身的值
    }, false);
}

上面这段代码看上去貌似没问题,当我们点击任意一个按钮时,程序都会报错,提示Cannot read property 'innerText' of undefined。因为循环完成后i的值是3,buttons[3]并不存在。计数器i存在于上一层作用域中,就意味着在对它的引用被全部解除之前,它胡一直保存着循环结束后的值,即按钮的个数。

let定义变量的块级作用域

for(let i = 0; i < buttons.length; ++i) {
    // TODO
}

const定义常量

ES2015之前,对于常量的定义,一般都使用大写字母等方式来约定。这种妥协的“常量”是随时可以被改变的。ES2015引入了const来定义常量

// 定义一个常量
const PI = 3.1415926;
// 尝试修改
PI = 3.14; //=> Uncaught TypeError: Assignment to constant variable.

const确实创造了一个了一个不可变的内存绑定,字符串、数字、布尔值、undefined等基本值类型因为只使用了一段内存空间,所以它们由const定义,便是天生的值不可变。但对于对象和数组这类由若干内存空间片段组成的值并不会被锁定。

Object.freeze()让对象首层属性不可变

const obj1 = Object.freeze({
    a: 1,
    b: 2
});
obj1.a = 2; //=> Uncaught TypeError: Connot assign to read only property 'a' of object '<Object>'

const obj2 = Object.freeze({
    a: {}
});
obj2.a.b = 1;
console.log(obj2); //=> {a: {b: 1}} 首层中存在对象,对象值依然可以被改变

// 需要一个小工具来创建一个完全的值不可变对象
// Object.deepFreeze from MDN
// To make obj fully immutable, freeze each object in obj.
// To do so, we use this function.
Object.deepFreeze = function(obj) {
    // Retrieve the property names defined on obj
    var propNames = Object.getOwnPropertyNames(obj);
    // Freeze properties before freezeing self
    propNames.forEach(function(name) {
        var prop = obj[name];
        // Freeze prop if it is an object
        if(typeof prop == 'object' && prop != null)
            Object.deepFreeze(prop)
    });
    
    // Freeze self (no-op if already frozen)
    return Object.freeze(obj);
}

const obj3 = Object.deepFreeze({
    a: {
        b: 1
    }
});
obj3.a.c = 2; //=> Uncaught TypeError: Can't add property c, object is not extensible

与我们所熟知的全局常量不同,使用const定义的常量与let一样,可以产生块级作用域效果。

变量的生命周期

在ECMAScript中,一个变量的生命周期是固定的,由两种因素决定,分别是作用域和对其的引用。
先用ES2015之前的代码来看一下:

(function() {
    var foo = 'A';
})();
console.log(foo); //=> undefined

可以看到在一个匿名函数作用域内定义的变量foo,在脱离这个作用域后变量便不再存在。大部分ECMAScript引擎对垃圾收集的方式都是基于对变量的引用,当一个变量的引用被全部解除时,变量便会被清楚。我们常用闭包(Closure)的方式延长变量的生命周期:

闭包的原理是利用高阶函数产生能够穿透作用域的引用

function outter() {
    const innerVariable = 'foobar';
    return function() {
        return innerVariable;
    }
}

const fn = outter();
conlose.log(fn()); //=> foobar

这里成功地利用闭包将被定义在outter函数所形成作用域内的常量innerVariable所在的作用域引出来,从而在外部的作用域中能够读取到它的值。

let、const与新的循环语句

在ES5中,数组 (Array)被赋予了一个名为forEach的方法,传入回调函数来解决当时的for循环的作用域问题。
在ES2015(也叫ES6),引入了新的循环语句for-of,另外我有了let和const解决了块级作用域,于是乎,无需操心作用域的简洁的for循环又回来了。

const arr = [1, 2, 3];
for(const item of arr) {
    console.log(item);
}

// 配合ES2015的解构(Destructuring)特性,在处理JSON数据时,更加得心应手
const Zootopia = [
    {name: 'Nick', gender: 1, species: 'Fox'},
    {name: 'Judy', gender: 1, species: 'Bunny'}
];
for(const {name, species} of Zootopia) {
    console.log(`Hi, I am ${name}, and I am a ${species}.`);
}
//=>
// Hi, I am Nick, and I am a Fox.
// Hi, I am Judy, and I am a Bunny.

在ES2015中,数组再次被赋予了一个名为entries的方法,它可以返回对应的数组中每一个元素与其下标配对的一个新数组:

const arr = ['a', 'b', 'c'];
console.log(arr.entries()); //=> [[0, 'a'], [1, 'b'], [2, 'c']]

这个新特性可以与解构和for-of循环配合使用:

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

推荐阅读更多精彩内容