diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..478d472 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env", { "targets": { "ie": "11" } }]] +} diff --git a/__performance_tests__/measure.js b/__performance_tests__/measure.js new file mode 100644 index 0000000..bb13bc3 --- /dev/null +++ b/__performance_tests__/measure.js @@ -0,0 +1,127 @@ +import produce from '../dist/state-tracker.cjs.production.min' +import immerProduce from 'immer' +import cloneDeep from 'lodash.clonedeep' +import { performance } from 'perf_hooks'; + +let nextId = 1; +let diff = 0 + +function buildData(count) { + const data = new Array(count); + for (let i = 0; i < count; i++) { + data[i] = { + id: nextId++, + selected: false, + } + } + return { data }; +} + +const state = buildData(10000) + +function measureAccess(name, fn) { + const copy = cloneDeep(state) + const start = Date.now() + fn(copy) + const end = Date.now() + console.log(`${name}: `, end - start) +} + +// measureAccess('plain object map', (state) => { +// state.data.map(item => ({ +// ...item +// })) +// }) + +measureAccess('state-tracker map', (state) => { + const proxyState = produce(state) + + // console.log('proxy ', proxyState) + + const perf1 = performance.now() + + const data = proxyState.data + console.log('diff data ', performance.now() - perf1) + + data.map((item, index) => { + const start = performance.now() + let result = { ...item } + // result = { ...item } + // result = { ...item } + // result = { ...item } + + // const result = item + const end = performance.now() + diff += end - start + // console.log('index ', index, start) + return result + }) + + console.log('diff in data ', diff) + const perf2 = performance.now() + console.log('diff in perf ', perf2 - perf1) +}) + +measureAccess('immer map', (state) => { + const proxyState = immerProduce(state, draft => { + const start = performance.now() + const result = draft.data.map(item => ({ + ...item + })) + const end = performance.now() + + console.log('immer ', end - start) + return result + }) + + // console.log('proxy ', proxyState) + proxyState.map(item => ({ + ...item + })) +}) + +// measureAccess('plain object remove', (state) => { +// state.data.map(item => ({ +// ...item +// })) + +// const slice = [...state.data] +// slice.splice(20, 1) +// for (let i = 20; i < slice.length; i++) { +// ({ ...slice[i]}) +// } +// }) + +// measureAccess('state-tracker remove', (state) => { +// const proxyState = produce(state) + +// proxyState.data.map(item => ({ +// ...item +// })) + +// const slice = [...proxyState.data] +// slice.splice(20, 1) + +// proxyState.relink(['data', slice]) +// const data = proxyState.data + +// for (let i = 20; i < slice.length; i++) { +// ({ ...data[i] }) +// } +// }) + +// measureAccess('immer remove', (state) => { +// const slice = immerProduce(state, draft => draft.data.map(item => ({ +// ...item +// }))) +// for (let i = 0; i < slice.length; i++) { +// ({ ...slice[i]}) +// } +// const nextState = immerProduce(state, draft => { +// draft.data.splice(20, 1) +// }) + +// for (let i = 20; i < nextState.length; i++) { +// ({ ...nextState[i]}) +// } +// }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1f0c633..7741aff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,19 +33,19 @@ } }, "@babel/core": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", - "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "version": "7.12.3", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/core/download/@babel/core-7.12.3.tgz", + "integrity": "sha1-G0NohOHjv/b7EyjcArIIdZ3pKtg=", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.6", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.5", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.5", - "@babel/types": "^7.11.5", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -56,10 +56,111 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/generator": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/generator/download/@babel/generator-7.12.5.tgz", + "integrity": "sha1-osUN5ci21wirlb5eYFOTbBiEpN4=", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helper-member-expression-to-functions/download/@babel/helper-member-expression-to-functions-7.12.1.tgz", + "integrity": "sha1-+6Dy/P8/ugDm7LZku15uJuLWFlw=", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helper-module-imports/download/@babel/helper-module-imports-7.12.5.tgz", + "integrity": "sha1-G/wCKfeUmI927QpNTpCGCFC1Tfs=", + "dev": true, + "requires": { + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.12.1.tgz", + "integrity": "sha1-eVT+xx9bMsSOSzA7Q3w0RT/XJHw=", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + } + }, + "@babel/helper-replace-supers": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helper-replace-supers/download/@babel/helper-replace-supers-7.12.5.tgz", + "integrity": "sha1-8AmhdUO7u84WsGIGrnO2PT/KaNk=", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helper-simple-access/download/@babel/helper-simple-access-7.12.1.tgz", + "integrity": "sha1-MkJ+WqYVR9OOsebq9f0UJv2tkTY=", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/parser/download/@babel/parser-7.12.5.tgz", + "integrity": "sha1-tK8y3dRzwL+mQ71/8HKLjnG4HqA=", + "dev": true + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/traverse/download/@babel/traverse-7.12.5.tgz", + "integrity": "sha1-eKDGjI6KNeTKz9MduLswPVYG8JU=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/types/download/@babel/types-7.12.6.tgz", + "integrity": "sha1-rg5V7xzOH7yIHNJvgjTrPmV+3JY=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "resolved": "http://npm.devops.xiaohongshu.com:7001/debug/download/debug-4.2.0.tgz", + "integrity": "sha1-fxUPk5IOlMWPVXTC/QGjEQ7/5/E=", "dev": true, "requires": { "ms": "2.1.2" @@ -67,14 +168,14 @@ }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "resolved": "http://npm.devops.xiaohongshu.com:7001/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", "dev": true }, "semver": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "resolved": "http://npm.devops.xiaohongshu.com:7001/semver/download/semver-5.7.1.tgz", + "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=", "dev": true }, "source-map": { @@ -385,14 +486,82 @@ } }, "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/helpers/download/@babel/helpers-7.12.5.tgz", + "integrity": "sha1-Ghukp2jZtYMQ7aUWxEmRP+ZHEW4=", "dev": true, "requires": { "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + }, + "dependencies": { + "@babel/generator": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/generator/download/@babel/generator-7.12.5.tgz", + "integrity": "sha1-osUN5ci21wirlb5eYFOTbBiEpN4=", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/parser/download/@babel/parser-7.12.5.tgz", + "integrity": "sha1-tK8y3dRzwL+mQ71/8HKLjnG4HqA=", + "dev": true + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/traverse/download/@babel/traverse-7.12.5.tgz", + "integrity": "sha1-eKDGjI6KNeTKz9MduLswPVYG8JU=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/types/download/@babel/types-7.12.6.tgz", + "integrity": "sha1-rg5V7xzOH7yIHNJvgjTrPmV+3JY=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "http://npm.devops.xiaohongshu.com:7001/debug/download/debug-4.2.0.tgz", + "integrity": "sha1-fxUPk5IOlMWPVXTC/QGjEQ7/5/E=", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "http://npm.devops.xiaohongshu.com:7001/ms/download/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "http://npm.devops.xiaohongshu.com:7001/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "@babel/highlight": { @@ -406,6 +575,30 @@ "js-tokens": "^4.0.0" } }, + "@babel/node": { + "version": "7.12.6", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/node/download/@babel/node-7.12.6.tgz", + "integrity": "sha1-KNQDgtUNTdnG5xJ4DARDxr975cI=", + "dev": true, + "requires": { + "@babel/register": "^7.12.1", + "commander": "^4.0.1", + "core-js": "^3.2.1", + "lodash": "^4.17.19", + "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.4", + "resolve": "^1.13.1", + "v8flags": "^3.1.1" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/commander/download/commander-4.1.1.tgz", + "integrity": "sha1-n9YCvZNilOnp70aj9NaWQESxgGg=", + "dev": true + } + } + }, "@babel/parser": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", @@ -1083,6 +1276,19 @@ "esutils": "^2.0.2" } }, + "@babel/register": { + "version": "7.12.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/@babel/register/download/@babel/register-7.12.1.tgz", + "integrity": "sha1-zbCHvfxPckHAMjHyLhXSEazyFDg=", + "dev": true, + "requires": { + "find-cache-dir": "^2.0.0", + "lodash": "^4.17.19", + "make-dir": "^2.1.0", + "pirates": "^4.0.0", + "source-map-support": "^0.5.16" + } + }, "@babel/runtime": { "version": "7.11.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", @@ -3899,6 +4105,12 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "3.7.0", + "resolved": "http://npm.devops.xiaohongshu.com:7001/core-js/download/core-js-3.7.0.tgz", + "integrity": "sha1-sKdhoCSIV3r7+XF55Ggb9JVoUg8=", + "dev": true + }, "core-js-compat": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", @@ -5918,9 +6130,9 @@ "dev": true }, "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "version": "1.0.0-beta.2", + "resolved": "http://npm.devops.xiaohongshu.com:7001/gensync/download/gensync-1.0.0-beta.2.tgz", + "integrity": "sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA=", "dev": true }, "get-caller-file": { @@ -6169,6 +6381,15 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "http://npm.devops.xiaohongshu.com:7001/homedir-polyfill/download/homedir-polyfill-1.0.3.tgz", + "integrity": "sha1-dDKYzvTlrz4ZQWH7rcwhUdOgWOg=", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -6454,6 +6675,12 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, + "immer": { + "version": "7.0.14", + "resolved": "http://npm.devops.xiaohongshu.com:7001/immer/download/immer-7.0.14.tgz", + "integrity": "sha1-PmBfhYSxWpUg0vLz/alEHMkXDSU=", + "dev": true + }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -8859,6 +9086,12 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "http://npm.devops.xiaohongshu.com:7001/lodash.clonedeep/download/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9465,6 +9698,24 @@ } } }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "http://npm.devops.xiaohongshu.com:7001/node-environment-flags/download/node-environment-flags-1.0.6.tgz", + "integrity": "sha1-owrBNiH299Z0JgpU3t4EjDmCwIg=", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "http://npm.devops.xiaohongshu.com:7001/semver/download/semver-5.7.1.tgz", + "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=", + "dev": true + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10007,6 +10258,12 @@ "json-parse-better-errors": "^1.0.1" } }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "http://npm.devops.xiaohongshu.com:7001/parse-passwd/download/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, "parse5": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", @@ -13556,6 +13813,15 @@ } } }, + "v8flags": { + "version": "3.2.0", + "resolved": "http://npm.devops.xiaohongshu.com:7001/v8flags/download/v8flags-3.2.0.tgz", + "integrity": "sha1-skPjtN/XMfp3TnSSEoEJoP5m1lY=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 22d37ba..e1259bb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "lint": "tsdx lint --fix", "prepare": "tsdx build", "size": "size-limit", - "analyze": "size-limit --why" + "analyze": "size-limit --why", + "test:perf": "cd __performance_tests__ && babel-node measure.js" }, "peerDependencies": {}, "husky": { @@ -45,9 +46,13 @@ } ], "devDependencies": { + "@babel/core": "^7.12.3", + "@babel/node": "^7.12.6", "@size-limit/preset-small-lib": "^4.6.0", "@types/invariant": "^2.2.34", "husky": "^4.3.0", + "immer": "^7.0.14", + "lodash.clonedeep": "^4.5.0", "size-limit": "^4.6.0", "tsdx": "^0.14.0", "tslib": "^2.0.1", diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..1310dea --- /dev/null +++ b/src/data.ts @@ -0,0 +1,147 @@ +const b = { + update: function(newBaseValue: any) { + this.setBase(newBaseValue); + this.incrementUpdateTimes(); + }, + + updateShadowBase: function(newBaseValue: any) { + this.setShadowBase(newBaseValue); + this.incrementUpdateTimes(); + }, + + getUpdateTimes: function() { + return this._updateTimes; + }, + + incrementUpdateTimes: function() { + this._updateTimes += 1; + return this._updateTimes; + }, + + getBackwardAccessCount: function() { + return this._backwardAccessCount; + }, + + incrementBackwardAccessCount: function() { + this._backwardAccessCount += 1; + return this._backwardAccessCount; + }, + + setContext: function(context: string) { + this._context = context; + }, + + getContext: function() { + return this._context; + }, + + getId: function() { + return this._id; + }, + + getBase: function(): Base { + return this._base; + }, + + setBase: function(value: any) { + this._base = value; + }, + + getShadowBase: function(): Base { + return this._shadowBase; + }, + + setShadowBase: function(value: any) { + if (isObject(value) && typeof value.getTracker !== 'undefined') { + this._shadowBase = value.getTracker().getShadowBase(); + } else { + this._shadowBase = value; + } + }, + + getTrackedProperties: function(): Array { + return this._trackedProperties; + }, + + updateTrackedProperties: function(prop: IndexType): void { + if (this._trackedProperties.indexOf(prop) !== -1) { + this._trackedProperties.push(prop); + } + }, + + getParentProxy: function() { + return this._parentProxy; + }, + + getChildProxies: function(): ChildProxies { + return this._childProxies; + }, + + setChildProxies: function(value: ChildProxies) { + this._childProxies = value; + }, + + setFocusKeyToTrackerMap: function(value: FocusKeyToTrackerMap) { + this._focusKeyToTrackerMap = value; + }, + + getFocusKeyToTrackerMap: function(): FocusKeyToTrackerMap { + return this._focusKeyToTrackerMap; + }, + + getPeeking: function(): boolean { + return this._isPeeking; + }, + + setPeeking: function(falsy: boolean) { + this._isPeeking = falsy; + }, + + getStrictPeeking: function(): boolean { + return this._isStrictPeeking; + }, + + setStrictPeeking: function(falsy: boolean) { + this._isStrictPeeking = falsy; + }, + + getRootPath: function(): Array { + return this._rootPath; + }, + + getAccessPath: function(): Array { + return this._accessPath; + }, + + getStateTrackerContext: function() { + return this._stateTrackerContext; + }, + + getTime: function(): number { + return this._lastUpdateAt; + }, + + setTime: function(time: number) { + this._lastUpdateAt = time; + }, + + getMask: function(): string { + return this._mask; + }, + + setMask: function(value: string) { + this._mask = value; + }, + + getFocusKey: function(): string { + return this._focusKey; + }, + + setFocusKey: function(key: string) { + this._focusKey = key; + }, + + getType: function() { + return this._type; + }, +}; diff --git a/src/proxy.ts b/src/proxy.ts index b9eb3d0..f49afad 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -10,21 +10,52 @@ import { generateRandomKey, generateRandomContextKey, // generateRandomFocusKey, - arrayProtoOwnKeys, - objectProtoOwnKeys, - Type, + // arrayProtoOwnKeys, + // objectProtoOwnKeys, + // Type, + // inherit, DEFAULT_MASK, + canIUseProxy, } from './commons'; import StateTracker from './StateTracker'; import PathTracker from './PathTracker'; import { + Type, IStateTracker, ProduceOptions, ProduceState, StateTrackerInterface, RelinkValue, + ChildProxies, + Base, + IndexType, + FocusKeyToTrackerMap, } from './types'; import StateTrackerContext from './StateTrackerContext'; +import { performance } from 'perf_hooks'; +// const performance = { +// now: () => Date.now() +// } + +// import internal from './internal'; + +let diffPlain = 0; + +// let diff = 0; +let diff1 = 0; +let diff2 = 0; +let diff3 = 0; +let diff4 = 0; +let diff5 = 0; +let diff6 = 0; +let diff7 = 0; + +let secondDiff = 0; +let thirdDiff = 0; +let forth = 0; +let fifthDiff = 0; +let count = 0; +let idCount = 0; function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { const { @@ -57,6 +88,8 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { const handler = { get: (target: IStateTracker, prop: PropertyKey, receiver: any) => { + const start = performance.now(); + // console.log('prop ', prop) try { if (internalKeys.indexOf(prop as string | symbol) !== -1) return Reflect.get(target, prop, receiver); @@ -64,18 +97,24 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { return Reflect.get(target, prop, receiver); let tracker = Reflect.get(target, TRACKER) as StateTrackerInterface; // let pathTracker = Reflect.get(target, PATH_TRACKER); - const targetType = tracker.getType(); - - switch (targetType) { - case Type.Array: - if (prop !== 'length' && ~arrayProtoOwnKeys().indexOf(prop)) - return Reflect.get(target, prop, receiver); - break; - case Type.Object: - if (~objectProtoOwnKeys().indexOf(prop)) - return Reflect.get(target, prop, receiver); - break; - } + // const targetType = tracker.getType(); + + diff7 += performance.now() - start; + if (count > 9998) console.log('diff7 ', diff7); + + // switch (targetType) { + // case Type.Array: + // if (prop !== 'length' && ~arrayProtoOwnKeys().indexOf(prop)) + // return Reflect.get(target, prop, receiver); + // break; + // case Type.Object: + // if (~objectProtoOwnKeys().indexOf(prop)) + // return Reflect.get(target, prop, receiver); + // break; + // } + + diff6 += performance.now() - start; + if (count > 9998) console.log('diff6 ', diff6); if (tracker.getStrictPeeking()) return Reflect.get(target, prop, receiver); @@ -92,6 +131,9 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { let trackerMask = tracker.getMask(); let retryProxy = null; + diff5 += performance.now() - start; + if (count > 9998) console.log('diff5 ', diff5); + if (!isPeeking) { if (trackerContext.getCurrent()) { trackerContext @@ -135,10 +177,14 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { } } + diff4 += performance.now() - start; + if (count > 9998) console.log('diff4 ', diff4); + let value; // for rebase value, if base value change, the childProxy should be replaced let childProxyTracker = null; const childProxy = childProxies[prop as string]; + const diff3Start = performance.now(); if (isObject(base) && base.getTracker) { const baseTracker = base.getTracker(); @@ -150,6 +196,9 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { value = base[prop]; } + diff3 += performance.now() - diff3Start; + if (count > 9998) console.log('diff3 ', diff3); + // refer to test case: // If retryProxy exist, childProxies[prop] base should be update if (retryProxy) { @@ -169,6 +218,9 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { if (descriptor && descriptor.configurable) delete childProxies[prop as string]; } + + diff1 += performance.now() - start; + if (count > 9998) console.log('diff1 ', diff1); return value; } else if (!childProxyTracker && childProxy) { childProxyTracker = childProxy[TRACKER]; @@ -182,6 +234,8 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { if (tracker._context) childProxyTracker.setContext(tracker._context); childProxy.getTracker().setMask(trackerMask); + diff2 += performance.now() - start; + if (count > 9998) console.log('diff2 ', diff2); return childProxy; } } @@ -196,6 +250,14 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { const focusKey = `focus_${prop}`; // const focusKey = generateRandomFocusKey(); + const end = performance.now(); + + secondDiff += end - start; + + if (count > 9998) console.log('second ', secondDiff); + + const fifthStart = performance.now(); + const producedChildProxy = produce( // only new value should create new proxy object.. // Array.isArray(value) ? value.slice() : { ...value }, @@ -215,6 +277,10 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { childProxies[prop as string] = producedChildProxy; focusKeyToTrackerMap[focusKey] = producedChildProxy; + const fifthEnd = performance.now(); + fifthDiff += fifthEnd - fifthStart; + if (count > 9998) console.log('fifth ', fifthDiff); + return producedChildProxy; } catch (err) { console.log('[state-tracker] ', err); @@ -262,17 +328,203 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { if (mayReusedTracker && nextState === mayReusedTracker.getBase()) { tracker = mayReusedTracker; } else { - tracker = new StateTracker({ - base: nextState, - parentProxy, - accessPath, - rootPath, - stateTrackerContext: trackerContext, - context, - lastUpdateAt: Date.now(), - focusKey, - mask, - }); + // const start = performance.now(); + // tracker = new StateTracker({ + // base: nextState, + // parentProxy, + // accessPath, + // rootPath, + // stateTrackerContext: trackerContext, + // context, + // lastUpdateAt: Date.now(), + // focusKey, + // mask, + // }); + + // const end = performance.now(); + + // diff += end - start; + count++; + + // if (count > 9998) console.log('diff ', diff); + + const plainStart = performance.now(); + // @ts-ignore + tracker = { // eslint-disable-line + _id: canIUseProxy() + ? `ProxyStateTracker_${idCount++}` + : `ES5StateTracker_${idCount++}`, + _useProxy: canIUseProxy(), + _updateTimes: 0, + _stateTrackerContext: trackerContext, + _context: context, + _lastUpdateAt: Date.now(), + _backwardAccessCount: 0, + _mask: mask, + _accessPath: accessPath, + _rootPath: rootPath, + _type: Array.isArray(nextState) ? Type.Array : Type.Object, + _base: nextState, + _parentProxy: parentProxy, + _childProxies: {}, + _focusKeyToTrackerMap: {}, + _focusKey: focusKey, + _isPeeking: false, + _isStrictPeeking: false, + _shadowBase: {}, + + update: function(newBaseValue: any) { + this.setBase(newBaseValue); + this.incrementUpdateTimes(); + }, + + updateShadowBase: function(newBaseValue: any) { + this.setShadowBase(newBaseValue); + this.incrementUpdateTimes(); + }, + + getUpdateTimes: function() { + return this._updateTimes; + }, + + incrementUpdateTimes: function() { + this._updateTimes += 1; + return this._updateTimes; + }, + + getBackwardAccessCount: function() { + return this._backwardAccessCount; + }, + + incrementBackwardAccessCount: function() { + this._backwardAccessCount += 1; + return this._backwardAccessCount; + }, + + setContext: function(context: string) { + this._context = context; + }, + + getContext: function() { + return this._context; + }, + + getId: function() { + return this._id; + }, + + getBase: function(): Base { + return this._base; + }, + + setBase: function(value: any) { + this._base = value; + }, + + getShadowBase: function(): Base { + return this._shadowBase; + }, + + setShadowBase: function(value: any) { + if (isObject(value) && typeof value.getTracker !== 'undefined') { + this._shadowBase = value.getTracker().getShadowBase(); + } else { + this._shadowBase = value; + } + }, + + getTrackedProperties: function(): Array { + return this._trackedProperties; + }, + + updateTrackedProperties: function(prop: IndexType): void { + if (this._trackedProperties.indexOf(prop) !== -1) { + this._trackedProperties.push(prop); + } + }, + + getParentProxy: function() { + return this._parentProxy; + }, + + getChildProxies: function(): ChildProxies { + return this._childProxies; + }, + + setChildProxies: function(value: ChildProxies) { + this._childProxies = value; + }, + + setFocusKeyToTrackerMap: function(value: FocusKeyToTrackerMap) { + this._focusKeyToTrackerMap = value; + }, + + getFocusKeyToTrackerMap: function(): FocusKeyToTrackerMap { + return this._focusKeyToTrackerMap; + }, + + getPeeking: function(): boolean { + return this._isPeeking; + }, + + setPeeking: function(falsy: boolean) { + this._isPeeking = falsy; + }, + + getStrictPeeking: function(): boolean { + return this._isStrictPeeking; + }, + + setStrictPeeking: function(falsy: boolean) { + this._isStrictPeeking = falsy; + }, + + getRootPath: function(): Array { + return this._rootPath; + }, + + getAccessPath: function(): Array { + return this._accessPath; + }, + + getStateTrackerContext: function() { + return this._stateTrackerContext; + }, + + getTime: function(): number { + return this._lastUpdateAt; + }, + + setTime: function(time: number) { + this._lastUpdateAt = time; + }, + + getMask: function(): string { + return this._mask; + }, + + setMask: function(value: string) { + this._mask = value; + }, + + getFocusKey: function(): string { + return this._focusKey; + }, + + setFocusKey: function(key: string) { + this._focusKey = key; + }, + + getType: function() { + return this._type; + }, + } as any; + // inherit(tracker as any, internal); + + const plainEnd = performance.now(); + diffPlain += plainEnd - plainStart; + if (count > 9998) console.log('diff plain ', diffPlain); + if (mayReusedTracker) { tracker.setChildProxies(mayReusedTracker.getChildProxies()); tracker.setFocusKeyToTrackerMap( @@ -281,10 +533,19 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { } } + const forthStart = performance.now(); + const pathTracker = new PathTracker({ path: accessPath, }); const proxy = new Proxy(nextState, handler) as IStateTracker; + const forthEnd = performance.now(); + + forth += forthEnd - forthStart; + + if (count > 9998) console.log('forth ', forth); + + const thirdStart = performance.now(); // TODO: Cannot add property x, object is not extensible // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_define_property_object_not_extensible @@ -406,6 +667,11 @@ function produce(state: ProduceState, options?: ProduceOptions): IStateTracker { parentTracker.setPeeking(false); }); + const thirdEnd = performance.now(); + thirdDiff += thirdEnd - thirdStart; + + if (count > 9998) console.log('third ', thirdDiff); + return proxy as IStateTracker; }