本文是一系列就阮一峰的《ECMAScript 6 入门》的学习笔记。
附上篇零:ES6学习笔记 篇零 变量声明
JS赋值有传值赋值、引用赋值两种,ES6又为其添加了解构赋值这一方式。
JavaScript语言可以识别下面 7 种不同类型的值:
对于Object对象,采用的是引用赋值。
传值赋值
对于六种原型数据类型,赋值采用的为传值赋值。
eg:
var a = 'a';
var b = a;
a = 1;
console.log(a, b);
output-->1 'a';
通过篇零我们知道,赋值存在变量提升的问题。代码可以转化为:
var a; // 声明变量a(未为a分配实际内存)
a = 'a'; // 为变量a赋值(为a分配实际内存)
var b; // 声明变量b
b = 'a'; // 为变量b赋值(为a分配实际内存)
a = 1; // 改变a的值为1
由此可知,变量a和变量b是两个独立的个体,每个变量在被 赋值后 得到了 独立存在的 实际内存。
直接按值存放,所以可以直接访问。所以不论他们之间的值怎样变化、怎样相互赋值,都是将value放在各自的篮子里罢了,互不干扰。
引用赋值
存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。
当我们需要访问引用类型(如对象,数组,函数等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得对应数据。
图1可表示传值赋值与引用赋值的区别:
更多内容可参考: JS堆栈与拷贝
来一个习题练练手:
eg1:
var a = [1, 2, 3]; ①
var b = a; ②
a = [4, 5, 6]; ③
console.log(b);
==============
output--> [1, 2, 3]
解:
①在栈里放入名为a的对象,以指针的方式获取其挂载在堆内存中的方法或属性。
a指向的是[1, 2, 3]。
②接下来又将a的引用地址传递给b,此时b也指向a的指向,a和b指向的是同一个[1, 2, 3]。
③关键来了,这时a = [4, 5, 6]
可以翻译为a将指向改为[4, 5, 6],而不是改掉第一个指向堆内存中的值。
④所以此时b的指向依然是[1, 2, 3]。
eg2:
var a = [1, 2, 3]; ①
var b = a; ②
a.pop(); ③
console.log(b);
================
output-->[1, 2]
解:
①同上一题的①。声明并引用赋值。
②同上一题的②。将a的引用地址赋给b。两者指向同一个值。
③对a的指向进行pop,且a和b共享一个指向,那么b的指向就是[1, 2]。
eg3:
var a = {count: 2}; ①
var b = a.count; ②
a.count = 3; ③
console.log(b);
==============
output-->2
解:
①同上一题的①。声明并引用赋值。
②同上一题的②。将a的count属性的 值 传值赋值给b。即b = 2;
,一个普通的传值赋值。
③修改a指向的count属性的值。a为对象,b是一个值为2的参数。不存在引用关系。
解构赋值(ES6新特性)
让赋值变得更轻松。
当我们需要为多个变量赋值的时候,我们曾经是这么做的:
var a = 1;
var b = 2;
var c = 3;
一共要三行。
解构赋值的出现可以让我们将上面代码浓缩成一句话:
var [a, b, c] = [1, 2, 3];
也就是说,解构赋值是指从数组中提取值,按照对应位置,对变量赋值。
这是方法一:数组解构。
解构不成功:
不成功是因为表达式左右两边的变量与值的对应关系出现错误且导致有变量被赋值为undefined:
eg1:
let [b] = []; // b为undefined
let [a, b] = [1]; // a为1,b为undefined
下面给出一些解构成功的例子
eg2:
let [e, f, g] = [1, [2, 3], 4]; // e: 1; f: [2, 3]; g: 4; ①
let [a, [b], d] = [1, [2, 3], 4]; // a: 1; b: 2; d: 4; ②
let [i, , j] = [1, 2, 3]; // i: 1; j: 3; ③
let [m, ...n] = [1, 2, 3]; // m:1, n:[2,3] ④
let [o=5, p=7] = [1]; // o:1, p:7 ⑤
let [x, ...y,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma ⑥
解:
①由对应关系可知,以数组的形式,e,g被赋值为字符串,而f为数组。
②a和d的值无异议,关键在于b。[b]与[2, 3]结构对应,所以解构之后b为2。
③不完全解构,但依旧解构成功。两个变量i,j都被赋上了值。
④m被赋值为1,n由于扩展运算符的存在,使用剩余模式,将数组剩余部分赋值给n,n为[2, 3]。
⑤为了防止某些变量被赋值为undefined,为期赋上默认值。
⑥左侧中括号封闭前最后一个字符必须是变量。
方法二:对象解构。
二者不同之处在于,数组解构按照从左至右一一对应的方式为变量赋值,值由变量的位置决定;
而对象解构,对象的属性没有次序,赋值的依据由且仅由变量的属性名决定,变量必须与属性同名,才能取到正确的值。
eg1:
let { john, mary } = { john: 'aaa', mary: 'bbb' }; // john: 'aaa'; mary: 'bbb'; ①
let { mary, john } = { john: 'aaa', mary: 'bbb' }; // john: 'aaa'; mary: 'bbb'; ②
let { amy } = { john: "aaa", mary: "bbb" }; // amy: undefined ③
let {ben = 10, jack = 5} = {ben: 3}; // ben: '3'; jack: 5; ④
const { Loader, main } = require('toolkit/loader'); // ⑤
解:
①key 和 value 一一对应,解构成功。
②告诉我们在对象解构中左边变量的顺序并不影响赋值。只要key一一对应即可。
③左边的key没有出现在右边的key中,结构失败,amy值为undefined。
④为了防止某些变量被赋值为undefined,为期赋上默认值。
⑤解构赋值可以帮助加载一个模块的特定子集,(node和react中很常见)。
对象解构赋值的嵌套使用:
和数组一样,对象解构赋值也可使用嵌套的方法。
eg2:
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
// output--> x: ‘Hello'; y: 'World'; ①
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
// output--> x: ‘Hello'; y: 'World'; p: ["Hello", {y: "World"}] ②
解:
①结构相同,key 和 value 一一对应,解构成功。
②在①的基础上在左侧加了p变量,所以将obj里的p也有了key,一同被解构成功。