UNPKG

@aimwhy/vue-function-api

Version:

Vue2 plugin for the function-based RFC

233 lines (174 loc) 5.4 kB
'use strict'; const PATH_SEPARATOR = '.'; const TARGET = '[[target]]'; const isPrimitive = value => value === null || (typeof value !== 'object' && typeof value !== 'function'); const isBuiltinWithoutMutableMethods = value => value instanceof RegExp || value instanceof Number; const isBuiltinWithMutableMethods = value => value instanceof Date; const concatPath = (path, property) => { if (property && property.toString) { if (path) { path += PATH_SEPARATOR; } path += property.toString(); } return path; }; const walkPath = (path, callback) => { let index; while (path) { index = path.indexOf(PATH_SEPARATOR); if (index === -1) { index = path.length; } callback(path.slice(0, index)); path = path.slice(index + 1); } }; const shallowClone = value => { if (Array.isArray(value)) { return value.slice(); } return Object.assign({}, value); }; const onChange = function (object, onChange, options = {}) { debugger const proxyTarget = Symbol('ProxyTarget'); let inApply = false; let changed = false; let applyPath; let applyPrevious; const equals = options.equals || Object.is; const propCache = new WeakMap(); const pathCache = new WeakMap(); const proxyCache = new WeakMap(); const handleChange = (path, property, previous, value) => { if (!inApply) { onChange(concatPath(path, property), value, previous); return; } if (inApply && applyPrevious && previous !== undefined && value !== undefined && property !== 'length') { let item = applyPrevious; if (path !== applyPath) { path = path.replace(applyPath, '').slice(1); walkPath(path, key => { item[key] = shallowClone(item[key]); item = item[key]; }); } item[property] = previous; } changed = true; }; const getOwnPropertyDescriptor = (target, property) => { let props = propCache.get(target); if (props) { return props; } props = new Map(); propCache.set(target, props); let prop = props.get(property); if (!prop) { prop = Reflect.getOwnPropertyDescriptor(target, property); props.set(property, prop); } return prop; }; const invalidateCachedDescriptor = (target, property) => { const props = propCache.get(target); if (props) { props.delete(property); } }; const handler = { get(target, property, receiver) { if (property === proxyTarget || property === TARGET) { return target; } const value = Reflect.get(target, property, receiver); if ( isPrimitive(value) || isBuiltinWithoutMutableMethods(value) || property === 'constructor' || options.isShallow === true ) { return value; } // Preserve invariants const descriptor = getOwnPropertyDescriptor(target, property); if (descriptor && !descriptor.configurable) { if (descriptor.set && !descriptor.get) { return undefined; } if (descriptor.writable === false) { return value; } } pathCache.set(value, concatPath(pathCache.get(target), property)); let proxy = proxyCache.get(value); if (proxy === undefined) { proxy = new Proxy(value, handler); proxyCache.set(value, proxy); } return proxy; }, set(target, property, value, receiver) { if (value && value[proxyTarget] !== undefined) { value = value[proxyTarget]; } const ignore = options.ignoreSymbols === true && typeof property === 'symbol'; const previous = ignore ? null : Reflect.get(target, property, receiver); const result = Reflect.set(target[proxyTarget] || target, property, value); if (!ignore && !equals(previous, value)) { handleChange(pathCache.get(target), property, previous, value); } return result; }, defineProperty(target, property, descriptor) { const result = Reflect.defineProperty(target, property, descriptor); invalidateCachedDescriptor(target, property); handleChange(pathCache.get(target), property, undefined, descriptor.value); return result; }, deleteProperty(target, property) { if (!Reflect.has(target, property)) { return true; } const previous = Reflect.get(target, property); const result = Reflect.deleteProperty(target, property); invalidateCachedDescriptor(target, property); handleChange(pathCache.get(target), property, previous); return result; }, apply(target, thisArg, argumentsList) { debugger const compare = isBuiltinWithMutableMethods(thisArg); if (compare) { thisArg = thisArg[proxyTarget]; } if (!inApply) { inApply = true; if (compare) { applyPrevious = thisArg.valueOf(); } if (Array.isArray(thisArg) || Object.prototype.toString.call(thisArg) === '[object Object]') { applyPrevious = shallowClone(thisArg[proxyTarget]); } applyPath = pathCache.get(target); applyPath = applyPath.slice(0, Math.max(applyPath.lastIndexOf(PATH_SEPARATOR), 0)); const result = Reflect.apply(target, thisArg, argumentsList); inApply = false; if (changed || (compare && !equals(applyPrevious, thisArg.valueOf()))) { handleChange(applyPath, '', applyPrevious, thisArg[proxyTarget] || thisArg); applyPrevious = null; changed = false; } return result; } return Reflect.apply(target, thisArg, argumentsList); } }; pathCache.set(object, ''); const proxy = new Proxy(object, handler); onChange = onChange.bind(proxy); return proxy; };