发布作者: JavaScript大王
百度收录: 正在检测是否收录...
最后更新: 2023年 01月 02日 22:37
作品采用: 《 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 》许可协议授权
// ES6之前变通的方法为函数参数指定默认值
function fn1(x,y){
/* y = y || "World"; // 短路运算
return x + y; 该方法并不完美,y传入的值为false时无法正确设置默认值*/
// 判断y是否等于false
if(typeof y === "undefined"){
y = "World";
}
return x + y ;
}
let res1 = fn1("hello"); // helloWorld
// ES6 可以直接将参数默认值写在参数定义的后面
function fn2(x = "name",y = "peanut"){
console.log(x + ":" + y);
}
fn2(); // name:peanut
fn2("name","china"); // name:china
function test(x = 5){
let x = 1; // 错误
const x = 2; // 错误
}
使用默认值的几个好处:
// 解构赋值 + 参数默认值
function test({x,y = 1}) {
console.log(x,y);
}
test({}); // undefined 1
test({x : "num"}) //num 1
test({x : 111 , y : 222}); //111 222
/* 注意 : 参数使用的是对象的解构赋值,如果传入的参数不是对象,那么就无法生成对应的x、y
参数 此时会报错 */
test();
function fetch(url,{body = '' , method = 'GET' , headers = {}}) {
console.log(method);
}
fetch('www.peanut.run',{}); // GET
// 省略第二个参数会报错
fetch('www.peanut.run'); // error
可以使用双重默认值,使得第二个参数省略时函数也能正常运行:
function fetch(url,{method = "GET"} = {}) {
console.log(method);
}
fetch('www.peanut.run', {}); // GET
// 此时可以省略第二个参数
fetch('www.peanut.run'); // GET
// 有默认值的参数应该是尾参数
function myFn(x,y = 1,z) {
console.log(x,y,z);
}
myFn(11,,2); // 错误,默认值参数不是尾参数
myFn(11); // 自身可省略,其后面的所有参数都不能省略,否则后面的参数都是undefined,无法正确赋值
// 不指定参数默认值,函数的length属性返回的是函数参数的个数
function fl_1(name,age,sex) {}; // 3
// 指定参数默认值后,默认值参数及其后面的所有参数都将不被计入到length属性中
function fl_2(name,age = 18,sex) {}; // 1 age sex不计
// rest也不会被计入
function fl_3(name,age,...parm) {}; // 2
// 设置默认值,函数声明初始化时参数会形成独立作用域
let x = 1;
function fun1(y = x) {
let x = 3; // 内部的变量对参数作用域没有影响,参数作用域只会到外部作用域查找
console.log(y);
}
fun1(2); // 2
fun1(); // 1
var p = 1;
function foo(p,y = function(){ p = 2; }){
var p = 3;
y();
console.log(p);
}
foo(); // 3
// 指定某个参数不可省略,如果省略就抛出错误
function error() {
throw new Error("参数不可省略");
}
function must(z = error()) {};
must(); // "参数不可省略"
// 将某个参数默认值设为undefined 表明该参数不可省略
function must1(opt = undefined){
console.log(opt);
}
must1(1); // 1
must1(); // undefined
// rest作用与arguments对象类似
// rest参数是数组,arguments对象是伪数组
let test1 = (...nums) =>{
const arr = nums;
// rest参数支持数组所有的方法
arr.push(99);
console.log(arr);
console.log(this.length); // 0
}
test1(1,2,3,4,5); // [1, 2, 3, 4, 5, 99]
// 声明严格模式
"use strict";
function myFn(a = 1) {
"use strict"; // 错误
}
// ES6之后 匿名函数也会返回函数名
const f = function () {};
console.log(f.name); // f es5返回空字符串
// 具名函数赋值给变量,仍返回函数名
const ff = function myFn() {};
console.log(ff.name); // myFn
() => {};
// 1个返回值 1个参数
let fn1 = a => a;
console.log(fn1(1));
// 多个参数 代码块多条代码
let fn2 = (aa,bb) =>{
let cc = aa + bb;
console.log(cc);
}
fn2(1,2);// 3
let fn3 = (ID) => ({id : ID , name : "peanut"});
console.log(fn3(12)); // {id: 12, name: "peanut"}
let fn4 = ({first,last}) => {
return first + " " + last;
}
let res1 = fn4({first : "宝",last : "鸡"});
console.log(res1); // 宝 鸡
// 等同于ES5写法:
let fn5 = function (person) {
return person.first + " " + person.last;
}
// 简化回调函数
// ES5写法:
const arr = [1,2,3];
arr.map(function(x){
return x * x;
})
// ES6箭头函数
arr.map(x => x*x);
箭头函数有以下几点需要注意:
// 箭头函数中的this是固定的
function foo() {
setTimeout(() =>{
console.log('id:', this.id);
},100);
}
var id = 12;
foo.call({id : 21}); // 21 指向函数定义时的对象
// 箭头函数能使this指向固定化 适合封装回调函数
// DOM事件的回调函数封装在一个函数中
var handler = {
id : '123',
init : function () {
document.addEventListener('click',
event => this.doSomeThing(event.type),false);
},
doSomeThing : function (type) {
console.log('Handler' + type + 'for' + this.id);
}
}
handler.init(); // Handlerclickfor123
为什么箭头函数不能用作构造函数?
同this一样,以下三个参数也是指向箭头函数外部的对应变量,箭头函数内部并不存在:
// 多重嵌套函数 es5写法:
function insert(value) {
return {
into: function (array) {
return {
after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, val
return array;
}
};
}
};
}
// ES6写法
let insert = value => ({into : array => ({after : afterValue => {
array.splice(array.indexOf(afterValue + 1 , 0 , value));
return array;
}})})
function f(x){
return g(x);
}
// 以下三种情况不属于尾调用
// 1.调用其他函数后进行复制操作并返回结果
function f(x){
let y = g(x);
return y;
}
// 2.调用后还有其他操作
function h(x){
return i(x) + 1;
}
// 3.调用后未定义返回值
function k(x) {
l(x);
}
// =====>等同于以下代码
function k(x) {
l(x);
return undefined;
}
// 尾调用不一定出现在函数尾部
function f(x) {
if(x > 0){
return m(x); // 尾调用
}
return n(x); // 尾调用
}
1.首先搞清楚调用帧和调用栈的概念:
函数调用会在内存形成一个调用帧,用来保存调用位置和内部变量等信息;假设在A函数内部调用B函数,那么A函数的调用帧上方还会形成一个B的调用帧。等待B运行结束,将结果返回到A,B的调用帧才会消失;以此类推,如果B的内部还调用C,那么A的上方除了B的调用帧还会出现C的调用帧....;所有的调用帧就形成一个调用栈;
2.尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,直接用内层函数的调用帧取代外层函数即可;
尾调用优化:即只保留内层函数的调用帧。如果所有的函数都是尾调用,那么完全可以做到每次执行时调用帧只有一个,将大大提升性能,这也是尾调用优化的意义所在。
如下所示:
// 尾调用优化的目标是使得每一次调用都只有一个调用帧
function f() {
let m = 1;
let n = 2;
return g(m + n); // 调用g之后,f的调用帧就不需要了
}
f();
// 优化后的形式:
function f() {
return g(3);
}
f();
// 等同于:
g(3);
function addOne(a) {
var one = 1;
function inner(b) {
return b + one; // 无法尾调用优化 inner函数内部引用了外部函数的one变量
}
return inner(a);
}
// 尾调用优化后的尾递归函数
function fac(n,total = 1) {
if(n === 1){
return total;
}
return fac(n - 1,n * total);
}
fac(5,1);
// 尾调用优化仅在严格模式下生效
function test() {
"use strict";
test.arguments; // 报错
test.caller; // 报错
}
// 蹦床函数
function trampoline(f){
while(f && f instanceof Function){
f = f();
}
return f;
}
以上代码就是一个蹦床函数的实现,它接收一个参数f,只要f执行后就返回一个函数,就继续执行;简单说就是只返回一个函数,而不是在函数内部调用函数,避免了递归执行,消除调用栈过大;
function sum(x,y){
if(y > 0){
return sum(x + 1,y -1);
}else{
return x;
}
}
sum(1,10000);
sum(1,100000); // 报错 栈溢出
// 利用蹦床函数改写===>
function betterSum(x,y) {
if(y > 0){
return betterSum.bind(null,x + 1 , y - 1); // 绑定this,传入参数但是不执行
}else{
return x;
}
}
betterSum(1,1000000);
// 尾递归优化的真正实现
function tco(f) {
var active = false; // 表示激活状态
var temp_arr = []; // 保存参数f
var value; // tco函数返回值
return function accumulator(){
temp_arr.push(f); // 将参数加入到数组尾部
if(!active){
active = true;
while(temp_arr.length){
f = f.apply(this,temp_arr.shift()); // 将上面传入数组的f删除并返回赋值给新的f
}
active = false;
return value;
}
}
}
var sum_new = tco(function (x,y){
if(y > 0){
return sum(x + 1,y -1);
}else{
return x;
}
});
sum_new(1,1000000);
—— 评论区 ——