ES6 随记(3.4.1)-- 函数的拓展(参数默认值,扩展运算符)
上一章请见:
4. ES6 随记(3.2)-- 正则的拓展 & 数值的拓展
4. 拓展
e. 函数的拓展
· 参数默认值。此节与结构赋值关系甚大,可去往前文再看一次
再来烧脑的重新打开这个蛋疼的案例,
function fn1({x = 0, y = 0} = {}) { console.log(x, y); } function fn2({x, y} = { x: 0, y: 0 }) { console.log(x, y); } fn1() // 0, 0 fn2() // 0, 0 fn1({x: 3, y: 8}) // 3, 8 fn2({x: 3, y: 8}) // 3, 8 fn1({x: 3}) // 3, 0 fn2({x: 3}) // 3, undefined fn1({}) // 0, 0 fn2({}) // undefined, undefined fn1({z: 3}) // 0, 0 fn2({z: 3}) // undefined, undefined
但需要一提是函数参数作用域问题。
(写了好些次,还是无法清晰的描述这个现象,我尽力了)
参数,默认值,函数体是同一个作用域,所以不妨按严格的先后关系来运行,由参数开始先声明新变量,然后判断传参有无解析默认值,再运行函数体。
let a = 10; function fn1(b = a+1) { let a = 100; console.log(b,a); } fn1(); // 11, 100 a = 20; fn1(); // 21, 100 function fn2(a, b = a) { console.log(a, b); } fn2(); // undefined, undefined fn2(1); // 1, 1 // 同一作用域内同名变量不能声明两次 function fn3(x) { let x = 999; console.log(x); } fn3(); // 报错:Identifier 'a' has already been declared, // function fn4(x = x+1) 也会报错,同理
而默认值是函数时,这个问题就有些妖了,不过也还是符合规则的。
let a = 1; function test(fn = x => a) { // 此 a 已解析,参数作用域里没有,向上寻找 let a = 10; console.log(fn()); // 故而参数中的 a 和函数内的 a 早就没关系了 } test(); // 1 console.log(a); // 1 let x = 1; function test2(x, y = function(){ x = 2; }) { var x = 3; y(); // y 中的 x 只与参数 x 有关,var x 后,就是新的 x 不是参数 x 了, 所以不改变 var x 的值 console.log(x); } function test3(x, y = function(){ x = 2; }) { x = 3; y(); // 此处所有的 x 都是参数 x 的修改,所以可被修改 console.log(x); } test2(); // 3 console.log(x); // 1 函数内都有自己的 x,在自己作用域中,所以就不影响商机作用域了 test3(); // 2 console.log(x); // 1
所以判断传参有无再解析默认值这个也可以有些其他应用,比如默认值是自运行函数
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(arg = throwIfMissing()) { return arg; } // 没有传参选用了默认值,所以解析直接运行了 throwIfMissing, // 否则是不运行的那个自运行函数的 console.log(foo(1)); // 1 foo(); // Uncaught Error: Missing parameter
注:当传入参数为 null 时不会选用默认值,而 undefined 会。
function foo(x = 1, y = 1) { console.log(x, y); } foo(undefined, null); // 1, null
再者,非最后一位参数传入空值,会报错,显而易见的。
fn(, 3); // 报错 fn(1, , 3); // 报错
函数的 length 属性会被默认值打断,虽然并不知道这个有啥实际用途,但还是抄下来吧
(function (a) {}).length; // 1 (function (a = 5) {}).length; // 0 (function (a, b, c = 5) {}).length; // 2 (function(...args) {}).length // 0
· 拓展运算符 ...
1. 剩余参数非常好理解,也非常好用
function add(first, ...items) { return first + items.reduce((pre, cur) => pre + cur); } console.log(add(10, 2,3,4)); // 19
2. 数组的拆分
console.log(...[1,2]); // 1 2 let point1 = [0, 0], point2 = [3, 4]; function distance(x1, y1, x2, y2){ return Math.hypot((x2-x1), (y2-y1)); } console.log(distance(...point1, ...point2)); // 5
3. 转化类数组数据
[...arguments] [...document.querySelectorAll('div')]
4. 可取代 apply
Math.max.apply(null, [14, 3, 77]); Math.max(...[14, 3, 77]); Array.prototype.push.apply(arr1, arr2); arr1.push(...arr2);
5. 解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5]; console.log(first, rest); // 1 [2, 3, 4, 5]
6. 帮助解决字符串 UTF-16 问题
console.log([...'x\uD83D\uDE80'].length) // 2 console.log([...'x\uD83D\uDE80'].reverse().join('')); // 🚀x
· 'use strict' 的不同
由于现在默认值的解析是在函数体之前的,所以默认值解析完再设定严格模式当然有时会产生错误。
因此,现在的 'use strict' 只能放在全局或者没有参数的函数里
本文部分转载自 阮一峰 的 ECMAScript 6 入门