怎么封装一个组件?

//父组件<template>    <div>        <h1>{{title}}</h1>        <child :name="name" :age="age" :hobby="hobby" @titleChanged="titleChanged"></child>    </div></template>import child from "./components/child"export default {      name: 'App',      data(){        return{            title: '父级内容',              name: 'hello',             age: 19,              hobby: ['swim','run','walk']        }      },      components:{        "child":child      },    titleChanged(val){        this.title = val;    }}       
//子组件<template>  <div class="hello">    <h3>{{name}}</h3>    <p>{{age}}</p>    <ul>      <li v-for="h in hobby">{{h}}</li> //遍历传递过去的值,而后出现到页面    </ul>    <button @click="changeTitle">向父级传值</button>  </div></template><script>export default {    name: 'HelloWorld',      props:{        users:{           //这个就是父组件中子标签自定义名字              type:String,              required:true        },        age: {            type: Number,            default: 0,        },        hobby: {            type: Array,            defautl: ()=>[]        }      },      methods: {          changeTitle(){              this.$emit("titleChanged","子向父组件传值");          }      }}</script>

请说一下响应式数据的了解?

Vue通过设定对象属性的 setter/getter 办法来监听数据的变动,通过getter进行依赖收集,而每个setter办法就是一个观察者,在数据变更的时候告诉订阅者更新视图。

function observe (obj) { // 咱们来用它使对象变成可察看的  // 判断类型  if (!obj || typeof obj !== 'object') {    return  }  Object.keys(obj).forEach(key => {    defineReactive(obj, key, obj[key])  })  function defineReactive (obj, key, value) {    // 递归子属性    observe(value)    Object.defineProperty(obj, key, {      enumerable: true, //可枚举(能够遍历)      configurable: true, //可配置(比方能够删除)      get: function reactiveGetter () {        console.log('get', value) // 监听        return value      },      set: function reactiveSetter (newVal) {        observe(newVal) //如果赋值是一个对象,也要递归子属性        if (newVal !== value) {          console.log('set', newVal) // 监听          render()          value = newVal        }      }    })  }}

observe这个函数传入一个 obj(须要被追踪变动的对象),通过遍历所有属性的形式对该对象的每一个属性都通过 defineReactive 解决,以此来达到实现侦测对象变动。值得注意的是,observe 会进行递归调用。

因为 Vue 通过Object.defineProperty来将对象的key转换成getter/setter的模式来追踪变动,但getter/setter只能追踪一个数据是否被批改,无奈追踪新增属性和删除属性。如果是删除属性,咱们能够用vm.$delete实现,那如果是新增属性,该怎么办呢?
1)能够应用 Vue.set(location, a, 1) 办法向嵌套对象增加响应式属性;
2)也能够给这个对象从新赋值,比方data.location = {...data.location,a:1}

  • Object.defineProperty 不能监听数组的变动,须要进行数组办法的重写,具体代码如下:
let methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push'];// 先获取到原来的原型上的办法let arrayProto = Array.prototype;// 创立一个本人的原型 并且重写methods这些办法let proto = Object.create(arrayProto)methods.forEach(method => {  proto[method] = function() {    // AOP    arrayProto[method].call(this, ...arguments)    render()  }})function observer(obj) {  // 把所有的属性定义成set/get的形式  if (Array.isArray(obj)) {    obj.__proto__ = proto    return  }  if (typeof obj == 'object') {    for (let key in obj) {      defineReactive(obj, key, obj[key])    }  }}function defineReactive(data, key, value) {  observer(value)  Object.defineProperty(data, key, {    get() {      return value    },    set(newValue) {      observer(newValue)      if (newValue !== value) {        render()        value = newValue      }    }  })}observer(obj)function $set(data, key, value) {  defineReactive(data, key, value)}

这种办法将数组的罕用办法进行重写,进而笼罩掉原生的数组办法,重写之后的数组办法须要可能被拦挡。

vue中模板编译原理?

对于 Vue 编译原理这块的整体逻辑次要分三个局部,也能够说是分三步,这三个局部是有前后关系的:

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)
<div>  <p>{{name}}</p></div>

下面这样一个简略的 模板 转换成 element AST 后是这样的:

{  tag: "div"  type: 1,  staticRoot: false,  static: false,  plain: true,  parent: undefined,  attrsList: [],  attrsMap: {},  children: [      {      tag: "p"      type: 1,      staticRoot: false,      static: false,      plain: true,      parent: {tag: "div", ...},      attrsList: [],      attrsMap: {},      children: [{          type: 2,          text: "{{name}}",          static: false,          expression: "_s(name)"      }]    }  ]}

这段模板字符串会扔到 while 中去循环,而后 一段一段 的截取,把截取到的 每一小段字符串 进行解析,直到最初截没了,也就解析完了

  • 第二步是对 AST 进行动态节点标记,次要用来做虚构DOM的渲染优化(优化器)

优化器的指标是找出那些动态节点并打上标记,而动态节点指的是 DOM 不须要发生变化的节点。

每次从新渲染的时候不须要为动态节点创立新节点;在 Virtual DOM 中 patching 的过程能够被跳过。

  • 第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
{  render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`}

通过递归去拼一个函数执行代码的字符串,递归的过程依据不同的节点类型调用不同的生成办法,如果发现是一颗元素节点就拼一个 _c(tagName, data, children) 的函数调用字符串,而后 datachildren 也是应用 AST 中的属性去拼字符串。

如果 children 中还有 children 则递归去拼;最初拼出一个残缺的 render 函数代码。

vue中diff的原理?

渲染实在DOM的开销是很大的,比方有时候咱们批改了某个数据,如果间接渲染到实在dom上会引起整个dom树的重绘和重排,有没有可能咱们只更新咱们批改的那一小块dom而不要更新整个dom呢?diff算法可能帮忙咱们。

咱们先依据实在DOM生成一颗virtual DOM,当virtual DOM某个节点的数据扭转后会生成一个新的Vnode,而后VnodeoldVnode作比照,发现有不一样的中央就间接批改在实在的DOM上,而后使oldVnode的值为Vnode

diff的过程就是调用名为patch的函数,比拟新旧节点,一边比拟一边给实在的DOM打补丁。

应用双指针模式,对虚构节点进行比对,对雷同的节点进行复用,对发生变化的节点进行patch。以下是vue源码中对虚构节点的比对形式:

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {    let oldStartIdx = 0 // 旧头索引    let newStartIdx = 0 // 新头索引    let oldEndIdx = oldCh.length - 1 // 旧尾索引    let newEndIdx = newCh.length - 1 // 新尾索引    let oldStartVnode = oldCh[0] // oldVnode的第一个child    let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最初一个child    let newStartVnode = newCh[0] // newVnode的第一个child    let newEndVnode = newCh[newEndIdx] // newVnode的最初一个child    let oldKeyToIdx, idxInOld, vnodeToMove, refElm    // removeOnly is a special flag used only by <transition-group>    // to ensure removed elements stay in correct relative positions    // during leaving transitions    const canMove = !removeOnly    // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证实diff完了,循环完结    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {      // 如果oldVnode的第一个child不存在      if (isUndef(oldStartVnode)) {        // oldStart索引右移        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left      // 如果oldVnode的最初一个child不存在      } else if (isUndef(oldEndVnode)) {        // oldEnd索引左移        oldEndVnode = oldCh[--oldEndIdx]      // oldStartVnode和newStartVnode是同一个节点      } else if (sameVnode(oldStartVnode, newStartVnode)) {        // patch oldStartVnode和newStartVnode, 索引左移,持续循环        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)        oldStartVnode = oldCh[++oldStartIdx]        newStartVnode = newCh[++newStartIdx]      // oldEndVnode和newEndVnode是同一个节点      } else if (sameVnode(oldEndVnode, newEndVnode)) {        // patch oldEndVnode和newEndVnode,索引右移,持续循环        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)        oldEndVnode = oldCh[--oldEndIdx]        newEndVnode = newCh[--newEndIdx]      // oldStartVnode和newEndVnode是同一个节点      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right        // patch oldStartVnode和newEndVnode        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)        // 如果removeOnly是false,则将oldStartVnode.eml挪动到oldEndVnode.elm之后        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))        // oldStart索引右移,newEnd索引左移        oldStartVnode = oldCh[++oldStartIdx]        newEndVnode = newCh[--newEndIdx]      // 如果oldEndVnode和newStartVnode是同一个节点      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left        // patch oldEndVnode和newStartVnode        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)        // 如果removeOnly是false,则将oldEndVnode.elm挪动到oldStartVnode.elm之前        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)        // oldEnd索引左移,newStart索引右移        oldEndVnode = oldCh[--oldEndIdx]        newStartVnode = newCh[++newStartIdx]      // 如果都不匹配      } else {        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)        // 尝试在oldChildren中寻找和newStartVnode的具备雷同的key的Vnode        idxInOld = isDef(newStartVnode.key)          ? oldKeyToIdx[newStartVnode.key]          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)        // 如果未找到,阐明newStartVnode是一个新的节点        if (isUndef(idxInOld)) { // New element          // 创立一个新Vnode          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)        // 如果找到了和newStartVnodej具备雷同的key的Vnode,叫vnodeToMove        } else {          vnodeToMove = oldCh[idxInOld]          /* istanbul ignore if */          if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {            warn(              'It seems there are duplicate keys that is causing an update error. ' +              'Make sure each v-for item has a unique key.'            )          }          // 比拟两个具备雷同的key的新节点是否是同一个节点          //不设key,newCh和oldCh只会进行头尾两端的互相比拟,设key后,除了头尾两端的比拟外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key能够更高效的利用dom。          if (sameVnode(vnodeToMove, newStartVnode)) {            // patch vnodeToMove和newStartVnode            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)            // 革除            oldCh[idxInOld] = undefined            // 如果removeOnly是false,则将找到的和newStartVnodej具备雷同的key的Vnode,叫vnodeToMove.elm            // 挪动到oldStartVnode.elm之前            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)          // 如果key雷同,然而节点不雷同,则创立一个新的节点          } else {            // same key but different element. treat as new element            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)          }        }        // 右移        newStartVnode = newCh[++newStartIdx]      }    }

vue的渲染流程?

从模板到实在dom节点还须要通过一些步骤

把模板编译为render函数;

实例进行挂载, 依据根节点render函数的调用,递归的生成虚构dom;

比照虚构dom,渲染到实在dom;

组件外部data发生变化,组件和子组件援用data作为props从新调用render函数,生成虚构dom, 返回到步骤3。

为何vue采纳异步渲染?

如果不采取异步更新,那么每次更新数据都会对以后组件进行从新渲染,为了性能思考,Vue 会在本轮数据更新后,再去异步更新数据。

原理:

  • dep.notify() 告诉 watcher 进行更新操作
  • subs[i].update() 顺次调用 watcher 的 update
  • queueWatcher将 watcher 从新放到队列中
  • nextTick(flushSchedulerQueue) 异步清空 watcher 队列

谈一谈你对Vue性能优化的了解 ?

次要包含:上线代码包打包、源码编写优化、用户体验优化。

1.代码包优化

  • 屏蔽sourceMap
  • 对我的项目代码中的JS/CSS/SVG(*.ico)文件进行gzip压缩
  • 对路由组件进行懒加载

2.源码优化

  • v-if 和 v-show抉择调用
  • 为item设置惟一key值
  • 细分vuejs组件
  • 缩小watch的数据
  • 内容类零碎的图片资源按需加载
  • SSR(服务端渲染)

3.用户体验优化

  • 菊花loading
  • 骨架屏加载