diff --git a/packages/mpvue-template-compiler/build.js b/packages/mpvue-template-compiler/build.js index d96c41d3..2cd97f62 100644 --- a/packages/mpvue-template-compiler/build.js +++ b/packages/mpvue-template-compiler/build.js @@ -2810,10 +2810,13 @@ var observerState = { * object's property keys into getter/setters that * collect dependencies and dispatches updates. */ -var Observer = function Observer (value) { +var Observer = function Observer (value, key) { this.value = value; this.dep = new Dep(); this.vmCount = 0; + if (key) { + this.key = key; + } def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto @@ -2876,7 +2879,7 @@ function copyAugment (target, src, keys) { * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ -function observe (value, asRootData) { +function observe (value, asRootData, key) { if (!isObject(value)) { return } @@ -2890,7 +2893,9 @@ function observe (value, asRootData) { Object.isExtensible(value) && !value._isVue ) { - ob = new Observer(value); + ob = new Observer(value, key); + ob.__keyPath = ob.__keyPath ? ob.__keyPath : {}; + ob.__keyPath[key] = true; } if (asRootData && ob) { ob.vmCount++; @@ -2915,11 +2920,13 @@ function defineReactive$$1 ( return } + // TODO: 先试验标记一下 keyPath + // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; - var childOb = !shallow && observe(val); + var childOb = !shallow && observe(val, undefined, key); Object.defineProperty(obj, key, { enumerable: true, configurable: true, @@ -2942,6 +2949,7 @@ function defineReactive$$1 ( if (newVal === value || (newVal !== newVal && value !== value)) { return } + /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter(); @@ -2951,8 +2959,10 @@ function defineReactive$$1 ( } else { val = newVal; } - childOb = !shallow && observe(newVal); + childOb = !shallow && observe(newVal, undefined, key); dep.notify(); + obj.__keyPath = obj.__keyPath ? obj.__keyPath : {}; + obj.__keyPath[key] = true; } }); } @@ -2985,6 +2995,9 @@ function set (target, key, val) { return val } defineReactive$$1(ob.value, key, val); + // Vue.set 添加对象属性,渲染时候把val传给小程序渲染 + target.__keyPath = target.__keyPath ? target.__keyPath : {}; + target.__keyPath[key] = true; ob.dep.notify(); return val } diff --git a/packages/mpvue/index.js b/packages/mpvue/index.js index 9a399e41..6dcba81e 100644 --- a/packages/mpvue/index.js +++ b/packages/mpvue/index.js @@ -763,10 +763,13 @@ var observerState = { * object's property keys into getter/setters that * collect dependencies and dispatches updates. */ -var Observer = function Observer (value) { +var Observer = function Observer (value, key) { this.value = value; this.dep = new Dep(); this.vmCount = 0; + if (key) { + this.key = key; + } def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto @@ -829,7 +832,7 @@ function copyAugment (target, src, keys) { * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ -function observe (value, asRootData) { +function observe (value, asRootData, key) { if (!isObject(value)) { return } @@ -843,7 +846,9 @@ function observe (value, asRootData) { Object.isExtensible(value) && !value._isVue ) { - ob = new Observer(value); + ob = new Observer(value, key); + ob.__keyPath = ob.__keyPath ? ob.__keyPath : {}; + ob.__keyPath[key] = true; } if (asRootData && ob) { ob.vmCount++; @@ -868,11 +873,13 @@ function defineReactive$$1 ( return } + // TODO: 先试验标记一下 keyPath + // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; - var childOb = !shallow && observe(val); + var childOb = !shallow && observe(val, undefined, key); Object.defineProperty(obj, key, { enumerable: true, configurable: true, @@ -895,6 +902,7 @@ function defineReactive$$1 ( if (newVal === value || (newVal !== newVal && value !== value)) { return } + /* eslint-enable no-self-compare */ if ("production" !== 'production' && customSetter) { customSetter(); @@ -904,8 +912,10 @@ function defineReactive$$1 ( } else { val = newVal; } - childOb = !shallow && observe(newVal); + childOb = !shallow && observe(newVal, undefined, key); dep.notify(); + obj.__keyPath = obj.__keyPath ? obj.__keyPath : {}; + obj.__keyPath[key] = true; } }); } @@ -938,6 +948,9 @@ function set (target, key, val) { return val } defineReactive$$1(ob.value, key, val); + // Vue.set 添加对象属性,渲染时候把val传给小程序渲染 + target.__keyPath = target.__keyPath ? target.__keyPath : {}; + target.__keyPath[key] = true; ob.dep.notify(); return val } @@ -965,6 +978,9 @@ function del (target, key) { if (!ob) { return } + target.__keyPath = target.__keyPath ? target.__keyPath : {}; + // Vue.del 删除对象属性,渲染时候把这个属性设置为undefined + target.__keyPath[key] = 'del'; ob.dep.notify(); } @@ -5293,6 +5309,182 @@ function initMP (mpType, next) { } } +var updateDataTotal = 0; // 总共更新的数据量 +function diffLog (updateData) { + updateData = JSON.stringify(updateData); + if (!Vue$3._mpvueTraceTimer) { + Vue$3._mpvueTraceTimer = setTimeout(function () { + clearTimeout(Vue$3._mpvueTraceTimer); + updateDataTotal = (updateDataTotal / 1024).toFixed(1); + console.log('这次操作引发500ms内数据更新量:' + updateDataTotal + 'kb'); + Vue$3._mpvueTraceTimer = 0; + updateDataTotal = 0; + }, 500); + } else if (Vue$3._mpvueTraceTimer) { + updateData = updateData.replace(/[^\u0000-\u00ff]/g, 'aa'); // 中文占2字节,中文替换成两个字母计算占用空间 + updateDataTotal += updateData.length; + } +} + +function getDeepData (keyList, viewData) { + if (keyList.length > 1) { + var _key = keyList.splice(0, 1); + var _viewData = viewData[_key]; + if (_viewData) { + return getDeepData(keyList, _viewData) + } else { + return null + } + } else { + if (viewData[keyList[0]]) { + return viewData[keyList[0]] + } else { + return null + } + } +} +function compareAndSetDeepData (key, newData, vm, data) { + // 比较引用类型数据 + try { + var keyList = key.split('.'); + //page.__viewData__老版小程序不存在,使用mpvue里绑的data比对 + var oldData = getDeepData(keyList, vm.$root.$mp.page.data); + if (oldData === null || JSON.stringify(oldData) !== JSON.stringify(newData)) { + data[key] = newData; + } + } catch (e) { + console.log(e, key, newData, vm); + } +} + +function cleanKeyPath (vm) { + if (vm.__mpKeyPath) { + Object.keys(vm.__mpKeyPath).forEach(function (_key) { + delete vm.__mpKeyPath[_key]['__keyPath']; + }); + } +} + +function minifyDeepData (rootKey, originKey, vmData, data, _mpValueSet, vm) { + try { + if (vmData instanceof Array) { + // 数组 + compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data); + } else { + // Object + var _keyPathOnThis = {}; // 存储这层对象的keyPath + if (vmData.__keyPath) { + // 有更新列表 ,按照更新列表更新 + _keyPathOnThis = vmData.__keyPath; + Object.keys(vmData).forEach(function (_key) { + if (vmData[_key] instanceof Object) { + // 引用类型 递归 + if (_key === '__keyPath') { + return + } + minifyDeepData(rootKey + '.' + originKey, _key, vmData[_key], data, null, vm); + } else { + // 更新列表中的 加入data + if (_keyPathOnThis[_key] === true) { + if (originKey) { + data[rootKey + '.' + originKey + '.' + _key] = vmData[_key]; + } else { + data[rootKey + '.' + _key] = vmData[_key]; + } + } + } + }); + // 根节点可能有父子引用同一个引用类型数据,依赖树都遍历完后清理 + vm['__mpKeyPath'] = vm['__mpKeyPath'] || {}; + vm['__mpKeyPath'][vmData.__ob__.dep.id] = vmData; + } else { + // 没有更新列表 + compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data); + } + } + } catch (e) { + console.log(e, rootKey, originKey, vmData, data); + } +} + +function getRootKey (vm, rootKey) { + if (!vm.$parent.$attrs) { + rootKey = '$root.0' + ',' + rootKey; + return rootKey + } else { + rootKey = vm.$parent.$attrs.mpcomid + ',' + rootKey; + return getRootKey(vm.$parent, rootKey) + } +} + +function diffData (vm, data) { + var vmData = vm._data || {}; + var vmProps = vm._props || {}; + var rootKey = ''; + if (!vm.$attrs) { + rootKey = '$root.0'; + } else { + rootKey = getRootKey(vm, vm.$attrs.mpcomid); + } + Vue$3.nextTick(function () { + cleanKeyPath(vm); + }); + // console.log(rootKey) + + // 值类型变量不考虑优化,还是直接更新 + var __keyPathOnThis = vmData.__keyPath || vm.__keyPath || {}; + delete vm.__keyPath; + delete vmData.__keyPath; + delete vmProps.__keyPath; + if (vm._mpValueSet === 'done') { + // 第二次赋值才进行缩减操作 + Object.keys(vmData).forEach(function (vmDataItemKey) { + if (vmData[vmDataItemKey] instanceof Object) { + // 引用类型 + if (vmDataItemKey === '__keyPath') { return } + minifyDeepData(rootKey, vmDataItemKey, vmData[vmDataItemKey], data, vm._mpValueSet, vm); + } else if(vmData[vmDataItemKey] !== undefined){ + // _data上的值属性只有要更新的时候才赋值 + if (__keyPathOnThis[vmDataItemKey] === true) { + data[rootKey + '.' + vmDataItemKey] = vmData[vmDataItemKey]; + } + } + }); + + Object.keys(vmProps).forEach(function (vmPropsItemKey) { + if (vmProps[vmPropsItemKey] instanceof Object) { + // 引用类型 + if (vmPropsItemKey === '__keyPath') { return } + minifyDeepData(rootKey, vmPropsItemKey, vmProps[vmPropsItemKey], data, vm._mpValueSet, vm); + } else if(vmProps[vmPropsItemKey] !== undefined){ + data[rootKey + '.' + vmPropsItemKey] = vmProps[vmPropsItemKey]; + } + // _props上的值属性只有要更新的时候才赋值 + }); + + // 检查完data和props,最后补上_mpProps & _computedWatchers + var vmMpProps = vm._mpProps || {}; + var vmComputedWatchers = vm._computedWatchers || {}; + Object.keys(vmMpProps).forEach(function (mpItemKey) { + data[rootKey + '.' + mpItemKey] = vm[mpItemKey]; + }); + Object.keys(vmComputedWatchers).forEach(function (computedItemKey) { + data[rootKey + '.' + computedItemKey] = vm[computedItemKey]; + }); + // 更新的时候要删除$root.0:{},否则会覆盖原正确数据 + delete data[rootKey]; + } + if (vm._mpValueSet === undefined) { + // 第一次设置数据成功后,标记位置true,再更新到这个节点如果没有keyPath数组认为不需要更新 + vm._mpValueSet = 'done'; + } + if (Vue$3.config.devtools) { + // console.log('更新VM节点', vm) + // console.log('实际传到Page.setData数据', data) + diffLog(data); + } +} + // 节流方法,性能优化 // 全局的命名约定,为了节省编译的包大小一律采取形象的缩写,说明如下。 // $c === $child @@ -5429,6 +5621,8 @@ function getPage (vm) { return page } +// 优化js变量动态变化时候引起全量更新 + // 优化每次 setData 都传递大量新数据 function updateDataToMP () { var page = getPage(this); @@ -5437,6 +5631,9 @@ function updateDataToMP () { } var data = formatVmData(this); + + diffData(this, data); + throttleSetData(page.setData.bind(page), data); } diff --git a/src/core/observer/index.js b/src/core/observer/index.js index 9317dbbc..d1bfe3b0 100644 --- a/src/core/observer/index.js +++ b/src/core/observer/index.js @@ -36,10 +36,13 @@ export class Observer { dep: Dep; vmCount: number; // number of vms that has this object as root $data - constructor (value: any) { + constructor (value: any, key: any) { this.value = value this.dep = new Dep() this.vmCount = 0 + if (key) { + this.key = key + } def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto @@ -103,7 +106,7 @@ function copyAugment (target: Object, src: Object, keys: Array) { * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ -export function observe (value: any, asRootData: ?boolean): Observer | void { +export function observe (value: any, asRootData: ?boolean, key: any): Observer | void { if (!isObject(value)) { return } @@ -117,7 +120,9 @@ export function observe (value: any, asRootData: ?boolean): Observer | void { Object.isExtensible(value) && !value._isVue ) { - ob = new Observer(value) + ob = new Observer(value, key) + ob.__keyPath = ob.__keyPath ? ob.__keyPath : {} + ob.__keyPath[key] = true } if (asRootData && ob) { ob.vmCount++ @@ -146,7 +151,7 @@ export function defineReactive ( const getter = property && property.get const setter = property && property.set - let childOb = !shallow && observe(val) + let childOb = !shallow && observe(val, undefined, key) Object.defineProperty(obj, key, { enumerable: true, configurable: true, @@ -169,6 +174,7 @@ export function defineReactive ( if (newVal === value || (newVal !== newVal && value !== value)) { return } + /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() @@ -178,8 +184,10 @@ export function defineReactive ( } else { val = newVal } - childOb = !shallow && observe(newVal) + childOb = !shallow && observe(newVal, undefined, key) dep.notify() + obj.__keyPath = obj.__keyPath ? obj.__keyPath : {} + obj.__keyPath[key] = true } }) } @@ -212,6 +220,11 @@ export function set (target: Array | Object, key: any, val: any): any { return val } defineReactive(ob.value, key, val) + // Vue.set 添加对象属性,渲染时候把 val 传给小程序渲染 + if (!target.__keyPath) { + target.__keyPath = {} + } + target.__keyPath[key] = true ob.dep.notify() return val } @@ -239,6 +252,11 @@ export function del (target: Array | Object, key: any) { if (!ob) { return } + if (!target.__keyPath) { + target.__keyPath = {} + } + // Vue.del 删除对象属性,渲染时候把这个属性设置为 undefined + target.__keyPath[key] = 'del' ob.dep.notify() } diff --git a/src/platforms/mp/runtime/diff-data.js b/src/platforms/mp/runtime/diff-data.js new file mode 100644 index 00000000..ae5808c9 --- /dev/null +++ b/src/platforms/mp/runtime/diff-data.js @@ -0,0 +1,162 @@ +import Vue from 'core/index' +import { diffLog } from './runtime-trace' + +function getDeepData (keyList, viewData) { + if (keyList.length > 1) { + const _key = keyList.splice(0, 1) + const _viewData = viewData[_key] + if (_viewData) { + return getDeepData(keyList, _viewData) + } else { + return null + } + } else { + if (viewData[keyList[0]]) { + return viewData[keyList[0]] + } else { + return null + } + } +} + +function compareAndSetDeepData (key, newData, vm, data, forceUpdate) { + // 比较引用类型数据 + try { + const keyList = key.split('.') + // page.__viewData__老版小程序不存在,使用mpvue里绑的data比对 + const oldData = getDeepData(keyList, vm.$root.$mp.page.data) + if (oldData === null || JSON.stringify(oldData) !== JSON.stringify(newData) || forceUpdate) { + data[key] = newData + } + } catch (e) { + console.log(e, key, newData, vm) + } +} + +function cleanKeyPath (vm) { + if (vm.__mpKeyPath) { + Object.keys(vm.__mpKeyPath).forEach((_key) => { + delete vm.__mpKeyPath[_key]['__keyPath'] + }) + } +} + +function minifyDeepData (rootKey, originKey, vmData, data, _mpValueSet, vm) { + try { + if (vmData instanceof Array) { + // 数组 + compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data, true) + } else { + // Object + let __keyPathOnThis = {} // 存储这层对象的keyPath + if (vmData.__keyPath) { + // 有更新列表 ,按照更新列表更新 + __keyPathOnThis = vmData.__keyPath + Object.keys(vmData).forEach((_key) => { + if (vmData[_key] instanceof Object) { + // 引用类型 递归 + if (_key === '__keyPath') { + return + } + minifyDeepData(rootKey + '.' + originKey, _key, vmData[_key], data, null, vm) + } else { + // 更新列表中的 加入data + if (__keyPathOnThis[_key] === true) { + if (originKey) { + data[rootKey + '.' + originKey + '.' + _key] = vmData[_key] + } else { + data[rootKey + '.' + _key] = vmData[_key] + } + } + } + }) + // 根节点可能有父子引用同一个引用类型数据,依赖树都遍历完后清理 + vm['__mpKeyPath'] = vm['__mpKeyPath'] || {} + vm['__mpKeyPath'][vmData.__ob__.dep.id] = vmData + } else { + // 没有更新列表 + compareAndSetDeepData(rootKey + '.' + originKey, vmData, vm, data) + } + } + } catch (e) { + console.log(e, rootKey, originKey, vmData, data) + } +} + +function getRootKey (vm, rootKey) { + if (!vm.$parent.$attrs) { + rootKey = '$root.0' + ',' + rootKey + return rootKey + } else { + rootKey = vm.$parent.$attrs.mpcomid + ',' + rootKey + return getRootKey(vm.$parent, rootKey) + } +} + +export function diffData (vm, data) { + const vmData = vm._data || {} + const vmProps = vm._props || {} + let rootKey = '' + if (!vm.$attrs) { + rootKey = '$root.0' + } else { + rootKey = getRootKey(vm, vm.$attrs.mpcomid) + } + Vue.nextTick(() => { + cleanKeyPath(vm) + }) + // console.log(rootKey) + + // 值类型变量不考虑优化,还是直接更新 + const __keyPathOnThis = vmData.__keyPath || vm.__keyPath || {} + delete vm.__keyPath + delete vmData.__keyPath + delete vmProps.__keyPath + if (vm._mpValueSet === 'done') { + // 第二次赋值才进行缩减操作 + Object.keys(vmData).forEach((vmDataItemKey) => { + if (vmData[vmDataItemKey] instanceof Object) { + // 引用类型 + if (vmDataItemKey === '__keyPath') { return } + minifyDeepData(rootKey, vmDataItemKey, vmData[vmDataItemKey], data, vm._mpValueSet, vm) + } else if (vmData[vmDataItemKey] !== undefined) { + // _data上的值属性只有要更新的时候才赋值 + if (__keyPathOnThis[vmDataItemKey] === true) { + data[rootKey + '.' + vmDataItemKey] = vmData[vmDataItemKey] + } + } + }) + + Object.keys(vmProps).forEach((vmPropsItemKey) => { + if (vmProps[vmPropsItemKey] instanceof Object) { + // 引用类型 + if (vmPropsItemKey === '__keyPath') { return } + minifyDeepData(rootKey, vmPropsItemKey, vmProps[vmPropsItemKey], data, vm._mpValueSet, vm) + } else if (vmProps[vmPropsItemKey] !== undefined) { + data[rootKey + '.' + vmPropsItemKey] = vmProps[vmPropsItemKey] + } + // _props上的值属性只有要更新的时候才赋值 + }) + + // 检查完data和props,最后补上_mpProps & _computedWatchers + const vmMpProps = vm._mpProps || {} + const vmComputedWatchers = vm._computedWatchers || {} + Object.keys(vmMpProps).forEach((mpItemKey) => { + data[rootKey + '.' + mpItemKey] = vm[mpItemKey] + }) + Object.keys(vmComputedWatchers).forEach((computedItemKey) => { + data[rootKey + '.' + computedItemKey] = vm[computedItemKey] + }) + // 更新的时候要删除$root.0:{},否则会覆盖原正确数据 + delete data[rootKey] + } + if (vm._mpValueSet === undefined) { + // 第一次设置数据成功后,标记位置true,再更新到这个节点如果没有keyPath数组认为不需要更新 + vm._mpValueSet = 'done' + } + if (Vue.config._mpTrace) { + // console.log('更新VM节点', vm) + // console.log('实际传到Page.setData数据', data) + diffLog(data) + } +} diff --git a/src/platforms/mp/runtime/events.js b/src/platforms/mp/runtime/events.js index 081d7850..52be9815 100644 --- a/src/platforms/mp/runtime/events.js +++ b/src/platforms/mp/runtime/events.js @@ -60,7 +60,7 @@ function getHandle (vnode, eventid, eventTypes = []) { } function getWebEventByMP (e) { - const { type, timeStamp, touches, detail = {}, target = {}, currentTarget = {}} = e + const { type, timeStamp, touches, detail = {}, target = {}, currentTarget = {} } = e const { x, y } = detail const event = { mp: e, @@ -84,7 +84,7 @@ function getWebEventByMP (e) { export function handleProxyWithVue (e) { const rootVueVM = this.$root const { type, target = {}, currentTarget } = e - const { dataset = {}} = currentTarget || target + const { dataset = {} } = currentTarget || target const { comkey = '', eventid } = dataset const vm = getVM(rootVueVM, comkey.split(',')) diff --git a/src/platforms/mp/runtime/index.js b/src/platforms/mp/runtime/index.js index 77077464..d7dc7274 100644 --- a/src/platforms/mp/runtime/index.js +++ b/src/platforms/mp/runtime/index.js @@ -11,7 +11,6 @@ import { getTagNamespace, isUnknownElement } from 'mp/util/index' - import { patch } from './patch' // install platform specific utils diff --git a/src/platforms/mp/runtime/lifecycle.js b/src/platforms/mp/runtime/lifecycle.js index 85de360b..baf5e73a 100644 --- a/src/platforms/mp/runtime/lifecycle.js +++ b/src/platforms/mp/runtime/lifecycle.js @@ -12,6 +12,8 @@ export function callHook (vm, hook, params) { let handlers = vm.$options[hook] if (hook === 'onError' && handlers) { handlers = [handlers] + } else if (hook === 'onPageNotFound' && handlers) { + handlers = [handlers] } let ret @@ -225,6 +227,10 @@ export function initMP (mpType, next) { onError (err) { callHook(rootVueVM, 'onError', err) + }, + + onPageNotFound (err) { + callHook(rootVueVM, 'onPageNotFound', err) } }) } else if (mpType === 'component') { diff --git a/src/platforms/mp/runtime/render.js b/src/platforms/mp/runtime/render.js index 3341a968..a2f625ac 100644 --- a/src/platforms/mp/runtime/render.js +++ b/src/platforms/mp/runtime/render.js @@ -1,5 +1,6 @@ // 节流方法,性能优化 import { getComKey } from '../util/index' +import { diffData } from './diff-data' // 全局的命名约定,为了节省编译的包大小一律采取形象的缩写,说明如下。 // $c === $child @@ -128,6 +129,7 @@ function getPage (vm) { return page } +// 优化js变量动态变化时候引起全量更新 // 优化每次 setData 都传递大量新数据 export function updateDataToMP () { const page = getPage(this) @@ -136,6 +138,7 @@ export function updateDataToMP () { } const data = formatVmData(this) + diffData(this, data) throttleSetData(page.setData.bind(page), data) } diff --git a/src/platforms/mp/runtime/runtime-trace.js b/src/platforms/mp/runtime/runtime-trace.js new file mode 100644 index 00000000..45053955 --- /dev/null +++ b/src/platforms/mp/runtime/runtime-trace.js @@ -0,0 +1,17 @@ +import Vue from 'core/index' +var updateDataTotal = 0 // 总共更新的数据量 +export function diffLog (updateData) { + updateData = JSON.stringify(updateData) + if (!Vue._mpvueTraceTimer) { + Vue._mpvueTraceTimer = setTimeout(function () { + clearTimeout(Vue._mpvueTraceTimer) + updateDataTotal = (updateDataTotal / 1024).toFixed(1) + console.log('这次操作引发500ms内数据更新量:' + updateDataTotal + 'kb') + Vue._mpvueTraceTimer = 0 + updateDataTotal = 0 + }, 500) + } else if (Vue._mpvueTraceTimer) { + updateData = updateData.replace(/[^\u0000-\u00ff]/g, 'aa') // 中文占2字节,中文替换成两个字母计算占用空间 + updateDataTotal += updateData.length + } +}