1.闭包函数
闭包是一个函数
2.闭包作用
函数外面访问函数内部变量(局部变量)
3.闭包语法
闭包语法有很多种写法,但是一般分为三个环节
1)在外部函数中 声明一个闭包函数 (闭包函数可以访问1级链中的局部变量)
2)在闭包函数中,返回你想要访问的局部变量
3)在外部函数中,返回这个闭包函数
闭包本质:沟通 全局作用域 与 局部作用域 的一座桥梁
闭包作用引入:
1)js作用域
全局作用域(全局变量) 生命周期 : 从页面加载开启, 到页面关闭结束
局部作用域(局部变量) 生命周期 : 从执行函数开始, 到函数执行完毕结束
//1. 需求 : 在函数外面访问函数内部变量
function outer() {
var person = {
name: 'kunkun'
}
}
outer(); // 调用函数:执行函数体
// console.log(person); // 报错 person is not defined
// 报错原因: 局部变量声明周期 是从执行函数开始 到执行函数结束
// 生命周期:一个变量从开辟内存,到被系统回收的过程
2)返回值:return 值
弊端: 每一次返回的数据,不是同一个 (有一定的内存资源浪费)
function outer() {
var person = {
name: 'kunkun'
}
return person;
}
var p1 = outer();
console.log(p1);
var p2 = outer();
console.log(p2);
//思考 : p1 和 p2 虽然存储数据一样,但是是同一个对象吗?
// 不是同一个 : outer函数每调用一次,就会在堆中开辟空间, 虽然数据一样但是两个地址不同
console.log(p1 == p2); //false
3)使用闭包
function outer() {
// 1级链
var person = {
name: 'kunkun'
}
// 1. 在外部函数中声明一个内部函数(闭包函数)
function closure() {
// 2.在闭包函数中,返回想要获取的局部变量
return person;
}
// 3.在外部函数中,返回这个闭包函数
return closure;
}
// 调用外部函数:返回闭包函数
var bibao = outer();
// 掉用闭包函数:得到person
var p1 = bibao();
console.log(p1);
var p2 = bibao();
console.log(p2);
// p1和p2是同一个对象吗? 是
console.log(p1 == p2); //true
4.闭包注意点
得到 相同的值:外部函数(outer)调用一次,闭包函数(closure)调用多次
得到 不同的值:外部函数调用多次,闭包函数调用一次
function outer() {
var num = Math.ceil(Math.random() * 100);
// (1)声明一个闭包函数
function closure() {
// (2)返回你想要访问的局部变量
return num;
}
return closure;
}
// 1. 得到的是 同一个变量:外部函数调用依次,闭包函数调用多次
// 外部函数调用依次
var bibao = outer();
// 闭包函数调用多次
var num1 = bibao();
var num2 = bibao();
var num3 = bibao();
console.log(num1, num2, num3); //同一个num
// 2. 得到的是 不同的变量: 外部函数调用多次,闭包函数调用依次
// 外部函数调用多次
/*
var bibao1 = outer();
var n1 = bibao1();
var bibao2 = outer();
var n2 = bibao2();
var bibao3 = outer();
var n3 = bibao3();
*/
// 函数连续调用
var n1 = outer()();
var n2 = outer()();
var n3 = outer()();
console.log(n1, n2, n3); // 不同的数字
// 3. 测试题
// 有一个投票机,作用 投票
function toupiao() {
var num = 10;
function closure() {
num++;
return num;
}
return closure;
}
// 同一个投票机,连续投三票
// 相同的值:外部函数调用一次,闭包函数调用多次
var bibao = toupiao();
var p1 = bibao();
var p2 = bibao();
var p3 = bibao();
console.log(p1, p2, p3); // 11 12 13
// 三个投票机,各投一票
// 不同的值,外部函数调用多次,闭包函数调用一次
var piao1 = toupiao()();
var piao2 = toupiao()();
var piao3 = toupiao()();
console.log(piao1, piao2, piao3); // 11 11 11
5.闭包练习与应用场景
1)点击显示按钮索引
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
var buttonList = document.querySelectorAll('button');
// 遍历按钮数组,注册点击事件
for (var i = 0; i < buttonList.length; i++) {
// 声明外部函数
function outer() {
// 声明局部变量
var num = i;
// 1.声明闭包函数
function closure() {
// 2. 访问局部变量
console.log(num);
}
// 2. 返回闭包函数
return closure;
}
// 循环每走一次,就会调用一次外部函数,得到一个闭包函数
var bibao = outer();
// 事件处理函数,注册不会执行,而是点击执行
buttonList[i].onclick = bibao;
}
// 高级简洁写法
for (var i = 0; i < buttonList.length; i++) {
buttonList[i].onclick = function (num) {
return function () {
console.log(num);
};
}(i);
}
</script>
</body>
2)循环中的定时器(面试题)
面试题:下面代码打印是 1 2 3吗,如果不是,那么请问怎么才能打印 1 2 3
分析: 打印 4 4 4
原理:定时器类似于事件.注册的时候函数不会执行的,而是等页面加载完毕之后过一会执行.与事件区别只是时机不同
此时循环早已结束,全局变量i为固定值4
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
使用闭包解决
for (var i = 1; i <= 3; i++) {
// 声明外部函数
function outer() {
var num = i;
// 使用闭包
function closure() {
console.log(num);
}
return closure;
}
// 调用外部函数,得到一个闭包函数(调用三次外部函数,得到三个不同闭包 1 2 3 )
var bibao = outer();
setTimeout(bibao, 1000);
}
// 高级精简写法
for (var i = 1; i <= 3; i++) {
setTimeout(function (num) {
return function () {
console.log(num);
}
}(i));
};
3)斐波那契数列
三种方式:
递归 : 性能最低,浪费CPU (存在重复计算)
数组+循环 : 性能其次,浪费内存 (数组会存储很多元素,求100列数组就会存100个元素)
闭包+递推法 :性能最好 (没有重复计算,也不会浪费内存)
function feiBo() {
var arr = [1, 1, 1];
//1.声明闭包函数
function closure(n) {
//每一次计算 : 数组复位
arr = [1,1,1];
for (i = 2; i < n; i++) {
//计算
arr[2] = arr[1] + arr[0];
//结果前移,用于下一次计算
arr[0] = arr[1];
arr[1] = arr[2];
};
//2.返回访问的局部变量
return arr[2];
};
//3.返回闭包函数
return closure;
};
//外部函数调用一次得到闭包函数
var bibao = feiBo();
//闭包函数调用多次
console.log( bibao(10) );
console.log( bibao(15) );
console.log( bibao(20) );
4)闭包经典面试题
1.
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
return function () { return this.name }
}
};
console.log(object.getNameFunc()()); //"the window"
//(1) object.getNameFunc() : 返回一个全局的匿名函数
//(2) 调用这个匿名函数 : 此时this指向window
// var fn = object.getNameFunc();//全局函数
// fn();// this:window
2.
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
// this : object 声明局部变量存储1级链的this :object
var that = this;
return function () {
// 返回局部变量that的值
return that.name
}
}
};
console.log(object.getNameFunc()());//'myobject'
//(1) object.getNameFunc() : 调用对象的方法 , 返回全局匿名函数
//(2) 调用匿名函数 : 此时匿名函数中的this还是window,但是这个函数返回的不是this,而是that
5)闭包经典应用场景:沙箱模式
沙箱模式(js设计模式之一): 是一个封闭的内存空间(局部作用域), 通常是一个匿名函数自调用
.沙箱模式作用 :
封闭的内存空间 : 不存在全局变量污染
模块化开发 : 一个功能对应一个作用域
访问沙箱的成员(暴露接口) : 把沙箱内部的成员,赋值给window属性
(function (w) {
//局部作用域
var obj = {
name: '张三'
};
//封装 : 将函数放入对象的方法中
obj.eat = function(){};
obj.sayHi = function(){};
/*如何在全局作用域 访问 沙箱内部的成员(局部变量) : 闭包场景
注意点: 千万不要在沙箱内部访问全局变量
a. 破坏封装性 : 好不容易封起来了,你又用全局的
b. 避免代码压缩错误 : (后面代码会压缩成一行,去掉空格,复杂英文变成简写)
c. 解决方案: 函数传参,把全局变量作为参数传递
*/
//暴露接口 : 让外部可以访问沙箱里面的成员
w.obj = obj;
})(window);