JavaScript总结【4】函数进阶

JavaScript函数进阶

Rest 参数与 Spread 语法

Rest 参数

Rest 参数可以通过使用三个点 ... 并在后面跟着包含剩余参数的数组名称,来将它们包含在函数定义中。这些点的字面意思是“将剩余参数收集到一个数组中。但是Rest 参数必须放到参数列表的末尾

function sumAll(...args) { // 数组名为 args
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

arguments变量

有一个名为 arguments 的特殊的类数组对象,该对象按参数索引包含所有参数。但箭头函数是没有 arguments

function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );

  // 它是可遍历的
  // for(let arg of arguments) alert(arg);
}

// 依次显示:2,Julius,Caesar
showName("Julius", "Caesar");

// 依次显示:1,Ilya,undefined(没有第二个参数)
showName("Ilya");

Spread语法

  1. Spread语法...

    注意,这里是浅拷贝

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(...arr1, ...arr2) ); // 8

let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15(0,然后是 arr,然后是 2,然后是 arr2)

let str = "Hello";
alert( [...str] ); // H,e,l,l,o
  1. Array.from(obj)[...obj] 的区别
    • Array.from 适用于类数组对象也适用于可迭代对象。
    • Spread 语法只适用于可迭代对象

作用域和闭包

词法环境与变量

在 JavaScript 中,每个运行的函数,代码块 {...} 以及整个脚本,都有一个被称为 词法环境(Lexical Environment) 的内部(隐藏)的关联对象。词法环境对象由两部分组成:

  • 环境记录(Environment Record) —— 一个存储所有局部变量作为其属性(包括一些其他信息,例如 this 的值)的对象。
  • 外部词法环境 的引用,与外部代码相关联

“变量”只是 环境记录 这个特殊的内部对象的一个属性,“获取或修改变量”意味着“获取或修改词法环境的一个属性”。

闭包

  1. 什么是闭包:函数和函数内部能访问到的变量的总和,就是一个闭包(MDN:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。)
  2. 闭包的作用:
    • 间接访问一个变量/隐藏一个变量
    • 拓展作用域
    • 闭包不会造成内存泄漏,内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来,闭包中的变量是我们需要的,但IE浏览器使用完闭包后回收不了闭包的引用变量,这是IE的问题

其他详解见你不知道的javaScript

函数对象

属性name

函数名

let user = {
  sayHi() {
    // ...
  },
    
  sayBye: function() {
    // ...
  }
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye

属性length

传入参数的个数,当传入参数是函数时,可以用来判断参数个数

function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2,rest参数不参与计数

自定义属性

可以用来追踪调用次数等

function sayHi() {
  alert("Hi");

  // 计算调用次数
  sayHi.counter++;
}
sayHi.counter = 0; // 初始值

sayHi(); // Hi
sayHi(); // Hi

alert( `Called ${sayHi.counter} times` ); // Called 2 times

命名函数表达式

命名函数表达式(NFE,Named Function Expression),指带有名字的函数表达式的术语

let sayHi = function func(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    func("Guest"); // 使用 func 再次调用函数自身
  }
};

sayHi(); // Hello, Guest

// 但这不工作:
func(); // Error, func is not defined(在函数外不可见)

名字 func 有两个特殊的地方,这就是添加它的原因:

  1. 允许函数在内部引用自己
  2. 在函数外是不可见的,也就时不直接用变量名的原因,因为它可以再外面被我们改变,但是再内部不能

new Function语法

  1. 语法及作用:let func = new Function ([arg1, arg2, ...argN], functionBody);它允许我们将任意字符串变成函数来执行它,这样就可以从服务器接收一个新的函数并执行它
let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // 3
  1. 闭包:我们使用 new Function 创建一个函数,那么该函数的 [[Environment]] 并不指向当前的词法环境,而是指向全局环境。此类函数无法访问外部(outer)变量,只能访问全局变量
function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // error: value is not defined

setTimeout 和 setInterval

  • setTimeout 允许我们将函数推迟到一段时间间隔之后再执行
  • setInterval 允许我们以一定时间间隔开始重复运行一个函数(先要等待时间间隔)

setTimeout

  1. 语法:let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...),不建议传入字符串代码,因为形成词法欺骗,影响作用域,delay后的参数是前面的func的参数

  2. clearTimeout取消调度,let timerId = setTimeout(...); clearTimeout(timerId);

setInterval

  1. 语法:let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)参数同setTimeout
  2. 也可以使用clearTimeout取消调度

嵌套的setTimeout

/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/

let timerId = setTimeout(function tick() {
  alert('tick');
  timerId = setTimeout(tick, 2000); // (*)
}, 2000);
  • 嵌套的 setTimeout 能够精确地设置两次执行之间的延时,而 setInterval 却不能,因为后者的间隔会算上函数执行的时间,实际执行间隔就不会那么大

零延时setTimeout

  • 零延时调度 setTimeout(func, 0)(与 setTimeout(func) 相同)用来调度需要尽快执行的调用,但是会在当前脚本执行完成后进行调用
  • 浏览器会将 setTimeoutsetInterval 的五层或更多层嵌套调用(调用五次之后)的最小延时限制在 4ms。这是历史遗留问题。

装饰器模式和转发

一个简单的装饰器

装饰一个重负载函数,如果之前调用过则使用缓存,否则真的调用它

function slow(x) {
  // 这里可能会有重负载的 CPU 密集型工作
  alert(`Called with ${x}`);
  return x;
}

function cachingDecorator(func) {
  let cache = new Map();

  return function(x) {
    if (cache.has(x)) {    // 如果缓存中有对应的结果
      return cache.get(x); // 从缓存中读取结果
    }

    let result = func(x);  // 否则就调用 func

    cache.set(x, result);  // 然后将结果缓存(记住)下来
    return result;
  };
}

slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1) 被缓存下来了
alert( "Again: " + slow(1) ); // 一样的
alert( slow(2) ); // slow(2) 被缓存下来了
alert( "Again: " + slow(2) ); // 和前面一行结果相同

问题1:不适用于对象方法

// 我们将对 worker.slow 的结果进行缓存
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    // 可怕的 CPU 过载任务
    alert("Called with " + x);
    console.log("slow this", this) // 不绑定的话,下面调用时此处this = undefined
    return x * this.someMethod(); // (*)
  }
};

// 和之前例子中的代码相同
function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    console.log("cachingDecorator this",this); // 此处this = worker
    console.log("func",func); // 普通函数,不是对象方法
    let result = func(x); // (**) 
    cache.set(x, result);
    return result;
  };
}

alert( worker.slow(1) ); // 原始方法有效

worker.slow = cachingDecorator(worker.slow); // 现在对其进行缓存

alert( worker.slow(2) ); // 蛤!Error: Cannot read property 'someMethod' of undefined

适用对象方法的装饰器

使用内置方法func.call(context, ...args)显式的设置this可以解决问题

let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func.call(this, x); // 现在 "this" 被正确地传递了,绑在了worker上
    cache.set(x, result);
    return result;
  };
}

worker.slow = cachingDecorator(worker.slow); // 现在对其进行缓存

alert( worker.slow(2) ); // 工作正常
alert( worker.slow(2) ); // 工作正常,没有调用原始函数(使用的缓存)

问题2:这里只传递了一个参数

多个参数的传递

使用apply并借用数组组方法实现。使用call的话可以使用Spread语法将可迭代对象args作为列表传递给call即func.call(context, ...args),二者传递的参数不同,apply 仅接受 类数组对象 args。

let worker = {
  slow() {
    return [].join.call(arguments);
  }
};

function cachingDecorator(func, hash) {
  let cache = new Map();
  return function() {
    let key = hash(arguments); // (*)
    if (cache.has(key)) {
      return cache.get(key);
    }

    let result = func.apply(this, arguments); // (**)

    cache.set(key, result);
    return result;
  };
}

function hash(args) {
  return [].join.call(arguments);  // 'arg1, arg2, arg3, ...'
}

worker.slow = cachingDecorator(worker.slow, hash);

alert( worker.slow(3, 5, 7) ); // works
alert( "Again " + worker.slow(3, 5, 7) ); // same (cached)

装饰器和函数属性

通常,用装饰的函数替换一个函数或一个方法是安全的,但如果原始函数有属性除外。装饰后的函数将不再提供这些属性,但装饰器可以提供自己的属性

防抖和节流装饰器

防抖
function debounce(func, ms) {
  let timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, arguments), ms);
  };
}
节流
function throttle(func, ms) {

  let isThrottled = false,
    savedArgs,
    savedThis;

  function wrapper() {

    if (isThrottled) { // (2)
      savedArgs = arguments;
      savedThis = this;
      return;
    }

    func.apply(this, arguments); // (1)

    isThrottled = true;

    setTimeout(function() {
      isThrottled = false; // (3)
      if (savedArgs) {
        wrapper.apply(savedThis, savedArgs);
        savedArgs = savedThis = null;
      }
    }, ms);
  }

  return wrapper;
}

箭头函数

  • 没有this
  • 没有arguments
  • 不能使用new调用
  • 没有super
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值