ES6中函数的扩展

函数参数的默认值

1. 基本用法

之前不能为参数指定默认值,必须写判断

js 复制代码
function log(x, y) {
    if(typeof y === 'undefined') {
        y = 'world';
    }
    console.log(x, y);
}
log('hello'); // hello world
log('hello, 'china'); // hello china

现在可以直接写默认值,直接写在参数的后面

js 复制代码
function log (x = 'hello', y ='world') {
    console.log(x, y);
}
log(); // hello world
log('1'); // 1 world
log(1, 2); // 1 2

好处:可以让人知道那些参数是可以省略的,利于代码优化

参数变量是默认声明的,所以不能在用let和const再次声明,否则会报错。

参数默认值不是传值,每次调用函数会重新计算默认值表达式的值,不是一成不变的,若表达式值有所变化,默认值也会随之改变

js 复制代码
let x = 9;
function foo(y = x + 1) {
    console.log(p);
}
foo(); // 10
x = 10;
foo(); // 11

2. 与解构默认值一块使用

函数的参数可以解构、解构也可以设置默认值。也叫双重默认值。

解题方法先考虑解构,再考虑参数的默认值

js 复制代码
function foo({x, y = 5}) {
    console.log(x, y);
}
foo({}); // undefined 5

例子:

js 复制代码
function m1({x = 0, y = 0} = {}) {
    return [x, y];
}

function m2({x, y} = {x = 0, y = 0}) {
    return [x, y];
}

m1(); // [0, 0]   先看解构,右侧没有值,但左侧有解构默认值为x = 0, y = 0
m2(); // [0, 0]     先看解构,解构右侧没有值,左侧解构出来了,但没有默认值,应该为undefined,但参数有默认值,所以以默认值为准x = 0, y = 0

m1({x: 3, y: 8}); // [3, 8] 直接解构
m1({x: 3, y: 8}); // [3, 8]  直接解构

m1({x: 3}); // [3, 0] 直接解构 右侧x存在为3, y不存在但解构出来y有默认值
m2({x: 3}); // [3, undefined]

m1({}); // [0, 0]  直接解构
m2({}); // [0, 0]

3. 参数默认值的位置

如果参数有默认值,要写在参数的末尾。

因为要不是写尾部,则必须填写这个参数,否则将这个参数写成undefined才能使这个参数的默认值有效。

js 复制代码
function f(x = 1, y) {
    return [x, y];
}

f(, 1); // 报错

4. 函数的length属性

如果某个参数设置了默认值,则length属性时返回是不包括该参数

js 复制代码
function f1(x, y, z = 1) {
    
}
f1.length // 2  因为参数z设置了默认值

因为函数length属性含义是该函数预期要传入的参数个数,因为某个参数有了默认值,则不用传也没事,所以就不包括。

5. 作用域

只有在参数有默认值的情况下存在,如果参数一旦有了默认值,函数进行声明初始化时,参数就就会形成一个作用域,等到初始化结果后,这个作用域才会消失。

js 复制代码
let x = 1;
function f(y = x) {
    let x = 2;
    console.log(y);
}
f(); // 1

在调用f时,参数y = x 会形成一个作用域,在这个作用域中x没有声明,就会往上找,找到全局x,赋值给参数y。如果全局也没有x,就会报错。

也存在暂时性死区,不能没有声明前就使用变量。

6. 应用

利用参数默认值可以指定某个参数不能省略,如果省略就报错。

js 复制代码
function throwIfMissing() {
    throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
    return mustBeProvided;
}
foo(); // 报错

上面代码中,如果调用foo函数时,不传参数就直接会走默认值,而默认值是一个抛异常的函数。

rest参数

形式为...变量名,用来获取函数的多余参数。这样就不必使用arguments对象,rest参数搭配的变量是一个数组,该变量将多余的参数放入其中。

js 复制代码
function add(...values) {
    console.log(values);
}
add(1, 2, 3); // [1, 2, 3]

rest参数必须写在最后,否则就会报错。

函数中的length属性不会计算rest参数

js 复制代码
(function(a) {}).length // 1
(function(...a){}).length // 0

严格模式

如果函数参数使用了默认值,解构赋值或者扩展运算符,那么函数内部就不能显式设定严格模式,否则就会报错。

js 复制代码
// 报错
function foo(a, b = a) {
    'use strict'
    // code
}

原因是因为,函数执行时,先执行参数,在执行函数体,而是否使用严格模式是在函数体内才知道。

例如:

js 复制代码
function doSomething(value = 070) {
    'use strict'
}

在严格模式下八进制不能使用0表示,但是一开始的时候可以执行成功,后来又报错。

解决方法

设置成全局的严格模式

js 复制代码
'use strict'
function foo (x =1) {}

将函数包在一个无参的立即执行函数内

js 复制代码
const doSomething = (function() {
    'use strict'
    return function(value = 42) {
        return value;
    }
}())

name属性

返回该函数的函数名。

在ES5中一个匿名函数赋值给变量时,name返回空字符串,而在ES6中会返回实际的函数名。

Function构造函数返回的函数实例,name属性的值为anonymous

(new Function).name // anonymous

bind返回的函数,name属性值会加上bound前缀

js 复制代码
function foo() {}
foo.bind({}).name // bound foo

(function(){}).bind({}).name // bound

5 箭头函数

使用一个箭头来定义函数,当参数为多个时,参数部分用括号包裹。

和变量解构结合使用

注意点:

  • 函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象,没有自己的this。
  • 不可以当作构造函数,也就是说不能使用new命令,否则会抛出错误。
  • 不可以使用argument对象,该对象在函数体内不存在。如果要用,可以使用rest参数。
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。

由于箭头函数没有自己的this,所以也不能使用call()、apply()、bind()这些方法去改变this的指向。

箭头函数还可以嵌套使用

6. 绑定this

使用双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(this对象)绑定到右边的函数上。

js 复制代码
foo::bar
// 等同于
bar.bind(foo)

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上。

js 复制代码
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

双冒号运算符返回的是原对象,所以可以采用链式写法。

7. 尾调用优化

1. 什么是尾调用?

是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

注意点:

一定是最后一步,一定是调用另一个函数

不能是含有函数的表达式,或者返回的是上一步函数调用的结果

2. 如何优化尾调

需要知道的是:在函数调用时会在内存中形成一个调用记录,又称调用帧,用来保存位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方还会形成一个B的调用帧,等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用C,那就还有一个C的调用帧,依次类推。所有的调用帧就形成一个调用栈

而尾调是函数的最后一步,不需要保留外层函数的调用帧,因为调用的位置,内部信息都不会再用到了,直接用内层函数的调用帧取代外层函数的即可。

尾调用优化就是只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时调用帧只有一项,就会大大节省内存,这也是尾调用优化的意义。

注意:

只有不再用到外层函数的内部变量,内存函数的调用帧才会取代外层函数的调用帧,否则就无法进行尾调用优化

3. 尾递归

函数调用自身就是递归,如果尾调用自身就称为尾递归。

递归非常耗费内存,因为需要同时保存成百上千个调用帧,很容易造成栈溢出,但对于尾递归来说,只存在一个调用帧,因此不会发生栈溢出

在ES6中只要尾递归就一定不会造成栈溢出

4. 递归函数如何改写成尾递归

尾递归的实现往往需要改写递归函数,来确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

如何做到

  • 在尾递归函数之外再提供一个正常形式的函数
js 复制代码
function tailFactorial(n, total) {
    if (n === 1) return total;
    return tailFactorial(n - 1, n * total);
}
function factorial(n) {
    return tailFactorial(n, 1);
}
factorial(5); // 120
  • ES6的函数默认值
js 复制代码
function factorial(n, total = 1) {
    if (n === 1) return total;
    return factorial(n - 1, n * total);
}
factorial(5); // 120

5. 严格模式

ES6的尾调模式只在严格模式下开启。

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试