在一个函数b内定义的一个函数a里,a使用了其父级b的变量或者函数。这样闭包便形成了!!
作用: 1. 获取函数内部的变量 2.将变量保存在内存中。3.定义私有变量,避免全局变量污染
注意: 闭包一定是需要的时候使用,勿滥用,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS闭包Closures</title>
</head>
<body>
<div>
<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>
</div>
</body>
<script>
//-------------------------------------------------------------------------------
//闭包
//闭包是由函数以及创建该函数的词法环境组合而成。
//这个环境包含了这个闭包创建时所能访问的所有局部变量
function makeAdder(a) {
return function (b) {
return a + b;
}
}
var add5 = makeAdder(5);
var add20 = makeAdder(20);
add5(6); // ? 5+6 11
add20(7); // ? 20 +7 27
/*
当调用 makeAdder 时,解释器创建了一个作用域对象,它带有一个属性:a,这个属性被当作参数传入 makeAdder 函数。
然后 makeAdder 返回一个新创建的函数(暂记为 adder)。
通常,JavaScript 的垃圾回收器会在这时回收 makeAdder 创建的作用域对象(暂记为 b),
但是,makeAdder 的返回值,新函数 adder,拥有一个指向作用域对象 b 的引用。
最终,作用域对象 b 不会被垃圾回收器回收,直到没有任何引用指向新函数 adder。
作用域对象组成了一个名为作用域链(scope chain)的(调用)链。
它和 JavaScript 的对象系统使用的原型(prototype)链相类似。
*/
//闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。
function demo07() {
var Counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
/*
创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。
这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。
这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
*/
}
// demo07();//闭包示例
function demo08() {
var makeCounter = function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
/*
你也许注意到了,两个计数器里面的变量是独立的,互不影响。
每次调用一个计数器时,是通过改变变量的值,进而改变这个闭包的此法环境,但是不影响其他闭包的变量
*/
}
//循环闭包常见错误
demo09();
function demo09() {//这样写会始终提示 age xxx
/*
原因: 因为onfocus以及她所在的词法作用域形成了闭包,三个元素有三个闭包,共享同一个词法作用域里
该域里有变量item,在触发事件之前for循环早已结束。item早已指向最后一个!!!
*/
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)' }
];
// debugger
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus =function () {
showHelp(item.help)
}
// document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
/*
当内部函数用到外部函数的参数时,该参数不会被js垃圾回收机制回收。
// 解决方法0: 使用更多闭包,效果同下面方法一样
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus =(function (help) {
return function(){
showHelp(help)
}
})(item.help)
}
// 解决方法1:使用更多闭包,showhelp引用了外部变量help,形成了闭包,
//而且其词法作用域均独立,循环传入的值被分别保存在内存中,不再指向最后一个。
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
// function setupHelp() {
// for (var i = 0; i < helpText.length; i++) {
// var item = helpText[i];
// document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
// }
// }
//解决办法2: 匿名闭包
for (var i = 0; i < helpText.length; i++) {
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // 马上把当前循环项的item与事件回调相关联起来
}
//解决办法3: item前的关键字使用let,让每个闭包都使用了块级作用域的变量
for (var i = 0; i < helpText.length; i++) {
let item = helpText[i];
...
}
//解决办法4: 绑定到某个对象上
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).help = helpText[i].help;
document.getElementById(item.id).onfocus =function () {
showHelp(this.help)
}
}
*/
setupHelp();
}
//如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,
//因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
//阮一峰
/*
在函数内部声明变量时一定要使用var命令,否则会成为全局变量
*/
function f1() {
b = 22;
}
f1();
console.log(b)
//外部读取局部变量,利用return
function f12() {
function f11() {
var n = 999;
function f2() {
alert(n);
}
return f2;
}
var result = f11();
result(); // 999
/*
能够读取其他函数内部变量的函数,因为只有函数内部的子函数才能读取内部变量
那么闭包可以理解为定义在函数内部的函数,且引用了函数同级的变量!
用处: 1.读取函数内部变量; 2.让变量的值保存在内存中;
*/
}
// f12();
function f13() {
function f1() {
var n = 999;
nAdd = function () { n += 1 }
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
/*
f1()是一个闭包函数,被赋给了一个全局变量result,f2则始终在内存中,而f2依赖于f1,所以f1也在内存中,
f1运行了两次,999,1000。这也佐证了n未被垃圾回收机制回收
注意,nAdd没有var申明,为全局变量,其值又是一个匿名函数,也形成了闭包,nAdd就相当于setter,
可在函数内部对函数内部的变量进行操作!
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,
否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,
把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),
这时一定要小心,不要随便改变父函数内部变量的值。
匿名函数的this指向window
*/
}
// f13();
/*
他人博客: 在你写代码时将变量和块作用域写在哪里来决定,也就是词法作用域是静态的作用域,在你书写代码时就确定了
*/
//保存一个私有变量
function f14() {
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var counter = create_counter();
counter.inc();//1
counter.inc();//2
}
// f14();
function count() {
var arr = [];
// debugger
for (var i = 1; i <= 3; i++) {//改为let及正常打印
arr.push(function () {//这里push的是函数
return i * i;
});
console.log(i,arr);
}
return arr;
}
var results = count();
var f1 = results[0]; //打印16,因为循环到i=3时,结束循环,进入循环的只有i<=3满足的时候,
//第四次循环i=4,还是会进入判断i<=3,结束循环!
//var声明了i,i++在结束后还是要执行的,但是i=4不进入循环了,
var f2 = results[1];
var f3 = results[2];
//下面例子是廖雪峰老师,效果出来了,大概看了一下,没有深究
function f16() {
'use strict';
// 定义数字0:
var zero = function (f) {
return function (x) {
return x;
}
};
// 定义数字1:
var one = function (f) {
return function (x) {
return f(x);
}
};
// 定义加法:
function add(n, m) {
return function (f) {
return function (x) {
return m(f)(n(f)(x));
}
}
}
var two = add(one,one);
(two(
function() {
console.log('print two times?')
}
))();
var arr1 = [];
for(let i =0;i<4;i++){
arr1[i] = function(){
return i;
};
}
console.log(arr1[0]());
}
// f16();
//settimeout(异步)产生闭包在循环中的问题,和上面绑定事件差不多
function f17() {
for (var i = 0; i < 4; i++) {
setTimeout(function() {
console.log('origin: '+i);
}, 300);
}
for (var i = 0; i < 4; i++) {
setTimeout((function(num) {
return function(){
console.log('--------settime里增加闭包-------'+num);
}
})(i), 300);
}
for (var i = 0; i < 4; i++) {
(function(num){
setTimeout(function() {
console.log('-----------外部匿名闭包---------------'+num);
}, 300);
})(i)
}
for (var i = 0; i < 4; i++) {
(function(){
var temp = i;
setTimeout(function() {
console.log('--------外部匿名闭包(重定义值接收)----------'+temp);
}, 300);
})()
}
}
f17();
</script>
</html>
时隔一年再看闭包,理解的稍微深刻一些了,还会再看的!!
来源:https://blog.csdn.net/qq_39370934/article/details/99850988