文章的行文思路都是按照博主个人对相关知识的理解做编写,综合了权威书籍和诸多贴文的解释,最后编写为方便自己个人理解的版本。
同时附带校招过程中遇到的相关试题
闭包
-
概念:
- 函数和对其周围状态(即词法环境)的引用捆绑再一起构成闭包 (MDN解释)
- 也就是闭包=函数+词法环境的捆绑
- 其实在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
-
作用:
- 让内部函数访问外部函数的作用域
-
常见使用场景:
-
模拟私有变量
-
bind,函数柯里化的实现原理 (实际上就是,在外部函数中将某个变量锁定为某个值 )
-
在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
-
-
使用方法
-
在外部函数中定义并返回内部函数
-
内部函数引用了外部函数的变量
-
在其他地方调用返回函数
-
形如:
function outer(){ var n=0; return function inner(){ console.log(n); } } var method = outer(); method(); // 由于闭包让函数保持对周围状态(函数编写时的词法作用域)的引用,所以依旧能够调用外部函数中的变量n.(n值锁定为0)
-
-
缺点:
- 内存泄露(内部函数保留对外部函数的引用,导致其不能回收,外部函数的活动对象依旧在内存中存在。)
- 闭包在处理速度和内存消耗方面对脚本性能具有负面影响
内存泄露
定义:已分配的内存由于某些原因,无法进行垃圾回收来释放内存,从而导致内存浪费。
-
常见的内存泄露:
- 意外的全局变量 (绑定到window对象上,无法释放)
- 被遗忘的定时器和事件处理程序(回调函数)
- 闭包 (保持引用,无法释放)
- DOM引用
-
垃圾回收的方法:
- 标记清除
- 引用计数
笔试题
//CVTE笔试
function f(n,o) {
console.log(n+o);
return {
f:function(m) {
return f(m,n)
}
};
}
f(1).f(2).f(3).f(4); // NaN 3 5 7
//佳都笔试
function cb(m, n) {
console.log(n)
return {
cb: function (x) {
return cb(x, m);
}
};
}
let res = cb(0);// undefined 第一个输出的n未指定参数,为undefined
res.cb(1);//0 后面都是因为闭包利用外层函数的m参数,所以是cb(x,0); 执行cb函数的console,但是没有接收返回值,所以res不变,后面的执行也是cb(x,0);
res.cb(2);//0
res.cb(3);//0
cb(0).cb(2).cb(3).cb(2)
// undefined 0 2 3
面试题
输出1-5(间隔一秒)
经典面试题,但是网上没有一种很好的解答方案。
题目:如何解释? 输出 5 5 5 5 5
for(var i =0;i<5;i++){
setTimeout(function(){
console.log(i)
},0)
}
-
涉及知识点:
- Event Loop事件循环
- 变量提升
- 作用域问题
-
出现这种现象的根本原因:
- 没有把每次for的i值保存下来,导致setTimeout的回调没有自己独立的i值。
-
题解:
-
根据Event Loop原理分析得,setTimeout将挂起到事件队列,完成同步代码执行后再执行setTimeout
-
由于
var声明只存在于函数作用域和全局作用域,没有块级作用域,其将做变量声明提升到作用域顶部。即整个作用域中将共用一个
i变量,当然for循环内部保持的i在内存中都指向同一个位置,就是同一个i值。 -
同步代码执行完后,i变为5,setTimeout全部调用这个i值。
-
// 1.利用立即执行函数,创建一个函数作用域,将每次for的i值保存到IIFE的函数作用域,执行异步代码时依旧能使用到i的引用。
for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function () {
console.log(i);
},1000*i);
})(i)
}
//2. let 自带的块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
},1000*i);
}
函数柯里化
/*
必须根据面试官所给题目的具体情况书写,其实不难,根据所给条件做函数的输出即可,
其他的就做args数组,依次把数加进去数组即可。
函数柯里化,闭包的典型应用
内层函数保持对外层函数的持续引用,在外层做一个数组args,保存全部传入的参数
内层函数若有argument===0 就直接返回相加结果,否则返回内部函数用于下一次的调用同时将参数压入args数组
*/
//1. 让指定函数套用柯里化的版本,需要实现知道长度做柯里化的函数
function add(a, b, c) {
return a + b + c;
}
function curry(fn,args){
var len = fn.length;
var args= args||[];
return function(){
var newArgs = args.concat(Array.prototype.slice.call(arguments));
if(newArgs.length<len){
return curry.call(this,fn,newArgs)
}else{
return fn.apply(this,newArgs);
}
}
}
var f2 =curry(add);
console.log(f2(1,2)(3))
console.log(f2(1)(2,3))
console.log(f2(1,2,3))
// ES6 版本
const curry = (fn) =>
(judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
const add = (a, b, c) => a + b + c;
const curryAdd = curry(add);
console.log(curryAdd(1)(2)(3)); // 6
console.log(curryAdd(1, 2)(3)); // 6
console.log(curryAdd(1)(2, 3)); // 6
//2. 在toString中做和函数,返回函数本身的打印结果则为相加的结果。没有指定的终止条件
function sum() {
let args = [...arguments];
let fn = function(){
args.push(...arguments);
return fn; //这样返回,原本会输出这个函数的代码,但是由于改变了toString方法,因此输出为和
}
fn.toString = function(){
return args.reduce((a,b)=>a+b);
}
return fn;
}
console.log(sum(1)(2)(3))
//3. 依旧是返回函数本身,此次的终止条件为(),即所传参数为空时返回相加结果
function plus() {
let args = [...arguments];
let cb = function () {
if (arguments.length === 0) {
return args.reduce((a, b) => a + b);
} else {
args.push(...arguments)
return cb;
}
}
return cb;
}
console.log(plus(1)(2)(3)())//此次的终止条件为(),即所传参数为空时返回相加结果