函数进阶

image-20210820104723148

Rest参数和Spread语法

Rest参数

在函数定义时可以使用...args来用args数组收集后边所有的参数。...args后边不能再定义形参

例如,我们需要把所有的参数都放到数组 args 中:

1
2
3
4
5
6
7
8
9
10
11
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

我们也可以选择获取第一个参数作为变量,并将剩余的参数收集起来

下面的例子把前两个参数定义为变量,并把剩余的参数收集到 titles 数组中:

1
2
3
4
5
6
7
8
9
10
11
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar

// 剩余的参数被放入 titles 数组中
// i.e. titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}

showName("Julius", "Caesar", "Consul", "Imperator");

arguments变量

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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");

但是arguments没有array的方法,因此最好用rest函数

Spread语法

与rest参数相反,使用时在变量名前加...就可以拆出来,内部使用了迭代器的原理

1
2
3
let str = "Hello";

alert( [...str] ); // H,e,l,l,o

用spread合并数组

1
2
let a = [1,2,3],b = [4,5,6]
let arr = [...a,...b]

进行浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arr = [1, 2, 3];
let arrCopy = [...arr]; // 将数组 spread 到参数列表中
// 然后将结果放到一个新数组

// 两个数组中的内容相同吗?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

// 两个数组相等吗?
alert(arr === arrCopy); // false(它们的引用是不同的)

// 修改我们初始的数组不会修改副本:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3

总结

有一个简单的方法可以区分它们:

  • ... 出现在函数参数列表的最后,那么它就是 rest 参数,它会把参数列表中剩余的参数收集到一个数组中。
  • ... 出现在函数调用或类似的表达式中,那它就是 spread 语法,它会把一个数组展开为列表。

使用场景:

  • Rest 参数用于创建可接受任意数量参数的函数。
  • Spread 语法用于将数组传递给通常需要含有许多参数的列表的函数。

变量作用域,闭包

代码块

用{}包裹的代码是一个代码块

1
2
3
4
5
6
{
let a=1
}
{
let a =2
}

函数对象

  • name:返回函数的名称

  • length:返回函数参数数量

  • 自定义属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function sayHi() {
    alert("Hi");

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

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

    alert( `Called ${sayHi.counter} times` ); // Called 2 times
  • 命名函数表达式

    1
    2
    3
    4
    let sayHi = function func(who) {
    alert(`Hello, ${who}`);
    };

    func不会被外界所知,可以在函数内进行很方便的使用。

setTimeout和setInterval

  • setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。
  • setInterval 允许我们重复运行一个函数,从一段时间间隔之后开始运行,之后以该时间间隔连续重复运行该函数。

setTimeout

语法:

1
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

参数说明:

  • func|code

    想要执行的函数或代码字符串。 一般传入的都是函数。由于某些历史原因,支持传入代码字符串,但是不建议这样做。

  • delay

    执行前的延时,以毫秒为单位(1000 毫秒 = 1 秒),默认值是 0;

  • arg1arg2

    要传入被执行函数(或代码字符串)的参数列表(IE9 以下不支持)

传入函数不要加括号。

setInterval

setInterval 方法和 setTimeout 的语法相同:

1
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

所有参数的意义也是相同的。不过与 setTimeout 只执行一次不同,setInterval 是每间隔给定的时间周期性执行。

想要阻止后续调用,我们需要调用 clearInterval(timerId)

例:编写一个函数 printNumbers(from, to),使其每秒输出一个数字,数字从 from 开始,到 to 结束。

使用 setInterval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printNumbers(from, to) {
let current = from;

let timerId = setInterval(function() {
alert(current);
if (current == to) {
clearInterval(timerId);
}
current++;
}, 1000);
}

// 用例:
printNumbers(5, 10);

使用嵌套的 setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printNumbers(from, to) {
let current = from;

setTimeout(function go() {
alert(current);
if (current < to) {
setTimeout(go, 1000);
}
current++;
}, 1000);
}

// 用例:
printNumbers(5, 10);

请注意,在这两种解决方案中,在第一个输出之前都有一个初始延迟。函数在 1000ms 之后才被第一次调用。

如果我们还希望函数立即运行,那么我们可以在单独的一行上添加一个额外的调用,像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function printNumbers(from, to) {
let current = from;

function go() {
alert(current);
if (current == to) {
clearInterval(timerId);
}
current++;
}

go();
let timerId = setInterval(go, 1000);
}

printNumbers(5, 10);

装饰器,call/apply

缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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) ); // 和前面一行结果相同

call/apply

1
func.apply(context, args)

它运行 func 设置 this=context,并使用类数组对象 args 作为参数列表(arguments)。

callapply 之间唯一的语法区别是,call 期望一个参数列表,而 apply 期望一个包含这些参数的类数组对象。

方法借用:

1
2
3
4
5
function hash() {
alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);

函数绑定

let fun1 = func.bind(context,...args)可以把函数绑定了this赋值给fun1

箭头函数

没有this,没有arguments