JS閉包的理解及常見應用場景
一、總結
一句話總結:
閉包是指有權訪問另一個函數作用域中的變量的函數
1、如何從外部讀取函數內部的變量,為什么?
閉包:f2可以讀取f1中的變量,只要把f2作為返回值,就可以在f1外讀取f1內部變
原因:f1是f2的父函數,f2被賦給了一個全局變量,f2始終存在內存中,f2的存在依賴f1,因此f1也始終存在內存中,不會在調用結束后,被垃圾回收機制回收。
function f1(){ var n = 123; function f2(){ //f2是一個閉包 alert(n) } return f2; } js鏈式作用域:子對象會一級一級向上尋找所有父對象的變量,反之不行。
2、js鏈式作用域?
子對象會一級一級向上尋找所有父對象的變量,反之不行。
js中函數內部可以讀取全局變量,函數外部不能讀取函數內部的局部變量。
3、js變量兩種作用域?
全局變量、局部變量(函數內):js中函數內部可以讀取全局變量,函數外部不能讀取函數內部的局部變量。
4、閉包為什么可以實現在函數外讀取到函數內的變量?
|||-begin
function f1(){ var n = 123; function f2(){ //f2是一個閉包 alert(n) } return f2; }
|||-end
原因:f1是f2的父函數,f2被賦給了一個全局變量,f2始終存在內存中,f2的存在依賴f1,因此f1也始終存在內存中,不會在調用結束后,被垃圾回收機制回收。
二、對JS閉包的理解及常見應用場景
轉自或參考:對JS閉包的理解及常見應用場景
https://blog.csdn.net/qq_21132509/article/details/80694517
1、變量作用域
變量作用域兩種:全局變量、局部變量。js中函數內部可以讀取全局變量,函數外部不能讀取函數內部的局部變量。
2、如何從外部讀取函數內部的變量?
function f1(){
var n = 123;
function f2(){ //f2是一個閉包
alert(n)
}
return f2;
}
js鏈式作用域:子對象會一級一級向上尋找所有父對象的變量,反之不行。
f2可以讀取f1中的變量,只要把f2作為返回值,就可以在f1外讀取f1內部變量
3、閉包概念
能夠讀取其他函數內部變量的函數。
或簡單理解為定義在一個函數內部的函數,內部函數持有外部函數內變量的引用。
4、閉包用途
1、讀取函數內部的變量
2、讓這些變量的值始終保持在內存中。不會再f1調用后被自動清除。
3、方便調用上下文的局部變量。利於代碼封裝。
原因:f1是f2的父函數,f2被賦給了一個全局變量,f2始終存在內存中,f2的存在依賴f1,因此f1也始終存在內存中,不會在調用結束后,被垃圾回收機制回收。
5、閉包理解
/** * [init description] * @return {[type]} [description] */
function init() {
var name = "Chrome"; //創建局部變量name和局部函數alertName
function alertName() { //alertName()是函數內部方法,是一個閉包
alert(name); //使用了外部函數聲明的變量,內部函數可以訪問外部函數的變量
}
alertName();
}
init();
//一個變量在源碼中聲明的位置作為它的作用域,同時嵌套的函數可以訪問到其外層作用域中聲明的變量
/** * [outFun description] * @return {[type]} [description] */
function outFun(){
var name = "Chrome";
function alertName(){
alert(name);
}
return alertName; //alertName被外部函數作為返回值返回了,返回的是一個閉包
}
var myFun = outFun();
myFun();
/* 閉包有函數+它的詞法環境;詞法環境指函數創建時可訪問的所有變量。 myFun引用了一個閉包,閉包由alertName()和閉包創建時存在的“Chrome”字符串組成。 alertName()持有了name的引用, myFunc持有了alertName()的的訪問, 因此myFunc調用時,name還是處於可以訪問的狀態。 */
/** * [add description] * @param {[type]} x [description] */
function add(x){
return function(y){
return x + y;
};
}
var addFun1 = add(4);
var addFun2 = add(9);
console.log(addFun1(2)); //6
console.log(addFun2(2)); //11
//add接受一個參數x,返回一個函數,它的參數是y,返回x+y
//add是一個函數工廠,傳入一個參數,就可以創建一個參數和其他參數求值的函數。
//addFun1和addFun2都是閉包。他們使用相同的函數定義,但詞法環境不同,addFun1中x是4,后者是5
6、閉包應用場景之setTimeout
//原生的setTimeout傳遞的第一個函數不能帶參數
setTimeout(function(param){
alert(param)
},1000)
//通過閉包可以實現傳參效果
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,1000);
7、閉包應用場景之回調
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<style> body{ font-size: 12px; } h1{ font-size: 1.5rem; } h2{ font-size: 1.2rem; } </style>
<body>
<p>哈哈哈哈哈哈</p>
<h1>hhhhhhhhh</h1>
<h2>qqqqqqqqq</h2>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
<script> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(14); var size16 = changeSize(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; //我們定義行為,然后把它關聯到某個用戶事件上(點擊或者按鍵)。我們的代碼通常會作為一個回調(事件觸發時調用的函數)綁定到事件上 </script>
</body>
</html>
8、閉包應用場景之封裝變量
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>閉包模擬私有方法</title>
<link rel="stylesheet" href="">
</head>
<body>
<script> //用閉包定義能訪問私有函數和私有變量的公有函數。 var counter = (function(){ var privateCounter = 0; //私有變量 function change(val){ privateCounter += val; } return { increment:function(){ //三個閉包共享一個詞法環境 change(1); }, decrement:function(){ change(-1); }, value:function(){ return privateCounter; } }; })(); console.log(counter.value());//0 counter.increment(); counter.increment();//2 //共享的環境創建在一個匿名函數體內,立即執行。 //環境中有一個局部變量一個局部函數,通過匿名函數返回的對象的三個公共函數訪問。 </script>
</body>
</html>
9、閉包應用場景之為節點循環綁定click事件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<body>
<p id="info">123</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>
<script> function showContent(content){ document.getElementById('info').innerHTML = content; }; function setContent(){ var infoArr = [ {'id':'email','content':'your email address'}, {'id':'name','content':'your name'}, {'id':'age','content':'your age'} ]; for (var i = 0; i < infoArr.length; i++) { var item = infoArr[i]; document.getElementById(item.id).onfocus = function(){ showContent(item.content) } } } setContent() //循環中創建了三個閉包,他們使用了相同的詞法環境item,item.content是變化的變量 //當onfocus執行時,item.content才確定,此時循環已經結束,三個閉包共享的item已經指向數組最后一項。 /** * 解決方法1 通過函數工廠,則函數為每一個回調都創建一個新的詞法環境 */ function showContent(content){ document.getElementById('info').innerHTML = content; }; function callBack(content){ return function(){ showContent(content) } }; function setContent(){ var infoArr = [ {'id':'email','content':'your email address'}, {'id':'name','content':'your name'}, {'id':'age','content':'your age'} ]; for (var i = 0; i < infoArr.length; i++) { var item = infoArr[i]; document.getElementById(item.id).onfocus = callBack(item.content) } } setContent() /** * 解決方法2 綁定事件放在立即執行函數中 */ function showContent(content){ document.getElementById('info').innerHTML = content; }; function setContent(){ var infoArr = [ {'id':'email','content':'your email address'}, {'id':'name','content':'your name'}, {'id':'age','content':'your age'} ]; for (var i = 0; i < infoArr.length; i++) { (function(){ var item = infoArr[i]; document.getElementById(item.id).onfocus = function(){ showContent(item.content) } })()//放立即執行函數,立即綁定,用每次的值綁定到事件上,而不是循環結束的值 } } setContent() /** * 解決方案3 用ES6聲明,避免聲明提前,作用域只在當前塊內 */ function showContent(content){ document.getElementById('info').innerHTML = content; }; function setContent(){ var infoArr = [ {'id':'email','content':'your email address'}, {'id':'name','content':'your name'}, {'id':'age','content':'your age'} ]; for (var i = 0; i < infoArr.length; i++) { let item = infoArr[i]; //限制作用域只在當前塊內 document.getElementById(item.id).onfocus = function(){ showContent(item.content) } } } setContent() </script>
</body>
</html>