(今天第一次写博客,目的是给自己做学习笔记和个人知识点总结)
什么是闭包?(直入主题)
个人的解释就是函数内部再return一个函数或者内部函数要访问他的外部变量
在学闭包之前必须先了解 变量的作用域
① 函数内部可以直接读取全局变量
example:
var num = 123;
(() => console.log(num))()
// 123
log的结果num为123
② 在函数外部无法读取函数内部的局部变量
(ps函数内部定义的变量必须要用var 等来声明,否则执行js的时候被视为全局变量)
example:
(()=>{
var n = 999
})()
console.log(n) // error
因为我们无法直接访问函数内部的变量的值,所以要通过闭包来读取函数局部变量的值
example:
(()=> {
var a = 123
return ()=> a
})()
// 或者没有return
((time)=> {
var a = 123
setTimeout(()=> {
// 处理逻辑...
}, time)
})()
闭包作用?
① 访问函数内部变量的值(上面说了)
② 让闭包的值始终保存在内存中
example:
var counter = (()=>{
var num = 999
function changeBy(val){
num += val
}
return {
increment: () =>{ changeBy(1) },
decrement: () =>{ changeBy(-1) },
result: () =>{ return num }
}
})()
console.log( counter.result() ) // 999
//接下来我们改变下局部变量num值
counter.increment() //num + 1
//再打印num的值,已经发生改变
console.log(counter.result()) // 1000
counter.decrement()
counter.decrement()
console.log(counter.result()) // 998
在counter中,changeby被counter.increment,
counter.decrement, counter.result所共享,在这个匿名自调用函数下的changeBy和num都无法被外部所访问,必须通过匿名函数返回的三个公共函数来访问。
每次声明变量的时候都会创建一个新的函数储存空间
var counter = function() {
var num = 999
function changeBy(val) {
num += val
}
return {
increment: () =>{ changeBy(1) },
decrement: () =>{ changeBy(-1) },
result: () =>{ return num }
}
}
var n1 = counter()
var n2 = counter()
n1.result() // num = 999
n1.increment() //num + 1
// 此时再打印,num已经改变
n1.result() // num = 1000
n2.result() // num = 999
数内部私有上述代码中分别用n1,n2接收了counter函数的返回值,但是n1和n2他们各自都是独立的,每个闭包都是引用自己词法作用域内的变量 num。
闭包内的this指向问题
example:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
//此时的this指向全局Window
return this.name
}
}
}
alert(object.getNameFunc()()) // "The Window"
如果想要this的指向当前函数,在函数内部定义私有变量
example:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
// 因为函数内部私有变量外部无法读取
// 此时的this指向object
var that = this
return function(){
return that.name
}
}
}
alert(object.getNameFunc()()) // "My Object"
最后分享一个闭包案例:
<body>
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
</body>
(ps样式就不写了,主要是看闭包运行机制)
javascript逻辑部分:
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
]
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus =
function() {
showHelp(item.help);
}
}
setupHelp()
循环helpText数组获取对应的每一项的<p>元素并注册onfocus事件处理函数,让最上面的<p>提示框写入对应的help提示内容。
逻辑看起来没啥问题,运行这段代码之后,无论鼠标焦点在哪个input上, 最上面的<p>元素始终显示的是数组的最后一项的‘help’信息,并没有达到想要的效果。
原因是赋值给onfocus的是闭包。用我自己的话讲就是setupHelp函数下面还有个onfocus事件处理函数形成了闭包,在setupHelp函数域中存在一个变量item,在js执行机制中,循环在 onfocus事件触发之前早已执行完成,item的值已被锁定在循环对象的最后一项。
es6箭头函数=>完美解决闭包这个问题:
看代码=>
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
(() => {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
]
helpText.map(a => {
return document.getElementById(a.id).onfocus = function() {
showHelp(a.help)
}
})
})()
map循环使用匿名箭头函数就不存在闭包问题了。
解决这个闭包问题另一种方案就是使用更多的闭包:
example:
function showHelp(help) {
document.getElementById('help').innerHTML = help
}
function makeHelpCallback(help) {
return function() {
showHelp(help)
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
]
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i]
document.getElementById(item.id).onfocus = makeHelpCallback(item.help)
}
}
setupHelp()
makeHelpCallback函数为后面的每个回调提供了一个新的执行环境(不再处于setupHelp同一个函数的作用域内),help的此时指向helpText数组中所对应的值
还可以匿名函数闭包:把函数内部私有变量和闭包内的回调放在同一作用域
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
(() => {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
]
for (var i = 0; i < helpText.length; i++) {
// document.getElementById(item.id).onfocus = callbackHelp(item.help)
(()=> {
var item = helpText[i]
document.getElementById(item.id).onfocus = () => {
showHelp(item.help)
}
})()
}
})()
性能问题
总结一点,为了没必要的使用闭包,函数内的私有变量用let关键词来代替var
如果不是某些特定任务需要使用闭包,
在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
为了避免定义没有好处的闭包,在声明函数内部定义函数的时候,可以通过函数的prototype原型上添加事件处理函数,直接上代码吧:
example:
function MyObject(name, message) {
this.name = name.toString()
this.message = message.toString()
this.getName = function() {
return this.name
}
this.getMessage = function() {
return this.message
}
}
在上面代码中,this.getName,this.getMessage两个没有必要的闭包,
为了性能不再使用无用的闭包可以修改成:
function MyObject(name, message) {
this.name = name.toString()
this.message = message.toString()
}
MyObject.prototype.getName = function() {
return this.name
}
MyObject.prototype.getMessage = function() {
return this.message
}