// 闭包
// 何为闭包?: 有权访问另外一个函数作用域中变量的函数,简单形式就是:外部函数中生成一个内部函数,并通过return来返回,内部函数可以访问外部函数作用域中的函数,因此,外部函数调用之后变量对象本该被销毁,但由于闭包的存在,我们仍然可以在全局访问外部函数的变量。
// 关键:闭包引用外部函数的变量;在外部函数的外部调用闭包
// 注意事项:函数的作用域会一直保存到闭包不存在
// example:
function arrFn(){
var arr = []
for(var i=0;i<10;i++){
arr[i] = function(){
return i
}
}
return arr
}
arrFn().map(((ele)=>{
console.log(ele())
}))
// 输出为10个10
// arr数组中为10个匿名函数,当arrFunc执行完毕之后,作用域被销毁,但是变量对象仍然保存在内存中得以被匿名访问,遍历数组执行数组中的每个匿名函数时,匿名函数访问的i均为10
function arrFunc(){
var arr = []
for(var i=0;i<10;i++){
arr[i] = (function(){
return i;
})()
}
return arr;
}
console.log("arrFunc",arrFunc())
// 返回[0,1,2,3,4,5,6,7,8,9] arr数组中保存的是匿名函数立即执行后的返回值
// 匿名函数外层再嵌套一层函数,来保存循环过程中的每个i值
function arrFunc2(){
var arr = []
for(var i=0;i<10;i++){
arr[i] = function(num){
return (function(){
return num;
})
}(i)
}
return arr
}
arrFunc2().map((ele)=>{
console.log("arrFunc2",ele())
})
// 输出为 0-9
// 闭包的使用场景
// 1. 封装变量
// 场景:数据请求,上一次数据请求结果返回后,才能进行下次请求,设置flat标志是否请求完成 false-未结束,true--请求结束
// 未使用闭包
var flat = false
var fn = function () {
if(flat){
// ajax进行数据请求
$.ajax({
url: '',
data: {},
type: 'json',
success: function () {
},
complete: function () {
flat = true
}
})
}else{
return;
}
}
// 使用闭包来写
var fn = function () {
var flat = false
return (function () {
if(flat){
// ajax进行数据请求
$.ajax({
url: '',
data: {},
type: 'json',
success: function () {
},
complete: function () {
flat = true
}
})
}else{
return;
}
})
}
// var f = fn()
// f()
// VS: 不使用闭包,flat为全局变量暴露在全局,污染全局环境,使用闭包 flat作为局部变量,进行封装,不会被全局中其他方法访问/改变
// 2. 利用闭包,返回/生成其他的函数
// 场景: 邀新banner为例,不同的module_id对应执行不同的方法
// 不使用闭包
// if(module_id === 'invite0'){
// f1()
// }else if(module_id === 'inviteN'){
// f2()
// }
// 使用闭包
var action = function(module_id){
// if(module_id === 'invite0'){
// return (function () {
// // "invite0"时执行的方法
// })()
// }else if(module_id === 'inviteN'){
// return (function () {
// // "inviteN"时执行的方法
// })()
// }
// 优化:
var map = {
"invite0": function () {
console.log("0")
},
"inviteN": function () {
console.log("N")
}
}
return map[module_id]()
}
// action("invite0")
// VS: 利用闭包将不同module_id执行不同方法 封装成一个方法,使用时,直接调用action即可,
// 3. 解决浏览器兼容性问题
// 添加事件监听为例
// 不使用闭包
// 使用闭包
// 闭包一
var addEventListener = function (dom, type, fn) {
if(dom.addEventListener){
return (function () {
dom.addEventListener(type, fn, false)
})()
}else{
return (function () {
dom.attachEvent("on"+type, fn)
})()
}
}
// 闭包二: 上述闭包 每次调用闭包方法都会进行一次dom.addEventListener的判断 -- 只在第一次运行的时候判断
var addEventListener = function (dom, type, fn) {
if(dom.addEventListener){
return (function () {
addEventListener = dom.addEventListener(type, fn, false)
})()
}else{
return (function () {
addEventListener = dom.attachEvent("on"+type, fn)
})()
}
}
// 4. 函数科里化
// 预设情景: add方法,实现 add(1) add(2) add(3) add() : 调用add方法,当没有参数时,对前面添加的
var add = function (arr) {
var result = 0
for(let i=0;i<arr.length;i++){
result += arr[i]
}
return result
}
var addEv = function (...args) {
var array = args
return (function(...arg){
if(arg.length === 0){
return add(array)
}else{
array = array.concat(arg)
}
})
}
var a = addEv(1,2)
a(3,4)
a(5)
a() //求和:15
// 闭包中的this指向 --注意下面代码需作为html的脚本文件运行,不要利用run code直接运行脚本文件
var name = "window"
var obj = {
name: "object",
getName: function(){
return function(){
return this.name
}
}
}
console.log(obj.getName()())
// 匿名函数的执行具有全局性,匿名函数中的this指向全局,输出为window
// 要使this指向外部函数环境,下面的写法
var obj2 = {
name: "object2",
getName: function(){
let _this = this
return function(){
return _this.name
}
}
}
console.log(obj2.getName()()) // object2
// 闭包引起的内存泄漏
// js中的垃圾回收机制
// 1. 标记清除: 垃圾运行机制运行时,会给存储在内存中所有的变量都加上标记,然后会清除掉环境中变量的标记和被变量引用的变量的标记,此后如果变量再被标记,则表示该变量准备被删除
// 2. 引用计数: 跟踪记录每个值被引用的次数,声明一个变量被将一个引用类型的值赋给它,这个值的引用次数为1,当这个值被赋值给另外一个变量,引用次数加1,如果一个变量脱离这个值的引用,这个值的引用次数减1,当一个值的引用次数为0,就会等待垃圾回收器的回收。
// 引用计数策略来进行垃圾回收时:存在循环引用时,会造成内存泄漏(不再使用的内存不能被清楚,占用内存资源)
window.onload = function(){
var element = document.getElementById("id")
element.onclick = function(){
alert(element.id)
}
}
// 存在循环引用:匿名函数赋给element的onclick属性,匿名函数中又调用了element的id
window.onload = function(){
var element = document.getElementById("id")
var id = element.id // 解除循环引用
element.onclick = function(){
alert(id)
}
element = null // 引用对象清除
}
// 定时器清除
(function(){
var a = 0
let timer = setTimeout(function(){
console.log(a++)
},1000)
})()
// 定时器内引用外部函数变量对象,循环引用
// clearTimeout(this.timer)
// this.timer = null
// 总结:
// 闭包优缺点:优: 私有化变量,避免全局环境污染;缺: 内存泄漏
// 面试题:
function fun(n,o){
console.log(o)
return {
fun: function(m){
return fun(m,n)
}
}
}
var a = fun(0) // undefined
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
var b = fun(0).fun(1).fun(2).fun(3) // undefined 0 1 2
var c = fun(0).fun(1) // undefined 0
c.fun(2) // 1
c.fun(3) // 1
闭包
最新推荐文章于 2024-09-11 22:43:00 发布