JavaScript闭包使用场景

JavaScript闭包使用场景

闭包就是外层函数将内层函数返回出去,并且内层函数执行时带着外层函数的作用域,可以使用外层函数内部的变量,这些变量始终保存在内存中
本质:闭包相当于桥梁,连接函数内核函数外。
特点:保存函数的诞生环境
使用原因:函数外想要获取函数内部的变量,通过闭包形式
注意事项:闭包会将作用域保存在内存中,不用时需要将变量设置为null,防止内存泄漏。

闭包的形式

返回值形式

函数中返回一个函数

// 返回值:直接在函数中返回外层函数的变量
function fn() {
    var age = 18;
    return function () {
        return age;
    }
}
var func = fn();
console.log(func()); // 18

函数赋值形式

将内部函数赋值给外部变量

// 函数赋值
var fn2;
function fn() {
    var age = 18;
    fn2 = function () {
        console.log(age);
    }
};
fn2();  // 18

函数参数形式

将内层函数当做参数传入全局定义的函数中

function fnc(fn) {
    age = fn();
    console.log("已经"+age);
}
function fn() {
    var age = 18;
    fn2 = function () {
        return age;
    }
    fnc(fn2);
};
fn(); // 已经18

IIFE自执行函数

使用自执行函数,省去调用过程
上文自执行函数介绍

function fnc(fn) {
    age = fn();
    console.log("已经"+age);
}
(function fn() {
    var age = 18;
    fn2 = function () {
        return age;
    }
    fnc(fn2);
})();

相关代码:

循环赋值问题:查看代码
封装私有方法:查看代码
计数器和迭代器:查看代码
实现缓存机制:查看代码

闭包用途

计数器

计数器
通过闭包制作计数器
作用:读取函数内部的变量,这些变量始终保存在内存中

function fn() {
    count = 0;
    return function () {
        ++count;
        console.log("这是第"+count+"个函数;");
    }
}
var ins = fn();
ins();  // 这是第1个函数;
ins();  // 这是第2个函数;
ins = null;  // 防止内存泄漏

迭代器

类似于计数器

// 传入一个数组,返回一个迭代器
function initIter(iter) {
    arr = iter;
    count = 0;
    return {
        next:function () {
        if(count < arr.length){
            return {
                value:arr[count++],
                done:false
            }

        }
        return {
                value:undefined,
                done:true
            }
    }
    }
}
var arr = ["apple","banana","orange"];
var it = initIter(arr);
console.log(it.next());  // {done: false,value: "apple"}
console.log(it.next());  // {done: false,value: "banana"}
console.log(it.next());  // {done: false,value: "orange"}
console.log(it.next());  // {done: true,value: undefined}

自定义属性

通过设置函数属性,来保存信息

function add() {
    return add.count++;
}
add.count = 0
console.log(add());  // 0
console.log(add());  // 1

封装私有属性和方法

封装私有方法

// 闭包存储私有属性和方法
function Person() {
    var age;
    var setAge = function (n) {
        if(Number(n)){
            age = n;
        }
        return age;
    };
    var getAge = function () {
        return age;
    };
    return {
        setAge:setAge,
        getAge:getAge
    }
}
// 函数只能使用其提供的两个接口来操作age的值
var p1 = Person();
p1.setAge(18);
console.log(p1.getAge());
p1 = null;

闭包条件:函数嵌套、访问所在的作用域、在所在的作用域外被调用

自执行函数IIFE

自执行函数介绍
自执行函数,通过自执行函数,可以省去闭包初始化过程。
同样,自执行函数内部作用域和外部隔开的,不属于全局作用域,而是函数作用域

// 自执行函数 IIFE
// 方法一:作为函数表达式
(function () {

})();
// 方法二:作为匿名函数  
(function () { 
    
 }()); 
//注意加分号

例如:封装私有属性和方法

// 闭包存储私有属性和方法
var p1 = (function Person() {
    var age;
    var setAge = function (n) {
        if(Number(n)){
            age = n;
        }
        return age;
    };
    var getAge = function () {
        return age;
    };
    return {
        setAge:setAge,
        getAge:getAge
    }
})();
p1.setAge(18);
console.log(p1.getAge());
p1 = null;

缓存机制

缓存机制介绍
没有缓存机制时,每次使用都会重复调用函数,影响性能

// 没有缓存机制时
function factorial(n) {
    if(n < 0){
        throw RangeError("负数没有阶乘");
    }
    var res = 1;
    for(var i = 1;i <= n;i++){
        res *= i;
    }
    return res;
}
console.log(factorial(0));
console.log(factorial(1));
console.log(factorial(2));

添加缓存机制后

function factorial() {
    var fac_arr = {};  // 缓存:用来存储计算结果
    // 计算阶乘函数
    function calculate(n) {
        if(n < 0){
        throw RangeError("负数没有阶乘");
        }
        var res = 1;
        for(var i = 1;i <= n;i++){
            res *= i;
        }
        return res;
    }
    return function (n) {
        // 判断之前是否计算过,计算过直接取值,没有则计算,存储到缓存中
        if(n in fac_arr){
            return fac_arr[n];
        }else{
            var res = calculate(n);
            fac_arr[n] = res;
            return res;
        }
      }
}
var fac = factorial()
console.log(fac(0));  // 结果:1         fac_arr值{0: 1}
console.log(fac(10)); // 结果:3628800   fac_arr值{0: 1, 10: 3628800}
console.log(fac(10)); // 结果:3628800   fac_arr值{0: 1, 10: 3628800}

这个阶乘函数可以,更进一步,每计算一次,缓存一次

function factorial() {
    var fac_arr = [1,];  // 缓存:记录0-最大阶乘之间所有阶乘
    var max_fac = 0;  // 记录最大阶乘
    // 计算阶乘函数
    function calculate(n) {
        console.log(fac_arr);
        if(n < 0){  
        return RangeError("负数没有阶乘");
        }else if(n <= max_fac){   // 获取的值在该范围内,直接返回
            return fac_arr[n];
        }else{
            for(var i = max_fac;i < n;i++){
                fac_arr.push(fac_arr[i] * (i+1));
            }
            max_fac = n;
        }
        return fac_arr[n];
    }
    return calculate
}
var fac = factorial()
console.log(fac(20)); // 将1,2,3.。。20阶乘全部计算存储下来
console.log(fac(10));  // 直接从缓存中获取
console.log(fac(15));  // 直接从缓存中获取

优点:计算一次大的阶乘之后,0-20之间所有阶乘都存储下来。下次想要0-20之间的阶乘直接获取,不用重复计算。
缺点:当阶乘过大时,缓存存储的阶乘太多,消耗容量

循环闭包的错误结果

循环赋值
当我们在循环中使用闭包时,注意闭包的作用域

<ul id="myList">
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
</ul>
<script>
    //点击li标签时,获取li索引
    var myList = document.getElementById("myList");
    var lis = myList.children;  // 获取所有的li标签
    // 循环li标签
    for(var i = 0;i < lis.length;i++){
        lis[i].onclick = function () {
            this.innerHTML = i;
            console.log(i);
        }
    }
    console.log(i); // 5
</script>

运行上述代码时,会发现无论点击哪个li标签,返回的都是索引5.
原因:
1.onclick点击事件设置后不会立即执行,触发点击事件时才会调用事件
2.在事件中查找i变量,因为js中{}不为独立的作用域,每次循环使用同一个i,所以循环结束后i变量值为5。 即每次循环的i都是同一个i,相当于

// 循环li标签
var i;
for(i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        this.innerHTML = i;
        console.log(i);
    }
}
console.log(i); // 5

解决办法一:let变量

使用es6的let创建变量

//点击li标签时,获取li索引
var myList = document.getElementById("myList");
var lis = myList.children;  // 获取所有的li标签
// 循环li标签
for(let i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        this.innerHTML = i;
        console.log(i);
    }
}
console.log(i); //Uncaught ReferenceError: i is not defined

let变量会让{}变为独立作用域,即每循环一次,生成一个单独的作用域,每个作用域中都会创建一个i。
查找变量时,每个i的值不同。循环外部也不能访问i变量。

解决办法二:闭包

因为闭包可以保存数值,我们可以通过闭包来保存i的值

//点击li标签时,获取li索引
var myList = document.getElementById("myList");
var lis = myList.children;  // 获取所有的li标签
// 循环li标签
for(let i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        return (function (n) {
            lis[i].innerHTML = i;
            console.log(n);
        })(i);
    }
}

在js中,{}不能算作独立作用域,函数内部才能算独立作用域。(除了es6声明let、const等)
所以使用自执行函数,并且将i值传入函数,保存下来。

图片上报

因为img图片对象,在src获取url后,会下载图片,但是当前函数执行完毕后,变量会被销毁,导致丢失图片数据。
使用闭包解决

function report2() {
    var imgs = [];
    return function (src) {
        var img = new Image();
        img.src = src;
        imgs.push(img);
        return img;
    }
}
var rp = report2();
var img2 = rp("../photo.jpg");
App.appendChild(img2);
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页