浅谈Vue 性能优化之深挖数组

背景

最近在用 Vue 重构一个历史项目,一个考试系统,题目量很大,所以核心组件的性能成为了关注点。先来两张图看下最核心的组件 Paper 的样式。

从图中来看,分为答题区与选择面板区。

稍微对交互逻辑进行下拆解:

  • 答题模式与学习模式可以相互切换,控制正确答案显隐。
  • 单选与判断题直接点击就记录答案正确性,多选是选择答案之后点击确定才能记录正确性。
  • 选择面板则是记录做过的题目的情况,分为六种状态(未做过的,未做过且当前选择的,做错的,做错的且当前选择的,做对的,做对的且当前选择的),用不同的样式去区别。
  • 点击选择面板,答题区能切到对应的题号。

基于以上考虑,我觉得我必须有三个响应式的数据:

  • currentIndex: 当前选中题目的序号。
  • questions:所有题目的信息,是个数组,里面维护了每道题的问题、选项、正确与否等信息。
  • cardData:题目分组的信息,也是个数组,按章节名称对不同的题目进行了分类。

数组每一项数据结构如下:

currentIndex = 0 // 用来标记当前选中题目的索引

questions = [{
  secId: 1, // 所属章节的 id
  tid: 1, // 题目 id
  content: '题目内容' // 题目描述
  type: 1, // 题型,1 ~ 3 (单选,多选,判断)
  options: ['选项1', '选项2', '选项3', '选项4',] // 每个选项的描述
  choose: [1, 2, 4], // 多选——记录用户未提交前的选项
  done: true, // 标记当前题目是否已做
  answerIsTrue: undefined // 标记当前题目的正确与否
}]

cardData = [{
  startIndex: 0, // 用来记录循环该分组数据的起始索引,这个值等于前面数据的长度累加。
  secName: '章节名称',
  secId: '章节id',
  tids: [1, 2, 3, 11] // 该章节下面的所有题目的 id
}]

由于题目可以左右滑动切换,所以我每次从 questions 取了三个数据去渲染,用的是 cube-ui 的 Slide 组件,只要自己根据 this.currentIndex 结合 computed 特性去动态的切割三个数据就行。

这一切都显得很美好,尤其是即将结束了一个历史项目的核心组件的编写之前,心情特别的舒畅。

然而转折点出现在了渲染选择面板样式这一步

代码逻辑很简单,但是发生了让我懵逼的事情。

<div class="card-content">
 <div class="block" v-for="item in cardData" :key="item.secName">
  <div class="sub-title">{{item.secName}}</div>
  <div class="group">
   <span
    @click="cardClick(index + item.startIndex)"
    class="item"
    :class="getItemClass(index + item.startIndex)"
    v-for="(subItem, index) in item.secTids"
    :key="subItem">{{index + item.startIndex + 1}}</span>
  </div>
 </div>
</div>

其实就是利用 cardData 去生成 DOM 元素,这是个分组数据(先是以章节为维度,章节下面还有对应的题目),上面的代码其实是一个循环里面嵌套了另一个循环。

但是,只要我切换题目或者点击面板,抑或是触发任意响应式数据的改变,都会让页面卡死!!

探索

当下的第一反应,肯定是 js 在某一步的执行时间过长,所以利用 Chrome 自带的 Performance 工具 追踪了一下,发现问题出在 getItemClass 这个函数调用,占据了 99% 的时间,而且时间都超过 1s 了。瞅了眼自己的代码:

getItemClass (index) {
 const ret = {}
 // 如果是做对的题目,但并不是当前选中
 ret['item_true'] = this.questions[index]......
 // 如果是做对的题目,并且是当前选中
 ret['item_true_active'] = this.questions[index]......
 // 如果是做错的题目,但并不是当前选中
 ret['item_false'] = this.questions[index]......
 // 如果是做错的题目,并且是当前选中
 ret['item_false_active'] = this.questions[index]......
 // 如果是未做的题目,但不是当前选中
 ret['item_undo'] = this.questions[index]......
 // 如果是未做的题目,并且是当前选中
 ret['item_undo_active'] = this.questions[index]......
 return ret
},

这个函数主要是用来计算选择面板每一个小圆圈该有的样式。每一步都是对 questions 进行了 getter 操作。初看,好像没什么问题,但是由于之前看过 Vue 的源码,细想之下,觉得不对。

首先,webpack 会将 .vue 文件的 template 转换成 render 函数,也就是实例化组件的时候,其实是对响应式属性求值的过程,这样响应式属性就能将 renderWatcher 加入依赖当中,所以当响应式属性改变的时候,能触发组件重新渲染。

我们先来了解下 renderWatcher 是什么概念,首先在 Vue 的源码里面是有三种 watcher 的。我们只看 renderWatcher 的定义。

// 位于 vue/src/core/instance/lifecycle.js
new Watcher(vm, updateComponent, noop, {
  before () {
   if (vm._isMounted) {
    callHook(vm, 'beforeUpdate')
   }
  }
}, true /* isRenderWatcher */)

updateComponent = () => {
 vm._update(vm._render(), hydrating)
}

// 位于 vue/src/core/instance/render.js
Vue.prototype._render = function (): VNode {
  ......

  const { render, _parentVnode } = vm.$options
  try {
   vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e) {
   ......
  }
  return vnode
}

稍微分析下流程:实例化 Vue 实例的时候会走到 options 取到由 template 编译生成的 render 函数,进而执行 renderWatcher 收集依赖。_render 返回的是组件的 vnode,传入 _update 函数从而执行组件的 patch,最终生成视图。

其次,从我写的 template 来分析,为了渲染选择面板的 DOM,是有两层 for 循环的,内部每次循环都会执行 getItemClass 函数,而函数的内部又是对 questions 这个响应式数组进行了 getter 求值,从目前来看,时间复杂度是 O(n²),如上图所示,我们大概有 2000 多道题目,我们假设有 10 个章节,每个章节有 200 道题目,getItemClass 内部是对 questions 进行了 6 次求值,这样一算,粗略也是 12000 左右,按 js 的执行速度,是不可能这么慢的。

那么问题是不是出现在对 questions 进行 getter 的过程中,出现了 O(n³) 的复杂度呢?

于是,我打开了 Vue 的源码,由于之前深入研究过源码,所以轻车熟路地找到了 vue/src/core/instance/state.js 里面将 data 转换成 getter/setter 的部分。

function initData (vm: Component) {
 ......
 // observe data
 observe(data, true /* asRootData */)
}

定义一个组件的 data 的响应式,都是从 observe 函数开始,它的定义是位于 vue/src/core/observer/index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {
 if (!isObject(value) || value instanceof VNode) {
  return
 }
 let ob: Observer | void
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__
 } else if (
  shouldObserve &&
  !isServerRendering() &&
  (Array.isArray(value) || isPlainObject(value)) &&
  Object.isExtensible(value) &&
  !value._isVue
 ) {
  ob = new Observer(value)
 }
 if (asRootData && ob) {
  ob.vmCount++
 }
 return ob
}

observe 函数接受对象或者数组,内部会实例化 Observer 类。

export class Observer {
 value: any;
 dep: Dep;
 vmCount: number;
 constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  if (Array.isArray(value)) {
   if (hasProto) {
    protoAugment(value, arrayMethods)
   } else {
    copyAugment(value, arrayMethods, arrayKeys)
   }
   this.observeArray(value)
  } else {
   this.walk(value)
  }
 }
 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i])
  }
 }
 observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
 }
}

Observer 的构造函数很简单,就是声明了 dep、value 属性,并且将 value 的 _ ob _ 属性指向当前实例。举个栗子:

// 刚开始的 options
export default {
  data : {
    msg: '消息',
    arr: [1],
    item: {
      text: '文本'
    }
  }
}
// 实例化 vm 的时候,变成了以下
data: {
  msg: '消息',
  arr: [1, __ob__: {
      value: ...,
      dep: new Dep(),
      vmCount: ...
    }],
  item: {
    text: '文本',
    __ob__: {
      value: ...,
      dep: new Dep(),
      vmCount: ...
    }
  },
  __ob__: {
    value: ...,
    dep: new Dep(),
    vmCount: ...
  }
}

也就是每个对象或者数组被 observe 之后,多了一个 _ ob _ 属性,它是 Observer 的实例。那么这么做的意义何在呢,稍后分析。

继续分析 Observer 构造函数的下面部分:

// 如果是数组,先篡改数组的一些方法(push,splice,shift等等),使其能够支持响应式
if (Array.isArray(value)) {
 if (hasProto) {
  protoAugment(value, arrayMethods)
 } else {
  copyAugment(value, arrayMethods, arrayKeys)
 }
 // 数组里面的元素还是数组或者对象,递归地调用 observe 函数,使其成为响应式数据
 this.observeArray(value)
} else {
 // 遍历对象,使其每个键值也能成为响应式数据
 this.walk(value)
}
walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   // 将对象的键值转换成 getter / setter,
   // getter 收集依赖
   // setter 通知 watcher 更新
   defineReactive(obj, keys[i])
  }
}
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
}

我们再捋一下思路,首先在 initState 里面调用 initData,initData 得到用户配置的 data 对象后调用了 observe,observe 函数里面会实例化 Observer 类,在其构造函数里面,首先将对象的 _ ob _ 属性指向 Observer 实例(这一步是为了检测到对象添加或者删除属性之后,能触发响应式的伏笔),之后遍历当前对象的键值,调用 defineReactive 去转换成 getter / setter。

所以,来分析下 defineReactive。

// 如果是数组,先篡改数组的一些方法(push,splice,shift等等),使其能够支持响应式
if (Array.isArray(value)) {
 if (hasProto) {
  protoAugment(value, arrayMethods)
 } else {
  copyAugment(value, arrayMethods, arrayKeys)
 }
 // 数组里面的元素还是数组或者对象,递归地调用 observe 函数,使其成为响应式数据
 this.observeArray(value)
} else {
 // 遍历对象,使其每个键值也能成为响应式数据
 this.walk(value)
}
walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   // 将对象的键值转换成 getter / setter,
   // getter 收集依赖
   // setter 通知 watcher 更新
   defineReactive(obj, keys[i])
  }
}
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
}

首先,我们从 defineReactive 可以看出,每个响应式属性都有一个 Dep 实例,这个是用来收集 watcher 的。由于 getter 与 setter 都是函数,并且引用了 dep,所以形成了闭包,dep 一直存在于内存当中。因此,假如在渲染组件的时候,如果使用了响应式属性 a,就会走到上述的语句1,dep 实例就会收集组件这个 renderWatcher,因为在对 a 进行 setter 赋值操作的时候,会调用 dep.notify() 去 通知 renderWatcher 去更新,进而触发响应式数据收集新一轮的 watcher。

那么语句2与3,到底是什么作用呢

我们举个栗子分析

<div>{{person}}<div>
export default {
 data () {
  return {
   person: {
    name: '张三',
    age: 18
   }
  }
 }
}

this.person.gender = '男' // 组件视图不会更新

因为 Vue 是无法探测到对象增添属性,所以也没有一个时机去触发 renderWatcher 的更新。

为此, Vue 提供了一个 API, this.$set ,它是 Vue.set 的别名。

export function set (target: Array<any> | Object, key: any, val: any): any {
 if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.length = Math.max(target.length, key)
  target.splice(key, 1, val)
  return val
 }
 if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
 }
 const ob = (target: any).__ob__
 if (target._isVue || (ob && ob.vmCount)) {
  process.env.NODE_ENV !== 'production' && warn(
   'Avoid adding reactive properties to a Vue instance or its root $data ' +
   'at runtime - declare it upfront in the data option.'
  )
  return val
 }
 if (!ob) {
  target[key] = val
  return val
 }
 defineReactive(ob.value, key, val)
 ob.dep.notify()
 return val
}

set 函数接受三个参数,第一个参数可以是 Object 或者 Array,其余的参数分别为 key, value。如果利用这个 API 给 person 增加一个属性呢?

this.$set(this.person, 'gender', '男') // 组件视图重新渲染

为什么通过 set 函数又能触发重新渲染呢?注意到这一句, ob.dep.notify()ob 怎么来的呢,那就得回到之前的 observe 函数了,其实 data 经过 observe 处理之后变成下面这样。

{
 person: {
  name: '张三',
  age: 18,
  __ob__: {
   value: ...,
   dep: new Dep()
  }
 },
 __ob__: {
  value: ...,
  dep: new Dep()
 }
}
// 只要是对象,都定义了 __ob__ 属性,它是 Observer 类的实例

从 template 来看,视图依赖了 person 这个属性值,renderWatcher 被收集到了 person 属性的 Dep 实例当中,对应 defineReactive 函数定义的 语句1 ,同时, 语句2 的作用就是将 renderWatcher 收集到 person._ ob _.dep 当中去,因此在给 person 增加属性的时候,调用 set 方法才能获取到 person._ ob _.dep,进而触发 renderWatcher 更新。

那么得出结论,语句2的作用是为了能够探测到响应式数据是对象的情况下增删属性而引发重新渲染的。

再举个栗子解释下 语句3 的作用。

<div>{{books}}<div>
export default {
 data () {
  return {
   books: [
    {
     id: 1,
     name: 'js'
    }
   ]
  }
 }
}

因为组件对 books 进行求值,而它是一个数组,所以会走到语句3的逻辑。

if (Array.isArray(value)) { // 语句3
  dependArray(value)
}

function dependArray (value: Array<any>) {
 for (let e, i = 0, l = value.length; i < l; i++) {
  e = value[i]
  e && e.__ob__ && e.__ob__.dep.depend()
  if (Array.isArray(e)) {
   dependArray(e)
  }
 }
}

从逻辑上来看,就是循环 books 的每一项 item,如果 item 是一个数组或者对象,就会获取到 item._ ob _.dep,并且将当前 renderWatcher 收集到 dep 当中去。

如果没有这一句,会发生什么情况?考虑下如下的情况:

this.$set(this.books[0], 'comment', '棒极了') // 并不会触发组件更新

如果理解成 renderWatch 并没有对 this.books[0] 进行求值,所以改变它并不需要造成组件更新,那么这个理解是有误的。正确的是因为数组是元素的集合,内部的任何修改是需要反映出来的,所以语句3就是为了在 renderWatcher 对数组求值的时候,将 renderWatcher 收集到数组内部每一项 item._ ob _.dep 当中去,这样只要内部发生变化,就能通过 dep 获取到 renderWatcher,通知它更新。

那么结合我的业务代码,就分析出来问题出现在语句3当中。

<div class="card-content">
 <div class="block" v-for="item in cardData" :key="item.secName">
  <div class="sub-title">{{item.secName}}</div>
  <div class="group">
   <span
    @click="cardClick(index + item.startIndex)"
    class="item"
    :class="getItemClass(index + item.startIndex)"
    v-for="(subItem, index) in item.secTids"
    :key="subItem">{{index + item.startIndex + 1}}</span>
  </div>
 </div>
</div>
getItemClass (index) {
 const ret = {}
 // 如果是做对的题目,但并不是当前选中
 ret['item_true'] = this.questions[index]......
 // 如果是做对的题目,并且是当前选中
 ret['item_true_active'] = this.questions[index]......
 // 如果是做错的题目,但并不是当前选中
 ret['item_false'] = this.questions[index]......
 // 如果是做错的题目,并且是当前选中
 ret['item_false_active'] = this.questions[index]......
 // 如果是未做的题目,但不是当前选中
 ret['item_undo'] = this.questions[index]......
 // 如果是未做的题目,并且是当前选中
 ret['item_undo_active'] = this.questions[index]......
 return ret
},

首先 cardData 是一个分组数据,循环里面套循环,假设有 10 个章节, 每个章节有 200 道题目,那么其实会执行 2000 次 getItemClass 函数,getItemClass 内部会有 6 次对 questions 进行求值,每次都会走到 dependArray,每次执行 dependArray 都会循环 2000 次,所以粗略估计 2000 * 6 * 2000 = 2400 万次,如果假设一次执行的语句是 4 条,那么也会执行接近一亿次的语句,性能自然是原地爆炸!

既然从源头分析出了原因,那么就要找出方法从源头上去解决。

拆分组件

很多人理解拆分组件是为了复用,当然作用不止是这些,拆分组件更多的是为了可维护性,可以更语义化,在同事看到你的组件名的时候,大概能猜出里面的功能。而我这里拆分组件,是为了隔离无关的响应式数据造成的组件渲染。从上图可以看出,只要任何一个响应式数据改变,Paper 都会重新渲染,比如我点击收藏按钮,Paper 组件会重新渲染,按道理只要收藏按钮这个 DOM 重新渲染即可。

在嵌套循环中,不要用函数

性能出现问题的原因是在于我用了 getItemClass 去计算每一个小圆圈的样式,而且在函数里面还对 questions 进行了求值,这样时间复杂度从 O(n²) 变成了 O(n³)(由于源码的 dependArray也会循环)。最后的解决方案,我是弃用了 getItemClass 这个函数,直接更改了 cardData 的 tids 的数据结构,变成了 tInfo,也就是在构造数据的时候,计算好样式。

this.cardData = [{
  startIndex: 0,
  secName: '章节名称',
  secId: '章节id',
  tInfo: [
  {
    id: 1,
    klass: 'item_false'
  },
  {
    id: 2,
    klass: 'item_false_active'
  }]
}]

如此一来,就不会出现 O(n³) 时间复杂度的问题了。

善用缓存

我发现 getItemClass 里面自己写的很不好,其实应该用个变量去缓存 quesions,这样就不会造成对 questions 多次求值,进而多次走到源码的 dependArray 当中去。

const questions = this.questions

// good      // bad
// questions[0]  this.questions[0]
// questions[1]  this.questions[1]
// questions[2]  this.questions[2]
......

// 前者只会对 this.questions 一次求值,后者会三次求值

后感

从这次教训,自己也学到了也很多。

遇到问题的时候,要利用现有工具去分析问题的原因,比如 Chrome 自带的 Performance。

对于自己所用的技术,要追根究底,庆幸自己之前深入研究过 Vue 的源码,这样才能游刃有余地去解决问题,否则现在估计还一头雾水,如果有想深入理解 Vue 的小伙伴,可以参考Vue.js 技术揭秘,看过 GitHub 上面很多源码分析,这个应该是写的最全最好的,我自己也对该源码分析提过 PR。

实现一个需求很容易,但是要把性能做到最佳,成本可能急剧增加。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 使用imba.io框架得到比 vue 快50倍的性能基准

    我是标题党吗?是,但也不是.以图为证. 上图表示了vue, react 以及 imba 在 todo 这个项目中拥有60个 todoItem 不同进行 crud 操作的表现.可以看到 imba 达到了每秒操作5w次以上.如果你也想试一试该测试,可以访问Todos Bench.测试使用的是 Benchmark.js. imba 简单介绍 imba 是一种新的编程语言,可以编译为高性能的 JavaScript.可以直接用于 Web 编程(服务端与客户端)开发. 下面是语法: // 自定义标签 tag

  • 如何提升vue.js中大型数据的性能

    你好!欢迎大家访问VueDose的第一篇文章!我们在VueDose中开始冒险吧,你会喜欢这些对你有帮助的小技巧. VueDose的所有的文章都非常的简洁,我相信人们在这种格式下更容易找到有用的东西.所以,让我们直奔主题. 通常我们需要获取对象数据,比如用户,项目,文章,等等等等····· 有时,我们甚至不需要修改它们,只是为了展示它们或在(a.k.a. Vuex)中存贮它们的全局状态.那么获取这个数据的简单代码如下: export default { data: () => ({ users:

  • 使用异步组件优化Vue应用程序的性能

    单页应用其一个问题是首屏屏渲染速度较慢.这是因为页面首次加载时服务器将向客户端发送大量JavaScript,在屏幕上显示任何内容之前必须下载并解析.可以想象,随着应用程序规模的扩大,这个问题对用户体验的影响也会越来越突出. 现在幸运的是,当使用Vue CLI构建Vue应用程序时(使用webpack),可以采取一些措施来抵消这种情况.在本文中,我将演示如何在应用程序的初始渲染之后使用 异步组件 和webpack的代码分割功能加载到页面的某些部分.这将使初始加载时间降至最低,并为您的应用程序提供更好

  • mpvue性能优化实战技巧(小结)

    最近一直在折腾mpvue写的微信小程序的性能优化,分享下实战的过程. 先上个优化前后的图: 可以看到打包后的代码量从813KB减少到387KB,Audits体验评分从B到A,效果还是比较明显的.其实这个指标说明不了什么,而且轻易就可以做到,更重要的是优化小程序运行过程中的卡顿感,请耐心往下看. 常规优化 常规的Web端优化方法在小程序中也是适用的,而且不可忽视. 一.压缩图片 这一步最简单,但是容易被忽视.在tiny上在线压缩,然后下载替换即可. 我这项目的压缩率高达72%,可以说打包后的代码从

  • 如何测量vue应用运行时的性能

    在上一篇文章中,我们讨论了如何提高大型数据的性能.但是我们还没有测量它提高了多少. 我们可以使用Chrome DevTools 的性能选项来实现这一点.但是为了获取准确数据,我们必须在Vue上激活性能模式. 我们可以在main.js或者插件中设置全局变量,代码如下: Vue.config.performance = true; 如果你设置了正确的 NODE_ENV 环境变量,那么可以使用非生产环境做判断. const isDev = process.env.NODE_ENV !== "produ

  • 浅谈Vue 性能优化之深挖数组

    背景 最近在用 Vue 重构一个历史项目,一个考试系统,题目量很大,所以核心组件的性能成为了关注点.先来两张图看下最核心的组件 Paper 的样式. 从图中来看,分为答题区与选择面板区. 稍微对交互逻辑进行下拆解: 答题模式与学习模式可以相互切换,控制正确答案显隐. 单选与判断题直接点击就记录答案正确性,多选是选择答案之后点击确定才能记录正确性. 选择面板则是记录做过的题目的情况,分为六种状态(未做过的,未做过且当前选择的,做错的,做错的且当前选择的,做对的,做对的且当前选择的),用不同的样式去

  • 浅谈android性能优化之启动过程(冷启动和热启动)

    本文介绍了浅谈android性能优化之启动过程(冷启动和热启动) ,分享给大家,具体如下: 一.应用的启动方式 通常来说,启动方式分为两种:冷启动和热启动. 1.冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动. 2.热启动:当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热

  • 浅谈PHP性能优化之php.ini配置

    内存 默认设置 memory_limit = 128M 单个进程可使用的内存最大值,这个值的设定可以从以下几点考虑: 应用的类型.如果是内存集中型应用,可增加该值: 单个 PHP 进程平均消耗的内存,该值可通过多次运行同一个脚本来计算平均值: 能负担多少个 php-fpm 进程:该值等于分配的总内存除以单个 PHP 进程平均消耗的内存: 文件上传 默认设置 file_uploads = On max_file_uploads = 20 upload_max_filesize = 2M max_e

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • 浅谈react性能优化的方法

    React性能优化思路 软件的性能优化思路就像生活中去看病,大致是这样的: 使用工具来分析性能瓶颈(找病根) 尝试使用优化技巧解决这些问题(服药) 使用工具测试性能是否确实有提升(疗效确认) 初识react只是为了尽快完成项目,后期进行代码审查时候发现有很多地方需要优化,因此做了个小结. Code Splitting shouldComponentUpdate避免重复渲染 使用不可突变数据结构 组件尽可能的进行拆分.解耦 列表类组件优化 bind函数优化 不要滥用props ReactDOMSe

  • 浅谈vue项目优化之页面的按需加载(vue+webpack)

    通过vue写的单页应用时,可能会有很多的路由引入.当打包构建的时候,javascript包会变得非常大,影响加载.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了.这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来.结合Vue的异步组件和webpackde code splitting feature,轻松实现路由组件的懒加载. 就像图片的懒加载一样,如果客户根本就没有看到那些图片,而我们却在打开页面的时候全部给加载完了,这

  • 浅谈 Vue 项目优化的方法

    好久不写博文了,本文作为我使用半年 vue 框架的经验小结,随便谈谈,且本文只适用于 vue-cli 初始化的项目或依赖于 webpack 打包的项目. 前几天看到大家说 vue 项目越大越难优化,带来很多痛苦,这是避免不了的,问题终究要解决,框架的性能是没有问题的,各大测试网站都有相关数据.下面进入正题 基础优化 所谓的基础优化是任何 web 项目都要做的,并且是问题的根源.HTML,CSS,JS 是第一步要优化的点 分别对应到 .vue 文件内的,<template>,<style&

  • 浅谈vue路径优化之resolve

    通过vue-cli来创建vue+webpack的项目时,已经有很多都配置好了,但是路径方面为了方便开发,还可以优化. 1. resolve.extensions 在webpack.base.conf.js中,我们可以看到resolve配置,其中的extengsions是一个数组,如下所示: extensions: ['.js', '.vue', '.json'], 通过这样的配置,我们在组件中过着路由中应用组件时,就可以更为方便的应用,比如: import Hello from '@compon

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • 浅谈VUE监听窗口变化事件的问题

    Vuejs 本身就是一个 MVVM 的框架.但是在监听 window 上的 事件 时,往往会显得 力不从心. 比如 这次是 window.resize恩,我做之前也是百度了一下.看到大家伙都为这个问题而发愁. 问题: 今天我也 遇到了这样一个问题, 是关于canvas 自适应. 根据窗口的变化去变化 canvas 的宽度备注: 重要的问题说三遍 解决 框架内的bug 先说 框架 版本 版本 版本 (这里用的 Vue 2.x .ES6) 解决方案: 第一步: 先在 data 中去 定义 一个记录宽

随机推荐