JavaScript中的閉包真的過時了?其實Vue和React中都有用到!
1. 以生活中的例子解釋閉包
我們可以把閉包想象成一個神奇的小商店。這個小商店有自己的倉庫,倉庫里放著一些商品(數據)。當有顧客來買東西的時候,售貨員(函數)就會從倉庫里拿商品賣給顧客。
這個小商店的倉庫是不對外公開的,只有商店里的售貨員能進去拿東西。而且,即使商店外面的環境變了(比如街道重新裝修了),商店倉庫里的商品數量和種類還是不會受影響。
在編程里,閉包就類似于這個小商店。它是一個函數,這個函數可以訪問并記住它外部函數作用域里的變量,就像售貨員能記住倉庫里的商品一樣。而且,即使外部函數執行完了,閉包函數依然可以訪問這些變量,就像商店關門了,售貨員依然知道倉庫里有什么商品。
2.代碼案例
下面是一個使用 JavaScript 實現的閉包示例:
// 外部函數 createCounter,就像開了一家小商店
function createCounter() {
// 這個 count 變量就像是商店倉庫里的商品數量
let count = 0;
// 內部函數 increment,就像是商店里的售貨員
function increment() {
// 售貨員可以操作倉庫里的商品數量
count++;
console.log(count);
}
// 把售貨員(內部函數)返回出去,這樣外面的人也能使用它
return increment;
}
// 創建一個計數器實例,就像開了一家具體的小商店
const counter = createCounter();
// 使用計數器,每次調用就相當于有顧客來買東西,商品數量增加
counter(); // 輸出: 1
counter(); // 輸出: 2
counter(); // 輸出: 3
2.1 代碼解釋
- 外部函數 createCounter:這個函數就像是開了一家小商店,它里面有一個變量 count,這個變量就像是商店倉庫里的商品數量。
- 內部函數 increment:這個函數就像是商店里的售貨員,它可以訪問并修改外部函數里的 count 變量。
- 返回內部函數:createCounter 函數返回了 increment 函數,這樣就把“售貨員”送出去了。
- 使用閉包:當我們調用 createCounter 函數時,它返回了 increment 函數,我們把這個返回的函數賦值給 counter 變量。每次調用 counter() 時,實際上就是在調用 increment 函數,它會增加 count 的值并打印出來。
3. 閉包在前端開發中的應用案例
3.1 事件處理中的數據綁定
在前端開發中,我們經常需要為元素添加事件監聽器,并且希望在事件處理函數中訪問特定的數據。閉包可以幫助我們實現這一點。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="btn1">按鈕 1</button>
<button id="btn2">按鈕 2</button>
<script>
const buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
// 為每個按鈕添加點擊事件監聽器
buttons[i].addEventListener('click', (function(index) {
return function() {
console.log(`你點擊了按鈕 ${index + 1}`);
};
})(i));
}
</script>
</body>
</html>
在這個例子中,我們使用閉包來確保每個按鈕的點擊事件處理函數都能正確地訪問到對應的索引值。
3.2 封裝私有變量和方法
閉包可以用來創建私有變量和方法,這樣可以避免全局作用域的污染,同時保護數據不被外部隨意修改。
function createCounter() {
// 私有變量
let count = 0;
return {
// 增加計數的公共方法
increment: function() {
count++;
console.log(count);
},
// 減少計數的公共方法
decrement: function() {
if (count > 0) {
count--;
console.log(count);
}
}
};
}
const counter = createCounter();
counter.increment(); // 輸出: 1
counter.increment(); // 輸出: 2
counter.decrement(); // 輸出: 1
在這個例子中,count 變量是私有的,外部無法直接訪問和修改它,只能通過 increment 和 decrement 方法來操作。
3.3 實現函數柯里化
函數柯里化是指將一個多參數函數轉換為一系列單參數函數的技術。閉包在實現函數柯里化時非常有用。
function add(a, b) {
return a + b;
}
// 柯里化函數
function curryAdd(a) {
return function(b) {
return add(a, b);
};
}
const addFive = curryAdd(5);
console.log(addFive(3)); // 輸出: 8
在這個例子中,curryAdd 函數返回了一個閉包,這個閉包記住了傳入的第一個參數 a,并在后續調用時與第二個參數 b 相加。
4.閉包在前端框架中的應用
閉包在前端框架(如 Vue 和 React)中有著廣泛的應用,下面分別介紹其在這兩個框架中的應用場景及具體代碼案例。
4.1 閉包在 Vue 框架中的應用
1. 自定義指令中的閉包應用
在 Vue 里,自定義指令可用于封裝 DOM 操作。閉包能讓自定義指令記住某些狀態。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 閉包示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<input v-colorful="color" type="text" placeholder="輸入文本">
</div>
<script>
const app = Vue.createApp({
data() {
return {
color: 'red'
};
}
});
// 自定義指令
app.directive('colorful', function (el, binding) {
// 閉包記住 binding.value 的值
const color = binding.value;
returnfunction () {
el.style.color = color;
};
}());
app.mount('#app');
</script>
</body>
</html>
在上述代碼中,自定義指令 v-colorful 里使用閉包記住了 binding.value(即傳入的顏色值)。這樣,無論后續如何變化,指令始終能使用該顏色值來設置元素的文本顏色。
2. 組件內的事件處理函數閉包
在 Vue 組件里,事件處理函數可借助閉包訪問組件的數據。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 組件閉包示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<my-component></my-component>
</div>
<script>
const app = Vue.createApp({});
app.component('my-component', {
template: `
<div>
<button @click="increment">點擊增加</button>
<p>計數: {{ count }}</p>
</div>
`,
data() {
return {
count: 0
};
},
methods: {
increment() {
// 閉包訪問組件的 data 中的 count
this.count++;
}
}
});
app.mount('#app');
</script>
</body>
</html>
在這個組件中,increment 方法作為事件處理函數,它是一個閉包,能訪問組件 data 里的 count 變量,從而實現計數增加的功能。
4.2 閉包在 React 框架中的應用
1. 事件處理函數中的閉包
在 React 中,事件處理函數可以使用閉包來訪問組件的狀態。
import React, { useState } from'react';
import ReactDOM from'react-dom/client';
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
// 閉包訪問 count 狀態
setCount(count + 1);
};
return (
<div>
<button onClick={handleClick}>點擊增加</button>
<p>計數: {count}</p>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
在上述代碼中,handleClick 函數是一個閉包,它能夠訪問 App 組件的 count 狀態。每次點擊按鈕時,handleClick 函數就會更新 count 狀態。
2. 高階組件中的閉包
高階組件(HOC)是 React 中復用代碼的一種方式,閉包在高階組件中發揮著重要作用。
import React from'react';
import ReactDOM from'react-dom/client';
// 高階組件
const withLogging = (WrappedComponent) => {
return(props) => {
// 閉包記住 WrappedComponent
console.log('組件即將渲染');
return<WrappedComponent {...props} />;
};
};
// 普通組件
const MyComponent = (props) => {
return<p>{props.message}</p>;
};
// 使用高階組件包裝普通組件
const LoggedComponent = withLogging(MyComponent);
function App() {
return (
<div>
<LoggedComponent message="這是一條消息" />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
在這個例子中,withLogging 是一個高階組件,它返回一個新的組件。返回的組件是一個閉包,記住了傳入的 WrappedComponent,并在渲染前打印日志。