Part2 函数的扩展
2.1 函数参数默认值,可以传任意类型
函数名 = ([param1, ..., params=默认值]) => { }
// 等同于
function 函数名([param1, ..., params=默认值]) = { }
2.1.1 es5采用变通方法实现函数参数默认值,为什么要给函数扩展参数默认值?
function foo(a, b) {
b = b || 5;
console.log(a + b);
}
foo(1); // 6
foo(1, 2); // 3
foo(1, ''); // 6
foo(1, 0); // 6
上面代码检查函数doSomething的参数b有没有赋值,如果没有,则指定默认值为5。这种写法的缺点在于,如果参数b赋值了,但是对应的布尔值为false (0、-0、null、""、false、undefined 或者 NaN)
,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。 当然es5也可以去处理这几种特殊情况,不过es6给出更好的解决办法:给函数的参数扩展了默认值写法。
2.1.2 es6函数参数默认值可以传任意类型
es6 允许函数的参数设置为默认值,既直接写在参数定义的后面,先判断一下参数b是否被赋值,如果没有,再等于默认值,es6 允许函数的默认值可以传任意类型。
// 参数默认值为数字
function foo (a, b=5) {
console.log(a + b);
}
foo(1); // 6
// 参数默认值为字符串
function foo (a, b='World') {
console.log(a + b);
}
foo('Hello'); // "Hello World"
// 参数默认值为对象
function doSomething (a, b={x: 1, y: 2}) {
console.log(a + b.x + b.y);
}
foo(1, {x: null, y: 2}); // 3
// 默认值为解构赋值 (不建议)
function foo ({x, y = 2}) {
console.log(x, y);
}
foo({}) // undefined 2
foo({x: 1}) // 1 2
foo({x: 1, y: 3}) // 1 3
foo() // TypeError: Cannot read property 'x' of undefined
// 默认值为解构赋值:参数是一个对象 (推荐)
function foo ({x, y = 2} = { }) {
console.log(x, y);
}
foo() // undefined 2
函数参数默认值可以使用传任意类型,以上展示了数值、字符串、对象、解构赋值的写法,这种写法优势是:便于阅读,利于优化。
2.1.3 参数变量是默认声明时,不能用let或const再次声明。
(function foo (a) {
var a = 1;
console.log(a);
})()
// 1
( function foo (b) {
let b = 2;
console.log(b);
})()
// 报错:Identifier 'b' has already been declared
( function foo (c) {
const c = 3;
console.log(c);
})()
// 报错:Identifier 'c' has already been declared
以上例子,可以看出,在函数体中,不能用let或者const再次声明,否则会报错。
(4) 使用参数默认值时,函数不能有同名参数。
// 不报错
function foo(x, x, y, y) {
// ...
}
// 报错
function foo(x, y, y = 1) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
上面代码中,跟默认值参数名同名的时候报错,如果我跟默认值参数名不重名,跟其他的非默认值参数重名可以么?也是不可以的,只要使用了参数默认值,就不能有同名参数。
(5) 参数默认值的位置--尾参数
function foo(x, y = 2, z) {
return [x, y, z];
}
foo(1, ,2); // 报错
foo(1, undefined, 2); // [1, 2, 2]
foo(1, null, 2); // [1, null, 2]
如果默认值得参数不是尾参数,这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。传入undefined的,将触发该参数等于默认值,null则没有这个效果。为了避免这个问题,建议带参数默认值的函数,默认值参数设为尾参数。
正确写法:
function foo(x, z, y=5) {
console.log(x, z, y);
}
2.2 rest参数,必须只能是最后一个参数
rest参数说白了就是扩展运算符参数,形式为...x
,代表实参为数组的传进来的x变量,用于获取函数的多余参数。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。由于rest参数是一个真正的数组,数组持有的方法都可以使用。
function add (...items) {
let s = 0;
items.forEach(function(item) {
s += item;
});
return s;
}
add(1,2,3); // 6
在es5中arguments可以用来获取函数参数集合的类数组对象。由于arguments对象不是真正的数组,要想使用真正的数组方法,必须使用 Array.prototype.slice.call(arguments)
转为数组。
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
sortNumbers(3, 1, 0, 2); // [0, 1, 2, 3]
// rest参数的写法
sortNumbers = (...numbers) => numbers.sort();
sortNumbers(3, 1, 0, 2); // [0, 1, 2, 3]
显而易见,rest 参数的写法更自然也更简洁。对于rest参数,就可以替代arguments类数组,不需要 arguments对象了。
注意:rest 参数只能是最后一个参数,否则会报错
// 报错:Rest parameter must be last formal parameter
function foo(a, ...b, c) {
// ...
}
// 报错:Rest parameter must be last formal parameter
function foo(a, ...b, ...c) {
// ...
}
function foo(a, b, ...c) {
// ...
}
第一种情况,rest 参数没有放在最后的位置;第二种情况,虽然参数c是最后一个参数, 但是b却不是,就说明了rest参数只能有一个扩展运算符;第三种情况满足条件。
注意:一个函数只能有一个rest参数,并且rest参数只能是最后一个参数。
2.3 严格模式
从es5开始,函数内部可以设定为严格模式。
function foo(a, b) {
'use strict';
// code
}
es6 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错
function foo(a, b = a) {
'use strict';
// to do something
}
// 报错
const foo = function ({a, b}) {
'use strict';
// to do something
};
// 报错
const foo = (...a) => {
'use strict';
// to do something
};
两种方法可以规避这种限制。
第一种是设定全局性的严格模式,这是合法的。
// 第一种是设定全局性的严格模式
'use strict';
function foo (a, b = a) {
// code
}
// 第二种是把函数包在一个无参数的立即执行函数里面
foo = (function () {
'use strict';
return function (value = 42) {
return value;
};
}());
2.4 箭头函数 Arrow functions
es6 中对函数的扩展多出了一个新东西“箭头函数”,可以说箭头函数是es6里使用频率最高的一个语法糖,箭头函数相当于匿名函数,并且简化了函数定义,es6 允许使用箭头 () => {}
定义函数。Arrow functions
命名箭头函数:
functionName = ([param1, ...]) => {
todoSomething
}
匿名箭头函数:
([param1, ...]) => {
todoSomething
}
一条语句,不同的参数的写法如下:
// 不传参数的箭头函数,使用一个圆括号代表参数部分
{
let foo = () => 5;
foo (); // 5
}
// 只传一个参数的箭头函数,参数部分的圆括号可以省略
{
let foo = a => a;
foo(5); // 5
}
// 传多个参数的箭头函数,使用圆括号代表参数部分
{
let foo = (a, b) => a + b;
foo(5, 10); // 15,
}
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }
和return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }
和return
。下面展示几个例子:
// 包含多条语句的箭头函数
{
let foo = (a, b) => {
console.log(a); // 5
console.log(b); // 10
console.log(this); // Window
return a + b;
};
foo(5, 10); // 15
}
// 带有默认值参数的箭头函数
foo = (a, b=5) => a + b;
foo(1); // 6
// rest参数的箭头函数
// 箭头函数不可以使用arguments对象,若想使用,可以用 rest 参数代替
{
let add = (...items) => {
let s = 0;
items.map( item => {
s += item;
});
return s;
}
add(1,2,3); // 6
}
箭头函数很适合于无复杂逻辑或者无副作用的纯函数场景下,比如map
, filter
等回调函数定义中。
2.5 箭头函数中的this
2.5.1 普通函数中的this
- 如果调用函数属于某个对象,那么函数内的this指向该对象
- 在普通函数中,this指向它的直接调用者;如果找不到直接调用者, 则是函数独立调用,this则指向window
- 在严格模式下,没有直接调用者的函数中的this是undefined
- 如果是构造函数,那么函数内的this则指向实例化对象
- 使用call, apply,bind绑定的,可以改变this的指向
2.5.1 箭头函数中的this指向
箭头函数里面的this, 跟es5里普通函数里的this不一样的,箭头函数中的this始终指向函数定义时的作用域的this。实际原因是箭头函数根本没有自己的this,导致内部this就是外层代码块的this。
// 箭头函数中的this,在事件监听中的应用
<button id="button">按钮</button>
<script>
(function (d, log) {
let obj = {
a: 5,
clickHandle() {
log('这是事件监听内部')
log(this) // this指向obj这个对象
},
init() {
let btn = d.querySelector('#button');
let _this = this;
btn.addEventListener('click', (e) => {
log(this); // this指向obj这个对象
log(e.target); // 获取被点击的按钮
e.target.innerHTML = '点过'; //去操作按钮自身的属性
this.clickHandle(); // 调用外层对象的方法
});
}
};
obj.init();
})(document, window.console.log)
</script>
不要在最外层定义箭头函数,因为在函数内部操作this
会很容易污染全局作用域。最起码在箭头函数外部包一层普通函数,将this
控制在可见的范围内。
2.5.2 不要滥用箭头函数
// 普通的构造函数
function Person( name){
console.log(this);
this.name =name;
}
var p1=new Person('小王'); // Person {name: "小王"}
var p1=new Person('小李'); // Person {name: "小李"}
// 箭头函数不能用作构造函数
let Person = ( name) => {
console.log(this);
this.name =name;
}
let p1=new Person('小王'); // 报错:Person is not a constructor
let p1=new Person('小李'); // 报错:Person is not a constructor
Person构造函数的this,指向每次实例化的对象,若是用箭头函数改写构造函数,就会改变this的指向外层的this对象。所以箭头函数不能用作构造函数。除此之外箭头函数还有一些不能使用的情况,大家可以参考这篇文章:王仕军写的 什么时候你不能使用箭头函数?。
箭头函数最吸引人的地方是简洁。在有多层函数嵌套的情况下,箭头函数的简洁性并没有很大的提升,反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。
箭头函数有几个使用注意点。
(1)箭头函数体内的this对象,就是`定义`时所在的对象,而不是`调用`时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会报错。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
以上就是函数扩展的主要内容,其中尤为重要的是默认值参数和箭头函数。
小结
-
函数参数默认值:
- 可以传任意类型
- 不能用let或const再次声明
- 一个函数只能有一个rest参数,只能是最后一个参数
-
箭头函数:
- 参数不传、只传一个,传多个,以及代码块为单行、多行的书写形式
- 箭头函数中的this指向
网友评论