阮一峰ES6教程读书笔记(一)解构赋值、let和const

About

读完阮一峰大神ES5教程后自觉获益匪浅,遂拜读其ES6教程。为记录所感所得,打算写《阮一峰ES6教程读后感》系列文章,有兴趣的朋友可以关注一下。

一、详解let和const命令

letconstES6新增的变量声明命令,他们的用法与var类似,但是经它们声明的变量与var声明的变量有很大的区别。

(一)var和let的区别

  • var声明的变量的作用域为全局作用域或者函数作用域,而let声明的变量多一个块级作用域
  • var声明的变量可以进行变量提升,即在声明变量之前使用变量,而let声明的变量必须要在变量声明语句之后使用,否则会报错
  • let声明的变量存在“暂时性死区”,即从区块开始至变量声明语句之前是无法访问变量的
  • let声明的变量不允许重复声明

1. 块级作用域

我们先看看这个例子:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

其实我们预期的答案是6,但是如果这样写代码,我们无法的到预期的答案,因为a[6]里面存储的函数语句中的i指向的是一个全局变量,当for循环完毕后,i的值为10,即函数语句中的每一个i都指向了同一个地址,而这个地址中存储的数字为10,所以我们无论通过数组a中哪一个元素中的函数去打印i得到的答案都是10

为了得到我们预期的答案,我们应该将代码进行一定的改写:

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

可以看到这里面的for循环的i是通过let声明的,所以i仅在当前循环中有效,在其他循环中无法访问到当前循环的i,我们可以理解为每一个i是独立的,所以数组a中每一个元素中存储的函数语句中的i指向的是不同的地址,每个地址存储的是当前循环中的i

2. 不存在变量提升

正常的逻辑是我们必须先声明一个变量,然后再去使用,如果在没有声明就去使用而仅仅告诉我undefined,这显然不能让我们满意。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

通过上面的代码我们可以发现,使用let声明的变量终于纠正了这一点,我们无法在声明变量之前去使用它。

3. 暂时性死区(temporal dead zone,简称 TDZ)

正因为使用let声明的变量不存在变量提升现象,所以会出现暂时性死区的现象

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
  tmp = '2333'
  console.log(tmp) // 2333
}

尽管我们知道在使用大括号括起来的代码块内使用let声明变量,该变量的作用域为块级作用域,但是该块级作用域并非整个区块,从该区块开始到声明变量之前的区域被称之为“暂时性死区”,因为在这一区域内我们无法访问到后面声明的变量。

4. 不允许重复声明

在使用let声明的变量的作用域内,不允许重复声明,无论你用letvar还是const。在函数内也一样:

function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错

(二)块级作用域与函数声明

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。比如:

// 在使用ES5标准的浏览器中
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}()); // 'I am inside!'

因为ES5中,函数也是“一等公民”具有变量提升的作用,但是函数的变量提升不仅仅是函数名的变量提升,而是包括整个函数体的提升,所以上述代码可翻译为:

// 在使用ES5标准的浏览器中
function f() { console.log('I am outside!'); }

(function () {
  // 重复声明一次函数f
  function f() { console.log('I am inside!'); }
  if (false) {
    }
  f();
}()); // 'I am inside!'

但是在使用ES6标准的浏览器中,上述代码就会报错ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
    所以上述代码可翻译为:
// 在使用ES6标准的浏览器中
function f() { console.log('I am outside!'); }

(function () {
  var f  // 此时f为undefined
  if (false) {
  }

  f();
}()); // Uncaught TypeError: f is not a function

(三)const命令

letconst的用法差不多,只不过是const一般被用来声明常量,即声明语句中必须赋值,并且声明之后不能更改

1. const命令的本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

(四)顶层对象的属性

ES6以前的标准中,我们使用varfunction声明的全局变量会变成顶层对象的属性,ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

二、变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

(一)基本用法

let [a, b, c] = [1, 2, 3]; // a = 1, b = 2, c = 3

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值

1. 解构成功

解构成功的条件是=两边的模式完全相同

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

2. 不完全解构

因为我们使用解构的目的是为了给=左边的变量赋值,那么如果从左到右都能找到对应元素,那么就能完成解构,如果此时从右到左无法完成一一对应,那么就称之为不完全解构,例如:

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

3. 解构失败

如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。

// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。

(二)解构表达式默认赋值

同声明函数一样,我们可以在函数表达式中声明实参时给实参赋默认值,如果调用该函数时,形参为空,那么实参就采用声明函数时的默认值。解构赋值表达式同理

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x, y = 'b'] = ['a', 'c']; // x='a', y='c'

(三)对象的解构赋值

与数组一样,解构也可以用于嵌套结构的对象。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

此时的p仅仅是一个模式,如果要对p赋值,必须这么写:

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

(四)字符串解构赋值和布尔值解构赋值

1. 字符串解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

2. 布尔值解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

(五)解构赋值的作用

1. 交换变量的值

以前,这个功能只在Python中使用过,如今可以在JavaScript中使用还是很开心的

[x, y] = [y, x]

2. 从函数返回多个值

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

3. 函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

4. 提取JSON数据

这个太有用了,因为我们从接口中获得的数据一般都是JSON对象,所以使用结构赋值可以使我们的代码更简洁

const {ret, data} = res.data

5. 遍历Map结构

任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。这个在Vue中也是经常使用

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}

6. 输入模块的指定方法

加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");

参考链接

作者:阮一峰
链接:http://es6.ruanyifeng.com/#docs/destructuring

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

推荐阅读更多精彩内容