解析JavaScript的ES6版本中的解构赋值


Posted in Javascript onJuly 28, 2015

什么是解构赋值?

解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性值赋给一系列变量。这个语法非常简洁,而且比传统的属性访问更加清晰。

在不使用解构赋值的情况下,访问数组的前三项:

var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
 
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];

使用解构赋值后,相应的代码变得更简洁和可读:

var [first, second, third] = someArray;
 
var [first, second, third] = someArray;

SpiderMonkey(Firefox 的 JavaScript 引擎)已经支持解构赋值的大部分特性,但还不完全。
数组和可迭代对象的解构赋值

上面我们已经看到了数组解构赋值的例子,该语法的一般形式是:

[ variable1, variable2, ..., variableN ] = array;
 
[ variable1, variable2, ..., variableN ] = array;

这将把数组中对应的项依次赋给 variable1 到 variableN,如果同时需要声明变量,可以在解构表达式前面添加 var,let 或 const 关键字。

var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
 
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;

事实上,你还可以嵌套任意的深度:

var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3
 
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3

此外,还可以跳过数组中的某些项:

var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"

 
var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"

你还可以用一个 Rest 表达式来捕获数组中的剩余项:

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]
 
var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]

如果数组越界或访问数组中不存在的项,将得到和通过数组索引访问一样的值:undefined。

console.log([][0]);
// undefined

var [missing] = [];
console.log(missing);
// undefined
 
console.log([][0]);
// undefined
 
var [missing] = [];
console.log(missing);
// undefined

注意,数组解构赋值的方式也同样适用于可遍历的对象:

function* fibs() {
 var a = 0;
 var b = 1;
 while (true) {
  yield a;
  [a, b] = [b, a + b];
 }
}

var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
 
function* fibs() {
 var a = 0;
 var b = 1;
 while (true) {
  yield a;
  [a, b] = [b, a + b];
 }
}
 
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5

对象的解构赋值

对象的解构赋值允许你将变量绑定到对象不同的属性值。指定被绑定的属性名,后面紧跟要绑定的变量:

var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };

var { name: nameA } = robotA;
var { name: nameB } = robotB;

console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"
 
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
 
var { name: nameA } = robotA;
var { name: nameB } = robotB;
 
console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"

当绑定的属性名和接收属性值的变量名一样时,还有一个语法糖:

var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"
 
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"

与数组一样,也可以嵌套:

var complicatedObj = {
 arrayProp: [
  "Zapp",
  { second: "Brannigan" }
 ]
};

var { arrayProp: [first, { second }] } = complicatedObj;

console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"
 
var complicatedObj = {
 arrayProp: [
  "Zapp",
  { second: "Brannigan" }
 ]
};
 
var { arrayProp: [first, { second }] } = complicatedObj;
 
console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"

解构一个不存在的属性时,将得到 undefined:

var { missing } = {};
console.log(missing);
// undefined
 
var { missing } = {};
console.log(missing);
// undefined

使用对象的解构赋值时还有一个潜在的陷阱,在解构赋值时没有声明变量(没有 var、let或 const 关键字):

{ blowUp } = { blowUp: 10 };
// Syntax error
 
{ blowUp } = { blowUp: 10 };
// Syntax error

这是因为 JavaScript 语法告诉引擎任何以 { 开始的语句都是语句块(例如,{console} 就是一个合法的语句块),解决方法是将整个语句用一对括号包裹:

({ safe } = {});
// No errors
 
({ safe } = {});
// No errors

其他情况

当你尝试解构 null 或 undefined,你将得到类型错误:

var {blowUp} = null;
// TypeError: null has no properties
 
var {blowUp} = null;
// TypeError: null has no properties

不过,你可以对其他基本类型(Boolean、String 和 Number)进行解构,将得到 undefined:

var {wtf} = NaN;
console.log(wtf);
// undefined

 
var {wtf} = NaN;
console.log(wtf);
// undefined

结果也许会让你感到意外,但深究一下,其实原因很简单。在进行对象解构赋值时,被解构的对象将被强制转换为 Object,除 null 和 undefined 外,其它类型都可以被强制转换为对象。进行数组的结构赋值时,要求被解构的对象有一个遍历器。
默认值

可以为不存在的属性指定一个默认值:

var [missing = true] = [];
console.log(missing);
// true

var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"

var { x = 3 } = {};
console.log(x);
// 3
 
var [missing = true] = [];
console.log(missing);
// true
 
var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"
 
var { x = 3 } = {};
console.log(x);
// 3

实际应用
函数参数

作为开发人员,我们经常把一个包含多个属性的对象作为函数的参数,来实现更灵活的 API,而不是让 API 的使用者记住一些特定顺序的参数。我们可以使用对象的解构赋值,来避免每次使用参数时的属性访问:

function removeBreakpoint({ url, line, column }) {
 // ...
}
 
function removeBreakpoint({ url, line, column }) {
 // ...
}

配置对象

完善上面的例子,我们可以为要被解构的对象属性提供默认值,这在对那些作为配置参数的对象非常实用,因为许多配置项都有一个合理的默认值。例如,jQuery 的 ajax 方法的第二个参数为一个配置对象,我们可以这样实现:

jQuery.ajax = function (url, {
 async = true,
 beforeSend = noop,
 cache = true,
 complete = noop,
 crossDomain = false,
 global = true,
 // ... more config
}) {
 // ... do stuff
};
 
jQuery.ajax = function (url, {
 async = true,
 beforeSend = noop,
 cache = true,
 complete = noop,
 crossDomain = false,
 global = true,
 // ... more config
}) {
 // ... do stuff
};

这避免了类似这样的重复代码:var foo = config.foo || theDefaultFoo;。
与迭代器一起使用

当遍历 Map 对象时,我们可以使用解构赋值来遍历 [key, value]:

var map = new Map();
map.set(window, "the global");
map.set(document, "the document");

for (var [key, value] of map) {
 console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"

 
var map = new Map();
map.set(window, "the global");
map.set(document, "the document");
 
for (var [key, value] of map) {
 console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"

只遍历键:

for (var [key] of map) {
 // ...
}

 
for (var [key] of map) {
 // ...
}

只遍历值:
for (var [,value] of map) {
 // ...
}

 
for (var [,value] of map) {
 // ...
}

返回多个值

返回一个数组,通过解构赋值提取到返回值:

function returnMultipleValues() {
 return [1, 2];
}
var [foo, bar] = returnMultipleValues();

 
function returnMultipleValues() {
 return [1, 2];
}
var [foo, bar] = returnMultipleValues();

或者,返回一个键值对的对象:

function returnMultipleValues() {
 return {
  foo: 1,
  bar: 2
 };
}
var { foo, bar } = returnMultipleValues();

 
function returnMultipleValues() {
 return {
  foo: 1,
  bar: 2
 };
}
var { foo, bar } = returnMultipleValues();

这两者都比使用中间变量好:

function returnMultipleValues() {
 return {
  foo: 1,
  bar: 2
 };
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;
 
function returnMultipleValues() {
 return {
  foo: 1,
  bar: 2
 };
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;

采用延续式:

function returnMultipleValues(k) {
 k(1, 2);
}
returnMultipleValues((foo, bar) => ...);

 
function returnMultipleValues(k) {
 k(1, 2);
}
returnMultipleValues((foo, bar) => ...);

导入 CommonJS 模块的指定部分

还没使用过 ES6 的模块吧,那至少使用过 CommonJS 吧。当导入一个 CommonJS 模块 X 时,模块提供的方法也许多余你实际使用的。使用解构赋值,你可以明确指定你需要使用模块的哪些部分:

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

如果你使用 ES6 的模块机制,你可以看到 import 声明时有一个类似的语法。
结论

我们看到,解构赋值在很多场景下都很实用。在 Mozilla,我们已经有很多经验。Lars Hansen 在 10 年前就向 Opera 引入了解构赋值,Brendan Eich 在稍微晚点也给 Firefox 添加了支持,最早出现在 Firefox 2 中。因此,解构赋值已经渗透到我们每天对 JS 的使用中,悄悄地使我们的代码更简短、整洁。

Javascript 相关文章推荐
jquery自定义滚动条插件示例分享
Feb 21 Javascript
JavaScript实现三阶幻方算法谜题解答
Dec 29 Javascript
jQuery地图map悬停显示省市代码分享
Aug 20 Javascript
使用Function.apply()的参数数组化来提高 JavaScript程序性能的技巧
Dec 23 Javascript
jQuery文本框得到与失去焦点动态改变样式效果
Sep 08 Javascript
[原创]JavaScript语法高亮插件highlight.js用法详解【附highlight.js本站下载】
Nov 01 Javascript
jQuery Easyui datagrid editor为combobox时指定数据源实例
Dec 19 Javascript
vuex实现及简略解析(小结)
Mar 01 Javascript
微信小程序 slot踩坑的解决
Apr 01 Javascript
Webpack中loader打包各种文件的方法实例
Sep 03 Javascript
基于ajax及jQuery实现局部刷新过程解析
Sep 12 jQuery
js代码编写无缝轮播图
Sep 13 Javascript
深入学习JavaScript中的Rest参数和参数默认值
Jul 28 #Javascript
JQuery实现鼠标滚轮滑动到页面节点
Jul 28 #Javascript
CSS3实现动态背景登录框的代码
Jul 28 #Javascript
javascript制作幻灯片(360度全景图片)
Jul 28 #Javascript
详解JavaScript ES6中的模板字符串
Jul 28 #Javascript
javascript解决IE6下hover问题的方法
Jul 28 #Javascript
JavaScript如何自定义trim方法
Jul 28 #Javascript
You might like
php eval函数用法 PHP中eval()函数小技巧
2012/10/31 PHP
Ubuntu下安装PHP的mongodb扩展操作命令
2015/07/04 PHP
PHP命名空间和自动加载类
2016/04/03 PHP
PHP文件操作实例总结
2016/09/27 PHP
php监测数据是否成功插入到Mysql数据库的方法
2016/11/25 PHP
详解如何在云服务器上部署Laravel
2017/06/30 PHP
CL vs ForZe BO5 第一场 2.13
2021/03/10 DOTA
js 幻灯片的实现
2011/12/06 Javascript
ASP.NET jQuery 实例13 原创jQuery文本框字符限制插件-TextArea Counter
2012/02/03 Javascript
javascript 实现键盘上下左右功能的小例子
2013/09/15 Javascript
JS可以控制样式的名称写法一览
2014/01/16 Javascript
js数组操作常用方法
2014/05/08 Javascript
nodejs文件操作模块FS(File System)常用函数简明总结
2014/06/05 NodeJs
jQuery处理图片加载失败的常用方法
2015/06/08 Javascript
更高效的使用JQuery 这里总结了8个小技巧
2016/04/13 Javascript
利用Angularjs实现幻灯片效果
2016/09/07 Javascript
angular分页指令操作
2017/01/09 Javascript
JS实现图片预加载之无序预加载功能代码
2017/05/12 Javascript
利用C/C++编写node.js原生模块的方法教程
2017/07/07 Javascript
jQuery实现百度图片移入移出内容提示框上下左右移动的效果
2018/06/05 jQuery
JS查找孩子节点简单示例
2019/07/25 Javascript
如何使用Javascript中的this关键字
2020/05/28 Javascript
[07:20]2014DOTA2西雅图国际邀请赛 选手讲解积分赛第二天
2014/07/11 DOTA
Python编程中字符串和列表的基本知识讲解
2015/10/14 Python
python+PyQT实现系统桌面时钟
2020/06/16 Python
xadmin使用formfield_for_dbfield函数过滤下拉表单实例
2020/04/07 Python
解决使用python print打印函数返回值多一个None的问题
2020/04/09 Python
解决Keras 中加入lambda层无法正常载入模型问题
2020/06/16 Python
浅谈Python协程
2020/06/17 Python
tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this T
2020/06/22 Python
Python钉钉报警及Zabbix集成钉钉报警的示例代码
2020/08/17 Python
英国领先的野生鸟类食品供应商:GardenBird
2018/08/09 全球购物
环保志愿者活动总结
2014/06/27 职场文书
会员卡清退活动总结
2014/08/27 职场文书
2016年清明节寄语
2015/12/04 职场文书
Python中的嵌套循环详情
2022/03/23 Python