第六章:闭包
什么是闭包
闭包就是能够读取其他函数内部变量的函数,由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成
定义在一个函数内部的函数
。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的好处:延展了函数的作用域
闭包的弊端:
一般情况下:函数执行完毕,立即释放函数开辟的作用域(用完就释放,节省内存)
但是如果使用闭包:因为内部函数还要访问外部函数作用域中的变量,外部函数执行完毕,不会立即释放开辟的作用域,造成程序性能降低,消耗内存。
闭包的用途:
- 可以在函数外部读取函数内部成员
- 让函数内成员始终存活在内存中
闭包:在一个作用域中可以访问另一个作用域的变量
// 未发生闭包
function fn() { // 当fn函数执行时,会开启一个作用域。执行完毕后,作用域和作用域中的变量都会被销毁
var n = 10;
return n;
}
fn();
发生闭包,闭包特点:延展了函数的作用域范围
function fn() {
var n = 10;
return function () {
return n;
}
}
var f = fn(); // fn函数执行完毕后,因为f函数还要访问fn作用域中的变量n
console.log(f());
闭包演示
闭包演示:案例 1
闭包的概念:在一个作用域中可以访问另一个作用域的变量或者函数
function getRandom() {
var random = parseInt(Math.random() * 10) + 1;
return function () {
return random;
}
}
var fn = getRandom();
console.log(fn());
console.log(fn());
闭包演示:案例 2
注意:形参n也是局部变量
function getFun(n) {
return function (m) { // 一个函数只有一个返回值 且 就近返回数值
return m;
}
}
// 求 100 + m
var fn100 = getFun(100);
// 求 1000 + m
var fn1000 = getFun(1000);
console.log(fn100(1));
console.log(fn1000(1));
闭包演示:经典面试题
面试题一:使用闭包改造代码
console.log('start');
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
console.log('end');
// 代码执行顺序:start -> end -> 结果为3(出现3次)
// ************** 需要:使用闭包改造上面代码,要求依次输出:0 1 2 ************
console.log('start');
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 0);
})(i);
}
console.log('end');
// 代码执行顺序:start -> end -> 依次:0 1 2
面试题二:点击不同的按钮,改变页面字体大小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
我是字体的演示
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button id="btn3">按钮3</button>
<script>
// ..........传统方法1:................
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
btn1.onclick = function() {
document.body.style.fontSize = '12px';
}
btn2.onclick = function() {
document.body.style.fontSize = '14px';
}
btn3.onclick = function() {
document.body.style.fontSize = '16px';
};
// ..........方法2:闭包的应用.............
// 创建一个函数,设置body的字体大小
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
// 创建一个函数makeFun,设置body的字体大小
function makeFun(size) {
return function () { // 函数不需要传参数? 如果本身没有传入参数可以去上级去找
document.body.style.fontSize = size + 'px';
}
}
// 真正执行点击事件的函数是:函数makeFun返回的函数
btn1.onclick = makeFun(12);
btn2.onclick = makeFun(14);
btn3.onclick = makeFun(16);
</script>
</body>
</html>
面试题二:点击不同的按钮,改变页面字体大小(闭包写法)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
我是字体的演示
<div id="box">
<button size="12">按钮1</button>
<button size="14">按钮2</button>
<button size="16">按钮3</button>
</div>
<script>
// ...................闭包的应用................
var box = document.getElementById('box');
var buttons = box.children;
// 创建一个函数makeFun,设置body的字体大小
function makeFun(size) {
return function () { // 不需要传参数? 如果本身没有传入参数可以去上级去找(js基础作用域链)
document.body.style.fontSize = size + 'px';
console.log(size);
}
}
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
// 获取标签的自定义属性getAttribute
var size = btn.getAttribute('size');
btn.onclick = makeFun(size); //点击的时候才会调用return返回的匿名函数
}
</script>
</body>
</html>
需求:点击li的时候输出当前li对应的索引(闭包实现)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul id="heroes">
<li>安琪拉</li>
<li>李白</li>
<li>诸葛亮</li>
<li>狄仁杰</li>
</ul>
<script>
// .......推荐方式1:给li注册点击事件...............
// 需求:点击li的时候输出当前li对应的索引
var heroes = document.getElementById('heroes');
var list = heroes.children;
for (var i = 0; i < list.length; i++) {
var li = list[i];
li.index = i; // 让对象的自定义属性:每一次循环,记录每次的i
li.onclick = function () {
// 2 点击li的时候输出当前li对应的索引
console.log(this.index);
}
}
// .......方式2:给li注册点击事件....................
// 需求:点击li的时候输出当前li对应的索引
var heroes = document.getElementById('heroes');
var list = heroes.children;
for (var i = 0; i < list.length; i++) {
var li = list[i];
// 每一个i,对应一个作用域
// 内部的function还要访问外部的变量i;延展了作用域
(function (i) {
li.onclick = function () {
// 2 点击li的时候输出当前li对应的索引
console.log(i);
}
})(i);
}
</script>
</body>
</html>
闭包的思考题
思考题 1:没有发生闭包
// 思考1:返回值是函数,不一定会存在闭包
// 闭包:一个作用域可以访问另一个作用域的变量(不能是全局作用域)或者函数
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name; // 函数中的this指向调用者fn(),所以this--->window
};
}
};
var fn = object.getNameFunc()
fn();
console.log(object.getNameFunc()()); // 结果是:The Window
思考题 2:发生了闭包
,闭包发生在getNameFunc
函数
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this; // this指向object,将this赋值给that,此时that也指向object
return function () {
return that.name; // that也指向object
};
}
};
console.log(object.getNameFunc()()); // My Object