## 1.函數的rest參數
ES6 引入 rest 參數(形式為`...變量名`),用于獲取函數的多余參數,這樣就不需要使用`arguments`對象了。rest 參數搭配的變量是一個數組,該變量將多余的參數放入數組中。
~~~javascript
// arguments變量的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest參數的寫法
const sortNumbers = (...numbers) => numbers.sort();
~~~
`arguments`對象不是數組,而是一個類似數組的對象。所以為了使用數組的方法,必須使用`Array.prototype.slice.call`先將其轉為數組。rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法都可以使用。下面是一個利用 rest 參數改寫數組`push`方法的例子。
~~~javascript
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
~~~
>注意,rest 參數之后不能再有其他參數(即只能是最后一個參數),否則會報錯。
~~~javascript
// 報錯
function f(a, ...b, c) {
// ...
}
~~~
### 2.數組的擴展運算符
擴展運算符(spread)是三個點(`...`)。它好比 rest 參數的逆運算,將一個數組轉為用逗號分隔的參數序列。
~~~javascript
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
~~~
該運算符主要用于函數調用。
~~~javascript
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
~~~
擴展運算符與正常的函數參數可以結合使用,非常靈活。
~~~javascript
function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
~~~
擴展運算符后面還可以放置表達式。
~~~javascript
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
~~~
注意,擴展運算符如果放在括號中,JavaScript 引擎就會認為這是函數調用。如果這時不是函數調用,就會報錯。
~~~javascript
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
~~~
#### 替代函數的 apply 方法
~~~javascript
// ES5 的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的寫法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
~~~
下面是擴展運算符取代`apply`方法的一個實際的例子,應用`Math.max`方法,簡化求出一個數組最大元素的寫法。
~~~javascript
// ES5 的寫法
Math.max.apply(null, [14, 3, 77])
// ES6 的寫法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
~~~
上面代碼中,由于 JavaScript 不提供求數組最大元素的函數,所以只能套用`Math.max`函數,將數組轉為一個參數序列,然后求最大值。有了擴展運算符以后,就可以直接用`Math.max`了。
另一個例子是通過`push`函數,將一個數組添加到另一個數組的尾部。
~~~javascript
// ES5的 寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的寫法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
~~~
上面代碼的 ES5 寫法中,`push`方法的參數不能是數組,所以只好通過`apply`方法變通使用`push`方法。有了擴展運算符,就可以直接將數組傳入`push`方法。
下面是另外一個例子。
~~~javascript
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);
~~~
#### 擴展運算符的應用
**(1)復制數組**
ES5 只能用變通方法來復制數組。
~~~javascript
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
~~~
上面代碼中,`a1`會返回原數組的克隆,再修改`a2`就不會對`a1`產生影響。
擴展運算符提供了復制數組的簡便寫法。
~~~javascript
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;
~~~
上面的兩種寫法,`a2`都是`a1`的克隆。
**(2)合并數組**
擴展運算符提供了數組合并的新寫法。
~~~javascript
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并數組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
~~~
不過,這兩種方法都是淺拷貝,使用的時候需要注意。
~~~javascript
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
a3[0] === a1[0] // true
a4[0] === a1[0] // true
~~~
上面代碼中,`a3`和`a4`是用兩種不同方法合并而成的新數組,但是它們的成員都是對原數組成員的引用,這就是淺拷貝。如果修改了原數組的成員,會同步反映到新數組。
**(3)與解構賦值結合**
擴展運算符可以與解構賦值結合起來,用于生成數組。
~~~javascript
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
~~~
另外一些例子
~~~javascript
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
~~~
如果將擴展運算符用于數組賦值,只能放在參數的最后一位,否則會報錯。
~~~javascript
const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯
~~~
## 3.對象的擴展運算符
**(1)解構賦值**
對象的解構賦值用于從一個對象取值,相當于將目標對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。
~~~javascript
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
~~~
上面代碼中,變量`z`是解構賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(`a`和`b`),將它們連同值一起拷貝過來。
由于解構賦值要求等號右邊是一個對象,所以如果等號右邊是`undefined`或`null`,就會報錯,因為它們無法轉為對象。
~~~javascript
let { x, y, ...z } = null; // 運行時錯誤
let { x, y, ...z } = undefined; // 運行時錯誤
~~~
解構賦值必須是最后一個參數,否則會報錯。
~~~javascript
let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤
~~~
上面代碼中,解構賦值不是最后一個參數,所以會報錯。
注意,解構賦值的拷貝是淺拷貝,即如果一個鍵的值是復合類型的值(數組、對象、函數)、那么解構賦值拷貝的是這個值的引用,而不是這個值的副本。
~~~javascript
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
~~~
上面代碼中,`x`是解構賦值所在的對象,拷貝了對象`obj`的`a`屬性。`a`屬性引用了一個對象,修改這個對象的值,會影響到解構賦值對它的引用。
另外,擴展運算符的解構賦值,不能復制繼承自原型對象的屬性。
~~~javascript
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
~~~
上面代碼中,對象`o3`復制了`o2`,但是只復制了`o2`自身的屬性,沒有復制它的原型對象`o1`的屬性。
下面是另一個例子。
~~~javascript
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
~~~
上面代碼中,變量`x`是單純的解構賦值,所以可以讀取對象`o`繼承的屬性;變量`y`和`z`是擴展運算符的解構賦值,只能讀取對象`o`自身的屬性,所以變量`z`可以賦值成功,變量`y`取不到值。ES6 規定,變量聲明語句之中,如果使用解構賦值,擴展運算符后面必須是一個變量名,而不能是一個解構賦值表達式,所以上面代碼引入了中間變量`newObj`,如果寫成下面這樣會報錯。
~~~javascript
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
~~~
解構賦值的一個用處,是擴展某個函數的參數,引入其他操作。
~~~javascript
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用 x 和 y 參數進行操作
// 其余參數傳給原始函數
return baseFunction(restConfig);
}
~~~
上面代碼中,原始函數`baseFunction`接受`a`和`b`作為參數,函數`wrapperFunction`在`baseFunction`的基礎上進行了擴展,能夠接受多余的參數,并且保留原始函數的行為。
**(2)擴展運算符**
對象的擴展運算符(`...`)用于取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。
~~~javascript
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
~~~
由于數組是特殊的對象,所以對象的擴展運算符也可以用于數組。
~~~javascript
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}
~~~
如果擴展運算符后面是一個空對象,則沒有任何效果。
~~~javascript
{...{}, a: 1}
// { a: 1 }
~~~
如果擴展運算符后面不是對象,則會自動將其轉為對象。
~~~javascript
// 等同于 {...Object(1)}
{...1} // {}
~~~
上面代碼中,擴展運算符后面是整數`1`,會自動轉為數值的包裝對象`Number{1}`。由于該對象沒有自身屬性,所以返回一個空對象。
下面的例子都是類似的道理。
~~~javascript
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
~~~
但是,如果擴展運算符后面是字符串,它會自動轉成一個類似數組的對象,因此返回的不是空對象。
~~~javascript
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
~~~
對象的擴展運算符等同于使用`Object.assign()`方法。
~~~javascript
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
~~~
擴展運算符可以用于合并兩個對象。
~~~javascript
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
~~~
如果用戶自定義的屬性,放在擴展運算符后面,則擴展運算符內部的同名屬性會被覆蓋掉。
~~~javascript
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
~~~
上面代碼中,`a`對象的`x`屬性和`y`屬性,拷貝到新對象后會被覆蓋掉。
這用來修改現有對象部分的屬性就很方便了。
~~~javascript
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};
~~~
上面代碼中,`newVersion`對象自定義了`name`屬性,其他屬性全部復制自`previousVersion`對象。
如果把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。
~~~javascript
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
~~~
與數組的擴展運算符一樣,對象的擴展運算符后面可以跟表達式。
~~~javascript
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
};
~~~
- React進階
- React進階-組件 & Props
- React進階-組件 & Props - 代碼篇
- 組件擴展-組件生命周期
- 組件擴展-組件生命周期-代碼篇
- React-Redux
- Redux入門教程-基本用法
- Redux入門教程-基本用法-代碼篇
- Redux入門教程-中間件和異步操作
- Redux入門教程-React-Redux 的用法
- Redux入門教程-React-Redux的用法-代碼篇
- ES6-變量的解構賦值
- 數組的解構賦值
- 對象的解構賦值
- React用法
- JSX技巧
- ES6-神奇的...
- yarn+webpack+react零基礎創建項目
- 0-init
- 1-webpack.config.md
- 2-react相關依賴
- 3.編寫react相關代碼
- pnpx-react-config
- pnpx+create-react
- pnpm+react-config
- pnpm+react-antd