@[toc]
函数的扩展
函数参数的默认值
基本用法
在ES6允许为函数的参数设置默认值,即直接写在参数定义的后面
function Ponint(x = 0 , y = 0){
this.x = x;
this.y = y;
}
const p = new Ponint();
console.log(p); // { x : 0,y : 0 }
优点:
- 书写简洁
- 方便阅读
- 有利于将来代码优化
- 注意:参数变量是默认声明的,所以不能使用
let或const再次声明
function foo(x = 5){
let x = 1; // 报错
const x = 1; // 报错
}
- 使用参数默认值时,函数不能有同名的参数
function foo(x,x,y){
// 不报错
}
function foo(x,x,y=1){
// 报错
}
- 参数默认传值不是传值的,而是每次都是重新计算默认值的表达式的值。参数默认值时惰性求值的。
let x = 99;
function foo(p = x + 1){
console.log(p);
}
foo() // 100
x = 100;
foo(); // 101
与解构赋值默认值结合使用
参数默认值可以和解构赋值的默认值结合起来使用
function foo({x, y = 5}){
console.log(x,y)
}
foo({}); // undefined 5
foo({x:1}); // 1 5
foo({x:1, y: 2}); // 1 2
foo(); // 报错
在这里使用了对象的解构赋值默认值,没有使用函数参数的默认值。当函数foo的参数为一个对象,x、y才能通过解构赋值生成,当没有设置参数时,就会报错。
// 写法1
function foo1({x = 0, y = 0} = {}){
return [x,y];
}
// 写法2
function foo2({x, y} = {x: 0, y: 0}){
return [x,y];
}
// 在函数都没有参数的情况:
console.log(foo1()); // [0,0]
console.log(foo2()); // [0,0]
// 在函数的参数x,y都有值的情况
console.log(foo1({x: 1,y: 2})); // [1,2]
console.log(foo2({x: 1,y: 2})); // [1,2]
// 在函数的参数只有一个有值的情况
console.log(foo1({x: 1})); // [1,0]
console.log(foo2({x: 1})); // [1,undefined]
// 在函数的参数都没有值的情况
console.log(foo1({})); // [0,0]
console.log(foo2({})); // [undefined,undefined]
console.log(foo1({z: 3})); // [0,0]
console.log(foo2({z: 3})); // [undefined,undefined]
两种写法的区别:
- 第一种写法函数参数的默认值是空对象,但是设置了对象解构赋值的默认值
- 第二种写法函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
参数默认值的位置
通常情况:定义了默认值的参数,应该是函数的尾参数,如果非尾部的参数设置了默认值,那么这个参数就没有办法省略了。
function foo(x = 1, y){
return [x,y];
}
console.log(foo()); // [1, undefined]
console.log(foo(2)); // [2, undefined]
console.log(foo(,1)); // 报错
console.log(foo(undefined,2)); // [1, 2]
当默认值的参数不是尾参数时,是不能省略这个参数的,除非设置这个参数为undefined
当只有传入undefined的时候,将会触发参数等于默认值,null是没有这个效果的。
函数的length属性
指定了默认值以后,函数的length属性将返回没有指定默认值的参数个数。
函数的length属性返回值 = 函数的参数个数 - 函数指定了默认值的参数个数
作用域
一旦设置了函数的默认值,函数进行声明初始化的时候,参数会形成一个单独的作用域,等到初始化结束这个作用域就结束了。在不设置参数的默认值的函数中,这个作用域是不存在的。
let x = 1;
function foo(y = x){
let x = 2;
console.log(y);
}
foo(); // 1
函数foo()被调用的时候,参数y = x形成单独的作用域,这个作用域中,变量x没有定义,所以指向外层的全局变量x.函数内部的局部变量不会影响默认值变量。当此时全局变量x不存在的时候,就会报错。
应用
- 利用参数默认值,可以指定某一个参数不可以省略,如果省略抛异常。
function throwOfMissing(){
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwOfMissing()){
return mustBeProvided;
}
foo(); // 报错:Missing parameter
上面的函数foo,如果调用的时候没有参数,就会调用默认值:throwOfMissing(),从而抛出一个错误。函数throwOfMissing()是有圆括号的,说明参数不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会执行。
- 将参数默认值设置为
undefined表明这个参数是可以省略的
function foo( optional = undefined ){ ... }
rest参数
ES6 引入rest参数(形式为:...变量名),用于获取函数的多余参数,这样就不用使用arguments对象了。rest参数搭配的变量是一个数组,这个变量将多余的参数放入数组中。
function add(...values){
let sum = 0;
for(const val of values){
sum += val;
}
return sum;
}
console.log(add(1,2,3,4,5)); // 15
上面的函数,利用 rest参数,可以向这个函数传入任意个数的参数。
例子:rest参数代替arguments变量
// 使用 arguments 变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// 使用 rest 参数的写法
const sortNumber = (...numbers) => numbers.sort();
注意:
rest参数之后不能再有其他参数(即:只能是最后一个参数),否则会报错。- 函数的
length属性不包括rest参数
严格模式的修改
在ES6中,函数参数使用了默认值、解构赋值、或者扩展运算符那么函数内部就不能显示设定为演示模式,否则会报错
使用严格模式:
'use strict';
规避这种限制的两种方法
- 设定全局的严格模式
'use strict';
function foo(a, b = a ){
// ...
}
- 将函数包在一个无参数的立即执行函数里面
const foo = (function () {
'use strict';
return function(value = 42 ){
return value;
}
})
name属性
函数的name的属性返回这个函数的函数名
- 将一个匿名函数赋值给一个变量,ES5的
name属性是空字符串,ES6的name属性会返回实际的函数名
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
- 将一个具名函数赋值给一个变量,ES5和ES6的
name属性都返回这个具名函数原本的名字
const bar = function baz(){};
// ES5
console.log(bar.name); // baz
// ES6
console.log(bar.name); // baz
Function构造函数返回的函数实例,name属性的值为anonymous.
console.log((new Function).name); // anonymous
bind返回的函数,name属性值会加上前缀bound.
function foo(){};
console.log(foo.bind({}).name); // bound foo
箭头函数
ES6 允许使用箭头(=>)定义函数
var f = v => v;
// 等同于
var f = function (v){
return v;
}
- 如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数的部分。
- 如果箭头函数的代码块部分多于一条语句,就使用大括号将它们括起来,并使用
return语句返回。 - 如果箭头函数直接返回一个对象,必须在对象外面加上大括号,否则会报错。
let getTempItem = id => {id: id, name: 'Temp'}; // 报错
let getTempItem = id => ({id: id, name: 'Temp'}); // 不报错
- 特殊情况,可以运行但是会得到错误的结果
let foo = () => {a : 1};
console.log(foo()); // undefined
上面的代码实际执行的语句是:1.
- 如果箭头函数只有一行语句,且不需要返回值,可以采用如下写法:
let fn = () => void doesNotReturn();
- 箭头函数可以与变量解构结合使用
const full = ({first, last}) => first + '' + last;
// 等同
function full(person){
return person.first + '' + person.last;
}
- 需要注意
-
函数体内的
this对象,就是定义时所在的对象,而不是使用时所在的对象。 -
不可以当作构造函数,也就是不可以
new.否则会抛出一个错误。 -
不可以使用
arguments对象,这个对象在函数体内不存在,如果需要使用,可以使用rest参数代替。 -
不可以使用
yield命令,因此箭头函数不能用做Generator函数。
- 对于
this对象指向是可变的,但是箭头函数中,它是固定的。- 指向外层函数的对应变量:
arguments、super、new.target在箭头函数中是不存在的- 由于箭头函数没有自己的
this,所以就不能在用call()、apply()、bind()这些方法去改变this的指向。
不能使用箭头函数的场景
由于箭头函数使得this从''动态''变成了''静态''
- 定义对象的方法并且这个方法内部包括
this.
const cat = {
lives: 9,
jumps: ()=>{
this.lives--;
}
}
这里的cat.jumps()方法是一个箭头函数,这里的错误的,当这里为普通函数的时候,这个方法内部的this指向的是cat,如果是箭头函数就使得this指向全局对象了,从而得不到预期的结果。
因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就成了全局作用域。
- 需要动态
this的时候,也不应该使用箭头函数
const button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
}); // 点击时报错
因为button的监听函数是一个箭头函数,导致函数中的this就是全局对象,如果改成普通函数,this就会动态指向被点击的按钮对象
嵌套的箭头函数
箭头函数内部,还可以使用箭头函数。
const insert = (value) => ({
into: (array) => ({
after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}
})
});
console.log(insert(2).into([1,3]).after(1)); // [1,2,3]
尾调用优化
尾调用(Tail Call)是函数定义
尾调用(Tail Call)是函数式编程的一个重要的概念,就是指某一个函数的最后一步式调用另一个函数。
function f(x){
return g(x);
}
这里的函数f的最后一步就是调用函数g,这就是尾调用。
以下情况都不是尾调用
// 一
function f(x){
let y = g(x);
return y;
}
// 二
function f(x){
return g(x) + 1;
}
// 三
function f(x){
g(x);
}
尾调用不一定出现在函数尾部,只要是最后一步操作就可以了。
function f(x){
if(x > 0){
return m(x);
}
return n(x);
}
上面也是尾调用函数
尾调用优化
函数在调用的时候会在内存形成一个调用记录,又称“调用帧(call frame)”,保存调用位置和内部变量信息。如果函数A内部调用函数B,那么A的调用帧上方会形成一个B的调用帧。等B运行结束了,将结果返回给A,B的调用帧才会消失。如果函数B内部还调用函数C,那么还有一个C的调用帧,以此类推。所有的调用帧就形成一个“调用栈(call stack)”.
尾调用由于是函数的最后一个操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,所以直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
function f(){
let m = 1;
let n = 2;
return g(m+n);
}
f();
// 等同于
fucntion f(){
return g(3);
}
// 等同于
g(3);
函数参数的尾逗号
ES2017允许函数的最后一个参数有逗号。在此之前,函数在定义和调用时都不允许最后一个参数后面出现逗号。
这样规定使得函数参数与数组和对象的尾逗号规则保持一致了。
Function.prototype.toString()
ES2019对函数实例的toString()方法返回代码本身,一模一样的原始代码,不会省略注视和空格。
在此之前,toString()方法返回的代码会省略注视和空格。
catch命令的参数省略
JavaScript 语言的try...catch解构,之前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。
try{
// ...
}catch (err){
// ...处理错误
}
在ES2019以后,catch语句省略参数
try{
// ...
}catch{
// ...
}
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。