1.允许设置默认值
1.1.形参允许设置默认值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
参数变量是默认声明的,所以在函数体中不能用let或const再次声明,不然会报错。
定义了默认参数值时,函数不能有同名参数(包括没有设置默认参数的)。否则报错
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
还有一点,参数的默认值,不会定义时直接传值,而是每次调用函数时重新计算默认参数的值。(惰性求值)
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100。
1.2.与赋值解构默认值结合使用
function foo({x = 6, y = 5, z}) {
console.log(x, y);
}
foo({}) // 6 5 undefind
foo({x: 1}) // 1 5 undefind
foo({x: 1, y: 2}) // 1 2 undefind
foo() // TypeError: Cannot read property 'x' of undefined
上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
上述代码,函数执行时若没有传参,将默认为一个空对象
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
// 报错
上述代码,第二个参数是个对象,当调用函数传参时,不传参数则报错。这段例子,也与上面的例子很相似。都表明了当参数是对象时,并且对象没有设置默认值,当执行函数没有传参,便会报错。
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
上面的两种方法对函数的参数都设置了默认值,区别是写法一设置了函数赋值解构的默认值,又让参数默认值为一个空对象。
写法二则没有设置函数赋值解构的默认值,又让参数等于了一个带有具体属性(键值对)的对象
1.3.参数默认值的位置
通常函数默认值,应该是函数的尾部参数,这样会比较容易看出来
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
上面代码很好的展示了设置了默认值的参数,在使用函数参数时是不可以省略的。
传undefined会触发默认值,传其他值时不会触发。
1.4.设置了参数默认值函数的作用域
若设置了函数的默认值,函数进行声明初始化时,函数会形成一个单独的作用域,在初始化之后,这个作用域就会消失
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
上述代码,因为 调用函数时需要声明初始化,这时函数是一个单独的作用域,所以这时 y = x 中的 x 指向同一作用域的 x(第一个参数)
注:这种语法行为,在没有参数默认值时是不会生效的
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
函数运行时 函数体内部也是一个单独的作用域,而 参数中的 x 找不到内部的,所以只能向外层寻找,所以输出的 1,函数体内部的 x 无法影响到默认值变量 x 。
若此时不定义全局变量 x 就会报错
若定义的 let 声明去拿前面的值,页面也会报错
var x = 1;
function foo(x = x) {
// ...
}
foo() // ReferenceError: x is not defined
上面的代码中,参数(x = x)会形成的单独作用域,实际执行的是let x = x ,因为 let 声明的暂时性死区,导致报错。
默认值设置为函数
function bar(func = () => foo) {
let foo = 'inner';
console.log(func());
}
bar() // ReferenceError: foo is not defined
上面代码中,匿名函数里面的 foo 指向函数外层,但是函数外层并没有声明变量foo,所以就报错了。
其实就是参数与函数体中的代码块是两个不同的作用域,所以参数的函数中的 foo 只能向外找,找不到就报错了。
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
如果将 foo 函数体内的 var x = 3 中的 var 去除,那么 y( ) 运行时 ,函数中的 x = 2 会重新赋值给 foo 函数体中的 x = 3,这是函数体中的 x = 2,而全局的 x 不变。
在函数作用域中,不使用变量关键字声明的变量,在赋值时会往上一级作用域寻找已经声明的同名变量,直到全局作用域时还没找到,则会成为window的属性
1.5.设置函数默认值的实际应用
利用默认值,可以指定摸一个参数不得省略,如果省略就抛出错误
function throwIfMissing() {
throw new Error('此参数不得省略');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。从上面也可以了解到,在设置默认值为函数时,后面跟了一个括号,所以默认值为函数时,是在运行时才能执行,在定义时不会因为有括号而执行
另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。
function foo( a = undefined) { ··· }
1.6.函数的length属性
在函数定义了默认值后,函数的length属性,将会返回没有指定默认值的个数。也就是说,指定了默认值后,length属性会失真
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。使用了标签模板传参时,{ }中变量length属性也会失效。
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
2.rest参数
2.1rest参数的使用
ES6规定了 rest参数,它用于获取函数的多余参数,多于的参数以数组形式储存,这样可以不需要arguments对象了
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
上面代码为一个求和函数,add中的形式参数(…values)为 rest参数的形式,可以向函数中传入任意数量的参数
下面是一个 rest 参数代替arguments变量的例子。
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
JavaScript中的Array.prototype.slice.call(arguments)能将有length属性的对象转换为数组(特别注意:
这个对象一定要有length属性).
但有一个例外,IE下的节点集合它不能转换(因为IE下的dom对象是以com对象的形式实现,js对象和com对象不能进行转换)
方法详情请点击此处
注意:
- rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
- 函数的length属性,不包括 rest 参数。
3.函数的严格模式
从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a, b) {
'use strict';
// code
}
ES6规定:只要函数中使用了 默认值、赋值解构、或是扩展运算符,那么就不能设为严格模式,会报错。
// 报错
function doSomething(a, b = a) {
'use strict';
// code
}
// 报错
const doSomething = function ({a, b}) {
'use strict';
// code
};
// 报错
const doSomething = (...a) => {
'use strict';
// code
};
const obj = {
// 报错
doSomething({a, b}) {
'use strict';
// code
}
};
这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。
所以导致了,若是函数参数中在严格模式下会报错,参数会先执行,然后在函数体中发现 ‘use strict’ 时,才导致报错。
解决方法:
1.在全局设置严格模式,这样会导致全局的代码都在严格模式下运行
2.把函数包在一个无参数的立即执行函数里面,在外层的函数设置严格模式。
4.函数的name属性
函数的name属性,返回该函数的函数名。
function foo() {}
foo.name // "foo"
//这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准。
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
上面代码中,变量f等于一个匿名函数,ES5 和 ES6 的name属性返回的值不一样。
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
当Function构造函数返回的函数实例,name属性的值为anonymous。
(new Function).name // "anonymous"
5.箭头函数
内容较多详情请点击此处
6.更多新特性
还有一些新特性没有列出可,可以在《ECMAScript 6 入门》中查看
以上内容参考于 阮一峰老师 的《ECMAScript 6 入门》