Skip to content

Instantly share code, notes, and snippets.

@dexbol
Last active February 14, 2023 05:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dexbol/3295333eacc66419c5b5236a860f0d63 to your computer and use it in GitHub Desktop.
Save dexbol/3295333eacc66419c5b5236a860f0d63 to your computer and use it in GitHub Desktop.
immer.js 实现方式简介
#!/usr/bin/env node
/**
* 此函数只为说明 immer.js 的基本实现方式(基于 ES6 Proxy 非 ES5),因此尽量保持简陋,
* 并且只支持普通对象,而且不能通过返回值的方式生成新对象,
* 也不能增加新属性。
* 同时,也不支持 Array Map Set 等其他数据类型。
*/
// 存储 revoke 函数,用于释放 Proxy 对象资源。
// 详见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable
const revokes = [];
const STATE_KEY = Symbol.for('state');
function produce(plainObject, recipe) {
var proxy = createProxy(plainObject, null);
recipe(proxy);
var state = proxy[STATE_KEY];
var result;
if (!state.modified) {
result = plainObject;
} else {
// 将 Proxy 对象,从末端属性依次还原回普通对象。
result = process(state.draft);
}
// 释放 Proxy 对象资源
while ((rev = revokes.shift())) {
rev();
}
return result;
}
function createProxy(base, parent) {
var state = {
parent,
modified: false,
base: base,
copy: null,
draft: null,
drafts: {}
};
var {proxy, revoke} = Proxy.revocable(state, proxyTraps);
state.draft = proxy;
revokes.push(revoke);
return proxy;
}
var proxyTraps = {
get(state, prop) {
var result;
if (prop === STATE_KEY) {
result = state;
} else {
result = (state.copy || state.base)[prop];
// 为了判断子对象的修改状态,生成一个对应的 Proxy 对象。
if (
typeof result == 'object' &&
result != null &&
!result[STATE_KEY]
) {
if (state.modified) {
state.drafts = state.copy;
}
result = state.drafts[prop] = createProxy(result, state);
}
}
return result;
},
set(state, prop, value) {
if (!state.modified) {
var baseValue = state.base[prop];
var changed = !Object.is(baseValue, value);
if (changed) {
markChanged(state);
state.copy[prop] = value;
}
} else {
state.copy[prop] = value;
}
return true;
}
};
function markChanged(state) {
if (!state.modified) {
state.modified = true;
state.copy = Object.assign({}, state.base, state.drafts);
state.drafts = null;
if (state.parent) {
markChanged(state.parent);
}
}
}
function process(draft) {
var state = draft[STATE_KEY];
if (!state) {
return draft;
}
if (!state.modified) {
return state.base;
}
for (var p in state.copy) {
draft[p] = processProperty(p, state.copy[p], draft);
}
return state.copy;
}
function processProperty(property, value, parent) {
if (value[STATE_KEY]) {
return (parent[property] = process(value));
}
return value;
}
var testObject = {
id: '007',
info: {
name: 'James Bond',
sexy: 'man',
favors: {
women: true,
guns: true,
terrorist: false
}
},
organization: {
name: 'MI6',
fullName: 'The Secret Intelligence Service'
}
};
var t1 = produce(testObject, (draft) => {
draft.info.favors.women = false;
});
var t2 = produce(testObject, (draft) => {
draft.info.name = 'James Bond';
});
console.log(t1);
// true t1.info.favor 被修改,引发整个对象不同。
console.log(t1 !== testObject);
// true
console.log(t1.info !== testObject.info);
// true 复用没有修改过的对象属性。
console.log(t1.organization === testObject.organization);
// true 修改的值没有变化,返回原对象。
console.log(t2 === testObject);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment