一、闭包
1、概念
闭包是指有权利访问另一个函数作用域中变量的函数,创建闭包的一般的形式就是在一个函数内创建另一个函数
标准回答
闭包 一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的闭包,A函数执行结束后这个A函数的变量也不会被销毁,并且这个变量在A函数外部只能通过B函数访问。
闭包形成的原理:
作用域链,当前作用域可以访问上级作用域中的变量
闭包解决的问题:
能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。
闭包带来的问题:
由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
加分回答
闭包的应用,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。
2、用途
(1)通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这个方法创建私有变量
(2)使已经运行结束的函数上下文中的变量继续留在内存中。保存变量对象的引用,不会被垃圾回收机制回收
3、经典面试题1
1、面试题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
每隔一秒输出6 ,因为setTimeout是异步函数,setTimeout 执行时,for循环已经结束了,i的值是6
2、优化:使用立即执行函数
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
使用立即执行函数构建自己的作用域,然后将i值传入立即执行函数,用j保存传入的值,此时值是不变的,然后setTimeout使用j
3、优化:使用setTimeout的第三个参数
for (var i = 1; i <= 5; i++) {
setTimeout(function timer(j) {
console.log(j)
}, i * 1000,i)
}
使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
4、优化:使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
4、经典面试题2
function test() {
var n = 4399;
function add() {
n++;
console.log(n);
}
return { n: n, add: add }
}
var result = test(); // 设置result的属性{n:4399,add:add()}
var result2 = test(); // 设置result2的属性{n:4399,add:add()}
result.add(); // 调用result的add方法,n是4399,进行n++,n等于4400
result.add(); // 调用result的add方法,进行n++,n是4400,n等于4401
console.log(result.n); // 调用result的属性,n,值是4399
result2.add(); // 调用result2的add方法,n是4399,n++之后,n的值是4400
// 闭包的定义:在A函数中定义B函数,在B函数中引用了A中的变量,就会产生闭包。
5、几种闭包形式
1、返回值是函数
/**
* 闭包
*/
function makeFun() {
var name = "tom";
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = makeFun();
myFunc();
2、立即执行函数
/**
* 立即执行闭包(2次立即执行函数的写法)
*/
(function fout() {
const a = 1;
function fin() {
console.log(a);
}
return fin;
})()(); // 1
3、闭包实现加法
/**
* 闭包
*/
function makeAdder(x) {
return function (y) {
return x + y;
}
}
var makeA = makeAdder(5);
var makeB = makeAdder(7);
console.log(makeA(2));
console.log(makeB(2));
6、闭包的使用案例
1、闭包,通过点击链接,实现改变body的字体大小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
<script>
function makeSize(size) {
return function () {
document.body.style.fontSize = size + 'px';
}
}
document.getElementById('size-12').onclick = makeSize(12);
document.getElementById('size-14').onclick = makeSize(14);
document.getElementById('size-16').onclick = makeSize(16);
</script>
</body>
</html>
2、闭包模拟私有方法
/**
* 闭包模拟私有方法
*/
var makeCounter = function () {
var privateCounter = 0; //私有变量
function changeBy(val) { //私有变量
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
}
}
}
// console.log(makeCounter().value()); // 0
var count1 = makeCounter();
var count2 = makeCounter();
console.log(count1.value()); //0
count1.increment();
console.log(count1.value()); //1
console.log(count2.value()); // 0
count2.increment();
count2.increment();
console.log(count2.value()); //2
匿名函数创建了2个私有属性:变量privateCounter和函数changeBy 这两项无法在外部环境中进行访问,所以通过匿名函数返回3个公共函数访问 这3个函数共享同一环境的闭包 2个计数器count1和count2的环境是相互独立的 在count1的闭包环境中对变量做的改变不会影响到另一个闭包环境中的变量
二、对作用域、作用域链的理解
一、作用域
1、全局作用域
最外层函数和最外层函数外面变量拥有全局作用域
未声明就赋值的变量
window对象的所有属性
2、函数作用域
函数内部定义的变量是函数作用域
3、块级作用域
let const 定义的变量是块级作用域
二、作用域链
访问一个变量,当前作用域找不到会去上一层作用域查找,找的到会返回,到全局作用域都没有找到就会报错 作用域链是分层的,内层的作用域可以访问外层的作用域的变量、方法;外层的作用域无法访问内层的
三、执行上下文的类型
1、全局执行上下文
不在函数内部的都是全局执行上下文,首先会创建一个window对象,设置this等于window,一个程序只有一个全局上下文
2、函数执行上下文
当一个函数被调用的时候,会为该函数创建一个函数执行上下文,可以有多个
3、eval执行上下文
没用过
四、执行上下文栈
js引擎使用执行上下文栈管理执行上下文,js执行代码的时候,首先遇到全局代码,创建全局执行上下文并压入栈,遇到调用函数,会创建函数执行上下文压入栈顶,js引擎会执行栈顶函数,执行完弹出,继续执行下一个函数上下文,当所有代码执行完,弹出全局执行上下文
五、执行上下文过程
1、创建阶段
将代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好。编译阶段完成好,才会进入执行阶段
2、执行阶段
对执行上下文的运行