目录
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语法
-
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
Array.from(obj)
和[...obj]
的区别Array.from
适用于类数组对象也适用于可迭代对象。- Spread 语法只适用于可迭代对象
作用域和闭包
词法环境与变量
在 JavaScript 中,每个运行的函数,代码块 {...}
以及整个脚本,都有一个被称为 词法环境(Lexical Environment) 的内部(隐藏)的关联对象。词法环境对象由两部分组成:
- 环境记录(Environment Record) —— 一个存储所有局部变量作为其属性(包括一些其他信息,例如
this
的值)的对象。 - 对 外部词法环境 的引用,与外部代码相关联
“变量”只是 环境记录 这个特殊的内部对象的一个属性,“获取或修改变量”意味着“获取或修改词法环境的一个属性”。
闭包
- 什么是闭包:函数和函数内部能访问到的变量的总和,就是一个闭包(MDN:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。)
- 闭包的作用:
- 间接访问一个变量/隐藏一个变量
- 拓展作用域
- 闭包不会造成内存泄漏,内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来,闭包中的变量是我们需要的,但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
有两个特殊的地方,这就是添加它的原因:
- 它允许函数在内部引用自己
- 它在函数外是不可见的,也就时不直接用变量名的原因,因为它可以再外面被我们改变,但是再内部不能
new Function语法
- 语法及作用:
let func = new Function ([arg1, arg2, ...argN], functionBody);
它允许我们将任意字符串变成函数来执行它,这样就可以从服务器接收一个新的函数并执行它
let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // 3
- 闭包:我们使用
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
-
语法:
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
,不建议传入字符串代码,因为形成词法欺骗,影响作用域,delay后的参数是前面的func的参数 -
clearTimeout
取消调度,let timerId = setTimeout(...); clearTimeout(timerId);
setInterval
- 语法:
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
参数同setTimeout - 也可以使用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)
相同)用来调度需要尽快执行的调用,但是会在当前脚本执行完成后进行调用 - 浏览器会将
setTimeout
或setInterval
的五层或更多层嵌套调用(调用五次之后)的最小延时限制在 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