JavaScript ES6 - 函数扩展
Posted 黑木令
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript ES6 - 函数扩展相关的知识,希望对你有一定的参考价值。
本章节的主要内容是: ES6 函数扩展
一: 函数扩展
1. 函数新增特性:
1. 参数默认值
2. rest 参数
3. 扩展运算符
4. 箭头函数
5. this 绑定
6. 尾调用
如图所示:
1. ES6 函数参数默认值
/**
1. 函数参数默认值
1. ES6 设置默认值, 直接写在参数定义的后面 。
2. 细节知识点:
1. ES6 参数变量是默认声明的, 所以在函数体中不能用 let / const 再次声明, 否则会报错 。
2. ES6 默认值不是传值, 而是每次都重新计算默认值, 也就是说默认值是惰性求值 。
3. ES6 参数设置默认值, 后面不能再有没有默认值的参数 。
1. 如果此时将默认参数设置为 undefiend, 将触发设置的默认值, null 没有这个效果 。
4. 函数的 length 属性:
1. length 属性的含义是这个函数预期传入参数的个数 。
1. 某个参数指定默认值后, 预期传入参数的个数就不包含这个参数了 。
5. 作用域的概念:
1. 函数一旦设置了参数的默认值, 函数进行初始化时, 参数会形成一个单独的作用域; 等待初始化结束, 这个作用域就会消失 。这种语法行为在不设置参数的默认值时是不会出现的 。
*/
// ES6 写法
function testES61(x, y='默认值')
console.log('ES6 设置默认值方法: ', x, y);
// ES6 验证在函数体中不能用 let / const 再次声明, 已经设置了默认值的参数为变量 。
// let y = 10 // 这里会报错 。
// 正常调用-1
testES61('Hello') // ES6 设置默认值方法: Hello 默认值
// 正常调用-2
testES61('Hello', '小明') // ES6 设置默认值方法: Hello 小明
// 正常调用-3
testES61('Hello', '') //ES6 设置默认值方法: Hello
// 正常调用-4
testES61('Hello', false) // ES6 设置默认值方法: Hello false
// ES6 默认值是惰性求值
/**
* testES62 函数的参数 res 的默认值是 num1 + 1, 每次调用 testES62 都会重新计算 num1 + 1, 而不是默认 res = 10
*/
let num1 = 9
function testES62(res = num1 + 1)
console.log('ES6 默认值 -- 惰性求值: ', res);
testES62() // ES6 默认值 -- 惰性求值: 10
num1 = 100
testES62() // ES6 默认值 -- 惰性求值: 101
// ES6 参数设置默认值, 后面不能再有没有默认值的参数
function testES63(x, y='默认值', z)
console.log('ES6 参数设置默认值, 后面不能再有没有默认值的参数: ', x, y, z);
testES63() // ES6 参数设置默认值, 后面不能再有没有默认值的参数: undefined 默认值 undefined
testES63('Hello') // ES6 参数设置默认值, 后面不能再有没有默认值的参数: Hello 默认值 undefined
testES63('Hello', '覆盖默认值-小明') // ES6 参数设置默认值, 后面不能再有没有默认值的参数: Hello 覆盖默认值-小明 undefined
// testES63('Hello', ,'第三个参数') // 报错, 无法设置
testES63('Hello', undefined,'第三个参数') // ES6 参数设置默认值, 后面不能再有没有默认值的参数: Hello 默认值 第三个参数
// 函数的 length 属性
// 没有设置默认值
function testES64(x, y, z)
console.log('函数的 length 属性');
console.log('函数的 length 属性 - 没有设置默认值: ', testES64.length); // 函数的 length 属性 - 没有设置默认值: 3
// 设置默认值
function testES65(x, y, z='位数参数-设置默认值')
console.log('函数的 length 属性');
console.log('函数的 length 属性 - 设置默认值: ', testES65.length); // 函数的 length 属性 - 设置默认值: 2
// 作用域的概念
// 作用域 - 示例一
let x = 'text'
function testES66(x, y = x)
console.log('参数作用域的概念 -- 与函数默认值有关联: ', x, y);
// 思考: 当我们只传入一个参数, 即形参 x 等于 11; 此时的 y 参数取值是什么?
// 此时参数 y 取值是 变量 x ? 还是形参 x ?
// 此时参数 y 取值就是形参 x 的值; 看执行结果 。
// 执行情况 1:
// testES66(11) // 参数作用域的概念 -- 与函数默认值有关联: 11 11
// 执行情况 2:
testES66() // 参数作用域的概念 -- 与函数默认值有关联: undefined undefined
// 作用域 - 示例二
let str = 'text'
function testES67(a, y = str)
console.log('参数作用域的概念 -- 与函数默认值有关联: ', a, y);
// 思考此时的执行结果
// 执行情况 1:
testES67(11) // 参数作用域的概念 -- 与函数默认值有关联: 11 text
// 执行情况 2:
testES67() // 参数作用域的概念 -- 与函数默认值有关联: undefined text
// // 作用域 - 示例三
// // 如果此时默认值 num 不存在, 就会报错 。
// function testES67(a, y = num)
// console.log('参数作用域的概念 -- 与函数默认值有关联: ', a, y);
//
// testES67() // num is not defined
// ES5 写法
function testEs51(x, y)
// 设置默认值
y = y || '默认值'
console.log('ES5 设置默认值: ', x, y);
// 正常调用-1
testEs51('Hello') // ES5 设置默认值: Hello 默认值
// 正常调用-2
testEs51('Hello', '传入参数') // 设置默认值: Hello 传入参数
// 问题调用--1
testEs51('Hello', '') // 设置默认值: Hello 默认值
// 问题调用--2
testEs51('Hello', false) // 设置默认值: Hello 默认值
/**
* 问题总结:
* 1. testEs51 函数这种写法的缺点在于如果赋值为 ''空 或者 false, 赋值是不起作用的; 就如 问题调用--1 与 问题调用--2 的调用方式 。
*
* 2. 解决办法: 如下代码
* 1. 为了避免这个问题, 我们需要先判断需要设置默认值的参数是否被赋值, 如果没有再使其等于默认值 。
*/
function testEs52(x, y)
// 设置默认值
if(typeof y === 'undefined')
y = '默认值'
console.log('ES5 设置默认值--避免上述问题: ', x, y);
// 问题调用-解决办法--1
testEs52('Hello', '') // 设置默认值--避免上述问题: Hello 默认值
// 问题调用-解决办法--2
testEs52('Hello', false) // 设置默认值--避免上述问题: Hello 默认值
2. ES6 rest 参数
/**
2. rest 参数
1. 形式是 ...变量名
2. 作用是在不确定参数数目时, 将一些列参数转换为一个数组 。
3. 细节知识点:
1. rest 参数后面不能再有其余的参数, 否则会报错 。
1. 如果 后面还有其它的参数时, 在转换数组时它不知道在什么地方截止 。
*/
// 示例一:
function restES61(...arg)
for (const val of arg)
console.log('ES6 -- rest 参数: ', val);
restES61(1,3,5)
// 打印结果:
// ES6 -- rest 参数: 1
// ES6 -- rest 参数: 3
// ES6 -- rest 参数: 5
// 示例二:
function pushRewrite(array, ...arg)
arg.forEach(function(item)
array.push(item)
)
var arr = []
pushRewrite(arr, 1,2,3,4,5)
console.log('改写 js 的 push 方法: ', arr); // 改写 js 的 push 方法: (5) [1, 2, 3, 4, 5]
3. ES6 函数扩展运算符
/**
3. 扩展运算符
1. 形式: ...[] 或者 ...变量名 或者 ...()
2. 它可以理解为 rest 参数 的逆运算, 将一个数组转换为用逗号隔开的参数序列 。
3. 细节知识点:
1. 扩展运算符后面可以放置表达式 。
2. 如果扩展运算符后面是一个空数组, 则不产生任何效果 。
*/
console.log('ES6 扩展运算符: ', ...[1,2,4]); // ES6 扩展运算符 1 2 4
let arr1 = [1,2,4]
console.log('ES6 扩展运算符: ', 'a', ...arr1, 6); // ES6 扩展运算符: a 1 2 4 6
let conditi = 0
let num = 8
let arr = [
...(conditi > 0 ? ['a'] : []),
num,
]
console.log('ES6 扩展运算符后面可以放置表达式: ', arr); // ES6 扩展运算符后面可以放置表达式: [8]
4. ES6 箭头函数
// ES6 声明函数方法
/**
* arrow1: 函数名
* v: 函数参数
* =>: 使用箭头链接
* v*2: 函数返回值
*/
let arrow1 = v => v*2
console.log('ES6 声明函数方法 -- 箭头函数: ', arrow1(2)); // ES6 声明函数方法 -- 箭头函数: 4
// 等价于
function arrow1Equivalence(v)
return v * 2
/**
* 1. 如果没有参数, 我们使用 ()圆括号 表示
* 2. 这里返回一个固定值
*/
let arrow2 = () => 5
console.log('没有参数, 我们使用 ()空括号 表示 -- 箭头函数: ', arrow2()); // 没有参数, 我们使用 ()空括号 表示 -- 箭头函数: 5
// 等价于
function arrow2Equivalence()
return 5
/**
* 1. 如果箭头函数的 '代码块' 大于一条语句, 我们就需要使用 大括号 包裹起来, 并且使用 return 语句返回 。
* 2. 如果箭头函数直接返回一个对象, 必须在外面加上 ()括号, 这是因为 大括号被解析为代码块的原因 。
*/
let arrow3 = (num1, num2) =>
return num1 + num2
console.log('箭头函数的 "代码块" 大于一条语句: ', arrow3(1, 3)); // 箭头函数的 "代码块" 大于一条语句: 4
let arrow4 = (age) => (ageNum: age, name: '小明')
console.log('箭头函数直接返回一个对象: ',arrow4(12)); // 箭头函数直接返回一个对象: ageNum: 12, name: '小明'
/**
* 箭头函数简化回调函数
*/
// 箭头函数写法
let arr1 = [1, 2, 3, 4]
let resArr1 = arr1.map(x => x + x)
console.log('箭头函数简化回调函数: ', 'arr1: ', arr1, 'resArr1: ', resArr1);
// 箭头函数简化回调函数: arr1: [1, 2, 3, 4] resArr1: [2, 4, 6, 8]
// 常规写法
let arr2 = [1, 2, 3, 4]
let resArr2 = arr2.map(function(item)
return item + item
)
console.log('回调函数--常规写法: ', 'arr2: ', arr2, 'resArr2: ', resArr2);
// 回调函数--常规写法: arr2: [1, 2, 3, 4] resArr2: [2, 4, 6, 8]
// 箭头函数写法
let resSort1 = [2,1,5,3,7].sort((a, b) => a - b)
console.log('resSort1: ', resSort1); // resSort1: [1, 2, 3, 5, 7]
// 常规写法
var resSort2 = [2,1,5,3,7].sort(function(a, b)
return a - b
)
console.log('resSort2: ', resSort2); // resSort2: [1, 2, 3, 5, 7]
/**
* rest 参数 与 回调函数的结合
*/
let fun3 = (num, ...arr3) => [num, arr3]
let res3 = fun3(1, 2, 3, 4)
console.log('rest 参数 与 回调函数的结合: ', res3); // rest 参数 与 回调函数的结合: [1, [2, 3, 4]]
/**
* 1. 箭头函数 this 指向
* 1. 在箭头函数中 this 指向是固定的 。
* 1. 箭头函数体内的 this 对象就是定义时所在的对象, 而不是使用时所在的对象 。
*/
// 示例一:
function funThis1()
// 箭头函数可以让 setTimeout 里面的 this 绑定--定义时所在的作用域, 而不是指向运行时所在的作用域 。
setTimeout(() =>
console.log("箭头函数 this 指向 -- 参数 id:", this.id);
// 打印结果:
// 箭头函数 this 指向 -- 参数 id: 99
, 1000);
let id = 12
funThis1.call(id: 99)
// 示例二:
function FunThis2()
this.num1 = 0
this.num2 = 0
// 箭头函数: 此 this 绑定定义时所在的作用域 即 FunThis2 。
setInterval(() => this.num1++, 1000);
// 常规函数: 此 this 指向运行时所在的作用域 即 全局对象 。
setInterval(function()
this.num2++
, 1000)
var funThis2 = new FunThis2()
setTimeout(() =>
console.log('num1: ', funThis2.num1); // num1: 3
, 3100);
setTimeout(() =>
console.log('num2: ', funThis2.num2); // num1: 0
, 3100);
5. ES6 函数尾调用
/**
5. 尾调用
1. 尾调用它是存在于函数式编程的一个概念 。
2. 函数的最后一步是调用另一个函数 。
3. 尾调用的好处:
1. 提升性能
2. 尾调用之所以与其它调用不同, 就在于它特殊的调用位置 。
3. 调用函数会在内存中生成一个 '调用记录'(或者是 '调用帧'), 保存调用位置和内部变量等信息 。
4. 如果有三个函数分别是 A 、 B 、 C;
1. A 调用 B, B 调用 C;
2. 那么在 A 的调用帧上方会生成一个 B 的调用帧, 在 B 的调用帧的上方还会生成一个 C 的调用帧;
3. 此时运行函数, 等到 B 运行结束, 将结果返回 A, B 的调用帧才会结束;
4. 同理类推 C 运行结束, 将结果返回 B, C 的调用帧才会结束 。
5. 所有的调用帧会形成一个 '调用栈' 。
5. 尾调用由于是函数的最后一步操作, 所以不需要保留外层函数的调用帧, 因为调用位置、内部变量信息都不会再用到, 直接用内层函数的调用帧取代外层函数的调用帧即可 。
6. 如果所有的函数都是尾调用, 那么可以做到每次执行时调用帧只有一项, 这将节省很大的内存空间; 这也是 尾调用优化 的意义 。
*/
// 尾调用--示例一:
function fir(x)
console.log('fir 函数 -- 尾调用: ', x);
function sec(x)
return fir(x)
sec(1111) // fir 函数 -- 尾调用: 1111
// 尾调用--示例二:
function fir1(x)
console.log('尾调用--fir1 函数');
function fir2(x)
console.log('尾调用--fir2 函数');
function sec1(x)
if(x > 1)
return fir1(x)
return fir2(x)
// 尾调用--示例三:
function a1()
let m = 1
let n = 2
return g(m + n)
// 尾调用--示例四: 只有不再用到外层函数的内部变量, 内层函数的调用帧才会取代外层函数的调用帧, 否则无法进行 尾调用优化 。
function b(num1)
var m1 = 2
function inner(num2)
// 此处就不能实现 尾调用优化, 因为它使用到了外层函数的内部变量, 外层函数调用帧无法被取代 。
return m1 + num2
return inner(a)
/**
* 下面不属于尾调用
* 1. 调用函数之后还有赋值操作
* 2. 调用之后还有操作
*/
function g(x)
console.log('尾调用函数');
// 示例一:
function f1(x)
let y = g(x)
return y
// 示例二:
function f2(x)
return g(x) + 1
// 示例三:
function f3(x)
g(x)
// 相当于
function f4(x)
g(x)
return undefined
6. ES6 函数尾递归
/**
* 尾递归:
* 1. 函数调用自身称为递归, 如果尾调用自身就称为尾递归 。
* 2. 尾递归非常消耗内存, 因为需要同时保存很多调用帧, 很容易发生 '栈溢出' 错误;
* 3. 但对于尾递归来说, 由于只存在一个调用帧, 所以永远不会发生 '栈溢出' 错误 。
* 4. 改写:
* 1. 改写递归函数为尾递归函数, 就是确保最后一步只调用自身;
* 2. 要做到这一点的方法就是把所有用到的内部变量改写为函数参数 。
*/
/**
* 1. 阶乘示例:
*/
// 递归函数
function fact1(n)
if(n === 1)
return 1
else
return n * fact1(n - 1)
fact1(5) // 120
// 尾递归
function fact2(n, total)
if(n === 1) return total
return fact2(n - 1, n * total)
fact2(5, 1) // 120
/**
* 2. Fibonacci 数列:
*/
// 递归函数
function fib1(n)
if( n <= 1) return 1
return fib1(n - 1) + fib1(n - 2)
console.log(fib1(10)); // 89
// fib1(100) // 堆栈溢出(转呀转转不动, 电脑嗡嗡要炸裂)
// fib1(500) // 堆栈溢出(转呀转转不动, 电脑嗡嗡要炸裂)
// 尾递归
function fib2(n, ac1 = 1, ac2 = 1)
if(n <= 1) return ac2
return fib2(以上是关于JavaScript ES6 - 函数扩展的主要内容,如果未能解决你的问题,请参考以下文章