参考:
概念
-
作用域
-
作用域变量往上层找
-
作用域变量无使用会被销毁
-
子函数被使用时父级环境将被保留 -- 闭包
-
某函数可以使用其他函数的作用域变量 -- 闭包
-
构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留 -- 闭包
-
本质上:闭包就是函数内部和函数外部链接的一座桥梁
什么是闭包?
-
当内部函数引用了外部函数的变量时就产生了闭包
-
通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
闭包需要三个条件
-
函数嵌套
-
访问所在的作用域
-
所在作用域外被调用
闭包的作用
①可以私有化变量;
②延长了局部变量的时间;
闭包的问题
内存溢出:
内存被用满了,程序自动崩溃了。
解决办法:让内部函数变成null
let f = test();
f = null; // 释放内存
内存泄露:
占用的内存太多了,没有及时释放,积累到一定程度就会内存溢出。
this指代:
箭头函数
或则重新指代
立即执行函数IIFE
立即执行函数和闭包没啥关系,不过经常和闭包放在一起使用罢了。
两种写法
必须用( )括起来,因为直接写function会报错,
通常情况下前面加个!,前面有代码没有分号就不会报错了
!(function(){})() // 常用
!(function(){}())
可以与闭包配合起来使用
一般一些js框架都是这样玩的
let add = (function(){
let count = 0
return function() {
console.log(count++)
})()
console.log(add)
console.log(add())
console.log(add())
console.log(add())
console.log(add())
for循环作用域和闭包
早期var带来的问题
// for循环条件中的var,定义的是全局的
for(var i=1;i<=5;i++) {
setTimeout(() => {
console.log(i)
}, i*1000)
}
// 利用闭包可以解决这个问题
for(var i=1;i<=5;i++) {
(function(j){
setTimeout(() => {
console.log(j)
}, j* 1000)
})(i)
}
闭包的生命周期
产生:在嵌套的内部函数定义执行完时就产生了,因为有函数提升的。(不是在外部函数调用时,也不是在外部函数定义时);
死亡:在嵌套的内部函数成为垃圾对象时;
function f1() {
//此时就已经产生闭包(因为函数提升)
var b = 2
function fn() {
b++
console.log(b)
}
return fn
}
var f = f1()
f()
f = null //闭包消失,因为内部函数成为了垃圾对象(没有变量在引用它)
闭包的使用
封装高阶函数
使用闭包返回数组区间元素
let arr = [3, 2, 4, 1, 5, 6];
function between(a, b) {
return function(v) {
return v >= a && v <= b;
};
}
console.log(arr.filter(between(3, 5)));
下例使用闭包按指定字段排序
let lessons = [ { title: "媒体查询响应式布局", click: 89, price: 12 }, { title: "FLEX 弹性盒模型", click: 45, price: 120 }, { title: "GRID 栅格系统", click: 19, price: 67 }, { title: "盒子模型详解", click: 29, price: 300 }];
function order(field) {
return (a, b) => (a[field] > b[field] ? 1 : -1);
}
console.table(lessons.sort(order("price")));
计数器
- 不希望变量暴露在外部,即希望只能由内部函数修改
- 希望多次调用后,能够每次都更新并保存变量的结果
作用:读取函数内部变量,这些变量始终在内存中
function a(){
var start =0
function b(){
return start++
}
retrun b
}
var inc = a()
log(inv()) // 0
log(inv()) // 1
log(inv()) // 2
inc = null // 释放内存
封装对象的私有属性和方法
function Person(name){
// 私有的属性
var age
// 私有的方法
function setAge(n){
age = n
}
function getAge(){
return age
}
return {
name:name,
setAge:setAge,
getAge:getAge
}
}
var p1 = new Person("hello")
p1.setAge(18)
console.log(p1.getAge())
p1 = null
定义js模块
往往配合立即执行函数
- 具有特定功能的js文件
- 将给所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
Demo
Demo1
如果子函数被使用时父级环境将被保留
function hd() {
let n = 1;
return function() {
let b = 1;
return function() {
console.log(++n);
console.log(++b);
};
};
}
let a = hd()();
a(); //2,2
a(); //3,3
Demo2
构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留。这能算是闭包吗?算
function User() {
let num = 1;
this.show = function() {
console.log(num++);
};
}
let a = new User();
a.show(); //1
a.show(); //2
let b = new User();
b.show(); //1
Demo3
使用外层函数的参数,也
闭包的10种表现形式
1. 返回值 最常见的一种形式
var fn = function(){
var name = 'mjj'
return function(){
return name;
}
}
var fnc = fn();
console.log(fnc())
2. 函数赋值 一种变形的形式是将内部函数赋值给一个外部的变量
var fnc;
var fn = function(){
var name = "mjj";
var a = function(){
return name;
}
fnc =a
}
fn();
console.log(fnc())
3. 函数参数
function fnc(f){
console.log(f())
}
function fn(){
var name = "mjj"
var a = function(){
return name
}
fnc(a)
}
fn()
4. IIFE
function fnc(f){
console.log(f())
}
(function(){
var name = "mjj"
var a = function(){
return name
}
fnc(a)
})()
5. 循环赋值
function foo(){
var arr = []
for(var i = 0;i<10;i++){
// 这里没有闭包的效果 结果是10
// arr[i] = function(){
// return i
// }
// 闭包做法一
// (function(i){
// arr[i] = function() {
// return i
// }
// })(i)
// 闭包做法二
// arr[i] = (function(n){
// return function(){
// return n;
// }
// })(i)
}
return arr
}
var bar = foo();
console.log(bar[3]())
getter和setter函数 将要操作的变量保存在函数内部,防止暴露在外部。
var getValue, setVule
(function(){
var num = 0
getValue = function(){
return num
}
setValue = function(v){
num = v
}
})()
console.log(getValue())
setValue(10)
console.log(getValue())
迭代器 类似计数器
var add = function(){
var num = 0
return function(){
return num++
}
}
console.log(add())
console.log(add())
// 自己实现一个迭代器
// ["alex","mjj","阿黄"]
function setUp(arr){
var i = 0
return function(){
return arr[i++]
}
}
var next = setUp(["alex","mjj","阿黄"])
console.log(next())
console.log(next())
区分首次
var firstLoad = (function(){
var list = []
return function(id){
if(list.indexOf(id) >= 0){
return false
}else{
list.push(id)
return true
}
}
})()
firstLoad(10) // true
firstLoad(10) // false
缓存机制
// 未加入缓存
function mult(){
// arguments
var sum =0
for(var i=0;i<arguments.length;i++){
sum = sum + arguments[i]
}
return sum
}
console.log(mult(1,2,3,4,5,5,4)) //求和
console.log(mult(1,2,3,4,5,5,4)) //求和
// 有缓存机制
{
key:value
1,2,3,4,5,5,4:
}
var mult = function(){
// 缓存对象
var cache = {};
var calculate = function(){
var sum =0
for(var i = 0;i<arguments.length;i++){
sum = sum +i
}
return sum
}
return function(){
// 对cache对象进行操作
Array.prototype.join.call(arguments,',')
}
}()
console.log(mult(1,2,3,4,4,5,5,6,9))
console.log(mult(1,2,3,4,4,5,5,6,9))
img对象图片上限
// new Image() 进行数据上报
// 低版本流浪器在进行数据上报会丢失左右的数据
// 因为http是异步的,还没有完成上报就销毁了作用域了
var report = function (src){
var img = new Image();
img.src = src;
}
report("url")
// 使用闭包来做图片上传
var report = function(src){
var imgs = []
return function(src){
var img = new Image();
imgs.push(img)
img.src = src
}
}()
report("url")