变量的解构赋值
目录:
-
数组的解构赋值
-
对象的结构赋值
-
字符串的解构赋值
-
数值和布尔值的解构赋值
-
函数参数的解构赋值
-
常见的使用场景
-
一些Tips
ES6允许我们通过官方规定的手法从可遍历数据结构中直接提取值并且赋到我们创造的变量中, 这种操作被称之为解构赋值(Destructuring)
数组的结构赋值
以前我们从一个数组中取值, 大概是这样
var arr = [1, 2, 3];
var first = arr[0];
var second = arr[1];
console.log(first, second); // 输出1, 2
这种场景其实是频繁的多, 我们总是可能要去取数组中的某一位值, 但是这样写的话, 官方也总觉得别扭, 而且不够灵活, 假如我们要取arr的前三位, 那我们势必是需要从arr[0]写到arr[3], 于是在ES6中, 官方推出了一种新的写法
const arr = [1, 2, 3];
const [first, second, third] = arr;
console.log(first, second, third); // 输出1, 2, 3
const fixArr = [[1, 2, 3], [4, 5, 6]];
const [firstArr, secondArr] = fixArr; // firstArr 为[1, 2, 3], secondArr 为[4, 5, 6]
const [[fstArrson], [secArrson]] = fixArr; // fstArrson 为1, secArrson为4
上面的方法的本质其实也是一种结构的匹配, 只要等号左边和等号右边的数组结构相同或者等号右边为可遍历的数据结构, 左边的变量就会被赋予相同的值, 同时如果是按顺序取值, 那么只要对应顺位匹配的人结构相同即可, 我们再来看一些规则
- 如果左右两边的总数据结构统一, 而子元素个数不统一, 则会按照从左到右的格式进行不完全解构
const arr = [1, 2, 3];
const [first, second] = arr; // 此时first和second被赋值为1, 2
const arr2 = [1, 2, 3];
const [first, third] = arr; // 即使我想要获取数组的顺位第三, 这样写也只能获得1, 2
- 如果不想按顺序获取所有内容, 则用逗号隔开
// 比如我就想获取数组的第一位和第三位
const arr = [1, 2, 3];
const [first, , third] = arr; // 此时first和third的值分别为1和3, 因为我们用逗号将中间隔出一个顺位, 代表第二位不需要结构
- 如果解构数组不成功, 则会定义默认值undefined
const arr = [1, 2]
const [first, second, third] = [1, 2];
// 很明显被解构数组只有两位,我们的third势必是取不到值的, 所以first -> 1, second -> 2, third -> undefined, 而我们用的const的话等于将third 赋值为undefined且无法改变
rest运算符
rest运算符写法就是...
关于这个rest运算符笔者在后面会详细进行介绍, 这里先混个脸熟, 这哥们就是把零散的值装到一起, 或者把装在一起的值散列开, 具体怎么运作还是看场景,有了这个rest运算符, 那我们解构又可以这么玩了
先读一句话,在解构中使用rest运算符会导致在解构过程中, 带有rest运算符的变量会直接匹配前面匹配完成的剩余所有值并将他们装进一个数组, 同时也代表这个带有rest运算符的变量是收集剩余的所有变量进一个数组, 所以他必须是最后一位, 否则会报错
const arr = [1, 2, 3];
const [first, ...restArr] = arr;
// 上面的代码 等于我定义了两个变量 一个first变量, 另一个restArr变量,
// 由于restArr前面有三个点, 所以他在解构时, 会直接匹配前面匹配完成的剩余所有值并将他们装进一个数组赋给restArr
// 所以上方的代码结果为: first -> 1, restArr -> [2, 3]
const arr = [1, 2, 3];
const [first, second, ...restArr] = arr;
// first -> 1, second -> 2, restArr -> [3]
const arr = [[1, 2, 3], [4, 5, 6]];
const [first, ...restArr] = arr;
// first -> [1, 2, 3], restArr -> [[4, 5, 6]]
const arr = [1, 2, 3, 4];
// 下面这样写会直接报错: SyntaxError: Rest element maust be last element
const [first, ...restArr, fourth] = arr;
同时如果带有rest运算符的变量结构不成功, 默认值不是undefined, 而是[]
(空数组)
const arr = [1, 2];
const [first, second, ...restArr] = arr;
// first -> 1, second -> 2, restArr -> []
不可解构的值
如果等号右边的值是不可遍历的结构, 则会报错, 这里你不必了解太多因为涉及到后方的ES6的其他知识, 当你对ES6掌握更加深入对的时候再回来看会有一些新的感悟
const [first] = 1; // 报错: TypeError: 1 is not iterable,
// undefined, NaN, null, {}, false都会报错
const [fstStr] = 'abc'; // fstStr -> 'a'
变量默认值
ES6允许如果结构赋值中变量未从右边的数据结构中解构到对应的值, 但是我们又不想让他默认值为undefined
或者[]
的话, 可以对其进行默认值处理
如果变量可以解构到值, 则不会走默认值
const arr = [3, 4]
const [first = 1, second = 2, third = 3] = arr;
// 此时first -> 3, second -> 4, third走了默认值3
注意ES6内部是在使用恒等于===
来判断右侧数据结构中对应值是否严格等于undefined, 只要严格等于undefined就会走默认值
const arr = [1, undefined];
const [first = 2, second = 3] = arr;
// first -> 1, second -> 3 因为arr的第二位已经恒等于undefined, 所以second会走默认值
const arr2 = [1, null];
const [fst = 2, sec = 3] = arr2;
// fst -> 2, sec -> null
默认值可以引用解构赋值的其他变量的值, 前提是被引用变量必须必须已经得到声明
const [fst = 1, sec = fst] = []; // fst -> 1, sec - >
const [fst2 = 1, sec2 = fst2] = [10]; // fst2 -> 10, sec2 -> 10
const [fst3 = 1, sec3 = fst3] = [4, 5]; // fst3 -> 4, sec3 -> 5
const [fst4 = sec4, sec4 = 1] = [2, 3]; // 报错因为fst4使用sec4的时候sec4还未得到声明
对象的解构赋值
如果数组的解构让你觉得需求不是那么的大的, 对于我个人而言, 对象的解构赋值让我用了以后就是停不下来
在ES5中我们要获取一个对象中的属性值, 基本是按照如下操作的
var obj = {
name: 'alice',
age: 18,
sex: 'female'
}
var name = obj.name;
var age = obj.age;
console.log(name, age); // 输出alice, 18
让我们看看用ES6中的解构赋值将如何书写
const obj = {
name: 'alice',
age: 18,
sex: 'female'
}
const {name, age} = obj;
console.log(name, obj); // 输出alice, 18
看到上方的操作, 你想到了啥, 我当时想到的是 从今以后如果我只需要对象里的某两个属性我再也不用把一个对象都拿过来操作了
顺道我们来看对象的解构有哪些新的规则
- 在对象的解构赋值中, 因为对象不是想数组一样是有序排列的, 所以一般情况下等号左边往往以键值对的形式出现, 如
const { 你要找的变量: 你希望将找到的属性值赋值给哪个变量 } = 一个对象, 且这是固定格式不可省略
例如:
const obj = {
name: 'loki',
sex: 'male'
}
const { name: userName, sex: userSex } = obj;
// 此时userName -> loki, userSex -> male
// 上面的含义是我去obj中找到 name和sex两个key 并将他们的属性值value赋值给userName 和userSex
- Es6提供给我们一个语法糖, 即如果我们要找的变量和希望被赋值的变量同名的话, 我们可以写成如下
const obj = {
name: 'loki',
sex: 'male'
}
const { name, sex } = obj;
// 此时name -> loki, sex -> male
// 上面的写法等同于 const { name: name, sex: sex } = obj;
但是只要你要找得key值和你希望被赋值的key值不是一致的, 该语法糖不会生效
- 我们同样可以解构复杂的对象, 只要数据解构对的上
const obj = {
name: 'loki',
age: 18,
address: {
province: '上海'
}
}
const { name, address:{ province } } = obj;
// 上面的写法代表我先去找obj中name属性, 同时我外边定义了一个name属性接着他
// 所以name -> loki
// 然后我在找obj的address属性, 然后我拿到address属性我还要进行解构, 我只拿他的province属性
// 并且我外部定义了一个province属性来接受他, 所以province -> 上海
在对象的解构中, 你始终要清晰一点就是:
冒号前边的是你要去找的人, 冒号后边的是你要将这个人找到了以后怎么样
所以我们再来看个例子
const obj = {
name: 'loki',
age: 18
}
let newObj = {};
({name: newObj.name, age: newObj.age} = obj);
// 此时newObj 如下
// {
// name: 'loki',
// age: 18
// }
上面我们是要干啥, 我们想找到obj中的name和age, 将他们的值赋给newObj中的name和age, 在此种情况下, 我们必须用个小括号从外部整句代码括起来, 以告诉js引擎这是一个表达式, 否则会报错, 关于小括号的问题, 在稍后会详细介绍
所以对象解构更加的灵活是毋庸置疑的, 我们甚至可以将一个对象中的两个属性解构出来赋值给一个对象和一个数组
const obj = {
name: 'loki',
age: 18
}
let arr = [];
let newObj = {};
({ name: newObj.name, age: arr[0]} = obj);
// 此时, newObj和arr分别如下
// newObj: {
// name: 'loki'
// },
// arr: [18]
- 同样如若解构失败, 则变量会被赋上默认值为undefined
const obj = {
name: 'loki'
}
const { age } = obj; // age为undefined
对象的解构依旧可以使用rest运算符
const obj = {
name: 'loki',
age: 18,
sex: 'male'
}
const { name, ...restObj } = obj;
console.log(name, restObj);
// name -> loki restObj -> { age: 18, sex: 'male'}
对象解构也可以给予默认值, 同样默认值生效的条件时被解构对象该属性恒等于(===
)undefined
const { name = 'loki' } = {}; // name值为loki
const { name: userName = 'loki' } = { age: 18 }; //userName -> loki
// 上方代码是去等号右边对的对象中寻找name, 并且赋值给userName
// 如果未找到, 则userName默认值为loki
字符串的结构赋值
将对象和数组讲完以后, 也算是两个大头搞定了, 剩下的都是一些小猫小脚, 我们来说说字符串的解构赋值
因为字符串本身也是一个可遍历的数据结构, 有同学说字符串是通过包装类不是它本身可以遍历啊, 你甭管他怎么实现遍历的, 你直接for循环不用其他操作可以取值就是可遍历
字符串遍历很简单, 规则跟数组基本一致
const str = 'abc';
const [fst, sec, third] = str; // fst -> 'a' , sec -> 'b', third -> 'c'
const [fst2, ...restArr] = str; // fst2 -> 'a', restArr -> ['b', 'c']
const [fst3, , ,fourth = 'd'] = str; // fst3 -> 'a', fourth -> d
记住字符串在遍历过程中会转化为类数组, 所以他一定有一个length属性
const str = 'abc';
const [length] = str; // length -> 3
数值和布尔值的解构
数值和布尔值的解构的时候, 内部会隐式的进行包装类, 由于调用包装类形成的对象为下图, 其中的[[PrimitiveValue]]
我们无法访问, 所以对数值和布尔值的解构本身是没有意义的, 但是我们依然可以拿到一些他身上的方法, 尽管也没任何意义, 但是至少我们可以证明他可以被解构
let {toString: s, toFixed: f, valueOf: v } = 123;
console.log(s, f, v); // 输出原型上的三个函数
console.log(s === Number.prototype.toString); // true
函数参数的解构赋值
这个算是比较常用的了, 平时我们总是会向函数中传递参数, 函数参数的解构赋值让我们的代码变得更加简洁也更加容易阅读
// 参数为一个数组的情况下, 我直接解构了这个数组, 在函数中我们就可以直接用x, y了
function add([x, y]) {
return x + y;
}
add([1, 2]);
// 在日常开发中, 我们可能会有一些渲染的操作, 数据结构传递过来可能是一个对象
// 但是这个对象我们确实只要用到其中的一部分属性, 如果是ES5的情况我们可能为了简洁操作直接
// 将对象传递过来, 但是ES6 我们有了结构, 可以稳稳的拿到我们需要的属性也不必费太多的周折
function renderStudentInfo({ name, age }) {
console.log(name, age); // 输出loki, 18
}
const studentObj = {
name: 'loki',
age: 18,
sNo: 123,
address: {
province: '上海'
}
}
renderStudentInfo(studentObj)
解构赋值的常用场景
- 交换变量的值
这个问题我相信大家一定不会陌生, 当我们刚刚接触编程的时候, 总是会编写让两个变量的值交换的过程, 在以前我们往往需要这么写
var a = 10;
var b = 20;
// 交换a和b的值
a = a + b;
b = a - b;
a = a - b;
console.log(a, b); // 20 , 10
或者需要借助第三方变量
var a = 10;
var b = 20;
var c = a;
a = b;
b = c;
console.log(a, b); //20 , 10
在解构中一切变得很简单
let a = 10;
let b = 20;
([b, a] = [a, b])
console.log(a, b); // 20, 10
- 加载模块
使用ES6的模块化或者commonjs的模块的时候, 我们知道暴露出去的往往是一个对象, 我们可以通过解构的方式来拿我们想要的数据
moduleA.js
export const name = 'loki';
export const age = 18;
moduleB.js
// B文件要使用A文件中的变量name和age, 则需要导入
import { name, age } from './moduleA.js';
console.log(name, age); // loki, 18
- 可以很方便的将现有对象的方法提取出出来
const { random, log, sin } = Math;
其实还有很多其他的应用场景比如提取json数据, 遍历map解构等, 不想一一在写了, 朋友在开发中慢慢用熟了就知道应用场景了
一些Tips
- 由于数组本质上是特殊的对象, 所以我们可以使用对象对数组进行解构
const arr = [1, 2, 3];
let {0: fst, 1: sec, ...restArr } = arr;
console.log(fst, sec, restArr); // 1, 2, {2: 3}
// restArr的 {2: 3}, 2 是arr数组的索引
- 解构赋值允许等号左边的模式中不放置任何变量名, 因此可以写出一些怪异的表达式
({} = [true, false]);
({} = []);
({} = 'abc');
上面的表达式毫无意义, 但是却不会报错, 所以面试要注意了哦