首页 > 编程知识 正文

javascript的闭包,js闭包实现

时间:2023-05-05 07:31:13 阅读:285197 作者:3433

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 = 0console.log(add()); // 0console.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);

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。