闭包在前端领域内算是老生常谈的一个话题了,基于我去解读zustand源码,我特定将闭包这个重新进行学习整理,已做到知其所以,知其所以然;
1、闭包概念
闭包可以这样说:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行;总结为一下两点
- 1、函数可以记住当前所在的词法作用域;
- 2、函数脱离当前词法作用域,也可以记住之前的词法作用域;
下面我们判断一个例子:
function foo(){
var a = 12
function bar(){
console.log(a)
}
bar()
}
foo()
我们判断一下他是否形成闭包了呢?答案是没有,这块只不过是一个正常的的foo函数调用,bar函数通过作用域查找方式进行了执行,严格的说并不算闭包,只是有一个闭包的雏形;
我们在看看下面一段代码:
function foo(){
var a = 12
function bar(){
console.log(a)
}
return bar
}
const fn1 = foo()
fn1() // 2
当foo被执行后,理论上,foo在后续的代码找中没有引用,垃圾回收机制就会自动回收,然而bar中引用了foo函数作用的变量,导致无法进行垃圾回收,当然我们执行fn1就会再次访问到bar的作用域,因此我们可以通过console.log(a)可以拿到foo值;
2、闭包与模块模式
我们通过下面方式来去说明代码结构
function todo() {
let states = [];
const getState = (i) => {
console.log(states[i], 'getState');
return states[i] || null;
};
const pushState = (data) => {
states.push(data);
return true;
}
const removeState = (i) => {
return states.splice(i, 1) || null;
}
return { getState, pushState, removeState };
}
const myTodo = todo();
myTodo.pushState('just todo something');
myTodo.getState(0);
todo这个函数的方式,在javascript中称之为模块模式,我们通过调用todo的方式,让其内部形成闭包,达到缓存数据的办法;
3、关于状态管理工具的原理
最近读完了zustand源码,他的状态管理办法就是主题代码就是用到闭包(参考上面的模块模式)及发布订阅者的方式实现的,我大概写了toy版本的状态管理核心代码,具体如下:
function createStore(createState) {
let state;
let listeners = new Set();
// 获取store内容
const getState = () => state;
// 设置store内容值
const setState = (partial, replace) => {
const nextState = typeof partial === 'function' ? partial(state) : partial;
if (nextState !== state) {
const prevState = state;
state = replace ? nextState : Object.assign({}, state, nextState);
listeners.forEach(listener => listener(state, prevState));
}
}
// 添加订阅信息
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
}
// 清除所有的listener
const destroy = () => listeners.clear();
const api = {getState, setState, destroy, subscribe};
// 创建初始的state
state = createState(setState, getState, api);
return api;
}
const store = createStore((setState) => ({
count: 0,
increatment: () => setState(state => ({count: state.count + 1})),
}));
store.getState().increatment();
console.log(store.getState().count);