vue面试题相关

135 阅读4分钟

vue的构建流程

1.初始化数据

new Vue调用原型链上的_init方法,将this挂载到vm上,new Vue中的参数options挂载到$options上,根据参数开始初始化
如果传入了data,就初始化data
initData函数中,判断根组件的data是不是函数,是就调用call到vm上,是对象直接使用

2.响应式原理,数据劫持

1的基础上,initData函数中拿到data后,observe(data)观察数据

observe中对对象进行劫持数据(null或者非对象直接return),为了避免重复劫持,用实例的形式即new Observer()

Observer类中Object.keys遍历使用Object.defineProperty,定义get/set方法

Object.defineProperty定义后,但是没有使用它,就没有绑定get/set方法,需要vm._data = data使用一下挂载到vm实例上

这样绑定只绑定了第一层,如果是引用类型,需要递归observe()

为了把数据绑定到vm上而不是vm._data要继续通过循环Object.defineProperty对每一个属性绑定get/set,这样就可以直接使用vm.name,vm.age

3.数组的劫持

另外一种情况就是对数组的劫持,分为两种情况,一种是对数组的push/pop/shift/unshift/reverse/splice/sort的重写,一种是对数组里的对象直接添加响应式

重写push等:
    通过Object.create(Array.prototype),在这个实例对象上去改写push方法,其他的方法如concat全部继承使用
    
    然后通过push,unshift,splice添加的元素进行数据劫持,不可枚举的属性去拿到父类Observer上的观测数组方法递归使用即可

Observer类中判断isArray,forEach遍历添加observe响应式

4.模版引擎

响应式处理完成,接下来和视图挂钩,定义原型链上的$mount方法

new Vue中如果定义render函数,没有的话看看有template字段,没有的话通过el去拿元素,通过编译生成ast语法树和render方法

5.模版转ast语法树,语法树转为render函数

/**
 * 第一部分:
 * 五个正则匹配(其实是6个,还有一个在转render函数时使用,匹配模版变量的):开始标签的开始,开始标签的结束,文本,属性键值对,结束标签
 *
 * while循环,通过indexOf查找<的位置,0的话不是开始标签就是结束标签,一边匹配tag和attrs,一边通过substring截取匹配过的部分,直到html为空的时候截取就结束了
 *
 *
 *
 * 第二部分:
 * 转成树结构,模拟栈,每遇到一个标签,就往数组的最后一位push, 标签结束就pop掉
 *
 *
 * 第三部分:转为render函数
 * render函数分为三个函数,_c,_v,_s用来处理节点\变量之类的,用字符串形式拼接起来
 */

6.render函数的准备

通过5拿到了render函数的字符串,要转换为函数,就通过new Function(),
render中的参数是挂载在vm,需要用到with(vm){}

定义mountComponent实现组件的挂载,在mountComponent中调用_render函数
    调用render生成虚拟dom
    将虚拟dom转换成真实dom
    插入到el元素中


// 模版引擎的实现原理:with + new Function

// vue的核心流程:
初始化数据 + 响应式
模板引擎转化为ast语法树,生成render方法
后续每次更新只执行render方法就行了

7.实现render函数,从虚拟dom转向真实dom

render:
调用render函数,实现render中_c,_v,_s函数

_c:createElement转为元素节点
_v:createTextVNode转为文本节点
_s:文本转为stringify转为字符串


update:
首次渲染和后续更新,都会调用update中的patch函数

初次渲染的时候,判断是不是真实dom,真实dom通过removeChild移除,把虚拟dom转化成真实dom,通过render方法递归,将产生的真实dom insertBefore插入即可


问题是:
修改数据时,不能主动触发,还得需要vm._update(vm._render())才可以触发,怎么解决?-->依赖收集

8.依赖收集--观察者模式

- 1.创建Watcher类,在mountComponent中new Watcher传入vm._update(vm._render())方法,即初渲染和重新渲染时才去回调调用vm._update(vm._render())方法
// Watcher存在多个,每个组件都有,用this.id++区分


- 2.Watcher类中将渲染方法挂载到类上即get方法,调用this.get()方法相当于初渲染或重新渲染了(有取值的操作,即Object.defineProperty中的get方法)


- 3.需要将属性和watcher关联起来,在属性上添加dep,属性的dep和watcher是多对多的关系
// 一个组件中有n个属性,一个watcher,即多个dep对应一个watcher
// 一个属性可能在多个组件中使用,那么一个dep可能对应多个watcher


- 4.新建Dep类,在响应式中Object.defineProperty中,new Dep给每个属性劫持添加dep


- 5.Dep类上添加一个静态参数target,在Watcher中调用get渲染之前,把watcher赋值给target,渲染后赋值为null,这样渲染的时候触发defineproperty中get方法就能判断Dep.target是否存在,即是否经历了渲染过程
// (在js中通过this.num也触发了get,但是没有渲染的话target就是空的)


- 6.响应式的get方法中判断Dep.target是否存在,存在的话,往dep中手机watcher,同时watcher中收集dep
// watcher中存储对应的dep是为了后续的计算属性和清理工作
// 为了避免重复收集,要在watcher中用一个set集合作has操作
// 怎么做:此时的Dep.target就是watcher,在Dep.target上添加一个方法addDep,把this传过去,那么在Watcher中使用这个方法,就可以处理并在Dep上添加一个方法addWatcher,在dep使用


- 7.6中互相标记就完成了,在响应式的set中重新渲染就出发Dep.notify()方法,就可以遍历Dep所有相关的watcher,让这些watcher更新也就是触发watcher中的get方法
// 完成

// dep和watcher是多对多关系

// 问题:同时改变name和age,结果watcher中更新了两次,不好
  • 2
  • 3