知识点


理解Vue设计思想

  • MVVM模式

mvvm.png

MVVM框架的三大要素,数据相应式,模版引擎,及渲染
数据响应式:监听数据变化,并在视图中更新

  • object.defineproperty()
  • proxy

模版引擎(提供描述视图的模版语法)

  • 插值表达式{{}}
  • 指令 v-bind v-on v-model v-for v-if

渲染(如何将模版转换成html)

  • 模版=>虚拟dom=>dom

数据响应式原理

数据变更能够响应在视图中,就是数据响应式。vue2利用object.defineProperty()实现变更检测。

  • 简单实现 ```vue const obj = {}

function defineReactive(obj,key,val){ Object.defineProperty(obj,key,{ get(){ console.log(get,${key},${val},) return val }, set(newVal){ if(newVal !=val){ val = newVal; console.log(set${key},${val}) } } }) }

defineReactive(obj,’foo’,’foo’); obj.foo; obj.foo = ‘joyce’

  1. - 结合视图
  2. ```vue
  3. <html>
  4. <head></head>
  5. <body>
  6. <div id="app"></div>
  7. <script>
  8. const obj = {}
  9. function defineReactive(obj,key,val){
  10. observe(val)// 解决嵌套参数问题 将嵌套对象变成响应式
  11. Ojbect.defineProperty(obj,key,{
  12. get(){
  13. console.log(`get ${key}${val}`)
  14. return val;
  15. },
  16. set(newVal){
  17. if(newVal!==val){
  18. observe(val) //解决赋值是对象的问题
  19. val = newVal
  20. //更新到视图
  21. update()
  22. }
  23. }
  24. })
  25. }
  26. defineReactive(obj,'foo','');
  27. obj.foo = new Date().toLocalTimeString();
  28. function update(){
  29. app.innerText = obj.foo;
  30. }
  31. setInterVal(()=>{
  32. obj.foo = new Date().toLocalTimeString();
  33. },1000)
  34. </script>
  35. </body>
  36. </html>
  • 遍历需要响应化的对象 ```vue function observe(obj){ if(typeof obj !== object || obj == null){ return; } object.keys(obj).forEach(key=>{ defineReactive(obj,key,obj[key]); }) }

const obj = {foo:’foo’,bar:’bar’,baz:{a:1}};

observe(obj)

obj.baz.a = 10; //嵌套对象 no ok 需要在defineReactive方法中解决

obj.baz = {a:1};//解决赋值是对象的问题

obj.dong = ‘dong’ //添加属性/删除新属性无法检测

function set(obj,key,val){ defineReactive(obj,key,val); }

set(obj,’dong’,’dong’);

//需要注意的是defineProPerty()不支持数组; 思考如何解决数组的响应式问题?

  1. <a name="VvPgn"></a>
  2. ## vue中的数据响应化
  3. <a name="d6jSv"></a>
  4. ### 原理分析
  5. 1. 首先执行new Vue()执行初始化,对data数据做响应式处理,这个过程发生在observe中。
  6. 1. 同时对模版进行编译,找到其中动态绑定的数据,从data中获取数据并初始化视图,这个过程发生在compile中
  7. 1. 同时定义一个watcher和更新函数,将来数据发生变化时,就会调用watcher中的更新函数。
  8. 1. 由于data中的某个key可能在视图中的多个地方出现,所以每个key都需要一个管家Dep来对应多个watcher
  9. 1. 将来一旦数据发生变化,会首先找到对应的Dep,通知所有watcher执行更新函数
  10. ![WechatIMG6.png](https://cdn.nlark.com/yuque/0/2021/png/21734057/1622107982501-d322c14a-2968-42dc-a363-aef2f2e282c3.png#clientId=u1cc31653-48c5-4&from=ui&id=u862bd760&margin=%5Bobject%20Object%5D&name=WechatIMG6.png&originHeight=1056&originWidth=2104&originalType=binary&size=429447&status=done&style=none&taskId=u00ed31a4-4a3f-45fb-885f-91cac5729b0)
  11. <a name="A8yPA"></a>
  12. ### 涉及类型介绍
  13. - KVue:框架构造函数
  14. - Obverser:执行数据响应化(需要区别对象 数组类型)
  15. - compile:编译模版,初始化视图,收集依赖(订阅数据变化,绑定更新函数,watcher创建)
  16. - watcher:执行更新函数(更新dom)
  17. - Dep:管理多个watcher,批量更新
  18. <a name="pnNuA"></a>
  19. ### Kvue
  20. 框架构造函数,执行初始化
  21. - 执行初始化,对data进行响应式处理Kvue.js
  22. ```vue
  23. function observe(obj){
  24. if(typeof obj !== object && obj == null){
  25. return
  26. }
  27. new Observer(obj);
  28. }
  29. function defineReactive(){
  30. ......
  31. }
  32. // 进行数据代理
  33. function proxy(vm){
  34. Object.keys(vm.$data).forEach(key={
  35. Object.defineProperty(vm,key,{
  36. get(){
  37. return vm.$data[key];
  38. },
  39. set(newVal){
  40. vm.data[key] = newVal;
  41. }
  42. })
  43. })
  44. }
  45. class KVue{
  46. constructor(options){
  47. this.$options = options;
  48. this.$data = options.data;
  49. observe(this.$data); //对数据进行响应式
  50. proxy(this, '$data');
  51. }
  52. }
  53. class Observer{
  54. construtor(value){
  55. this.$value = value;
  56. this.walk($this.value)
  57. }
  58. walk(obj){
  59. object.keys(obj).forEach(key=>{
  60. defineReactive(obj,key,obj[key]);
  61. })
  62. }
  63. }

编译-compile

编译模版中的特殊语法,初始化视图,更新视图
WechatIMG7.png

初始化视图

根据节点类型进行编译,compile.js

  1. class compile{
  2. constructor(el,vm){
  3. this.$vm = vm;
  4. this.$el = document.querySelector(el);
  5. if(this.$el){
  6. this.compile(this.$el)
  7. }
  8. }
  9. function compile(el){
  10. const childNodes = el.childNodes; //获取元素的子节点集合
  11. Array.from(childNodes).forEach(node=>{
  12. // 如果是元素节点
  13. if(this.isElement(node)){
  14. console.log('编译元素'+node.nodeName);
  15. // 插值文本
  16. }else if(this.this.isInterpolation(node)){
  17. this.textContent(node)
  18. console.log("编译插值文本" + node.textContent
  19. }
  20. //如果还存在子节点 进行递归操作
  21. if(node.childNodes && node.childrenNodes.length>0){
  22. this.compile(node);
  23. }
  24. })
  25. }
  26. }
  27. function isElement(node){
  28. return node.nodeType == 1;
  29. }
  30. function isTerpolation(node){
  31. return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  32. }
  33. //编译插值
  34. function compileText(node){
  35. console.log(RegXep.$1)
  36. node.textContent = this.$vm[RegExp.$1];
  37. }
  38. // 编译元素
  39. function compileElement(node){
  40. let nodeAttrs = node.attributes;
  41. Array.from(nodeAttrs).forEach(attr=>{
  42. let attrName = attr.name;
  43. let exp = attr.value;
  44. if(this.isDirective(attrName){
  45. let dir = attrName.subString(2);
  46. this[dir] && this[dir](node,exp);
  47. })
  48. })
  49. }
  50. //是否存在指令
  51. function isDirective(attr){
  52. return attr.indexOf("k-") === '0';
  53. }
  54. //
  55. function text(node,exp){
  56. node.textContent = this.$vm[exp];
  57. }

依赖收集

视图中会用到data中某key,这称为依赖,同一个key可能出现多次,每次都需要收集出来用一个Watcher来维护它们,此过程依赖收集。多个watcher需要一个Dep来管理,需要更新时由Dep统一通知

  1. new Vue({
  2. template:
  3. `<div>
  4. <p>{{name1}}</p>
  5. <p>{{name2}}</p>
  6. <p>{{name1}}</p>
  7. <div>`,
  8. data: {
  9. name1: 'name1',
  10. name2: 'name2'
  11. }
  12. });

WechatIMG8.png

实现思路

  1. defineReactive时为每一个key创建一个Dep实例2
  2. 初始化视图时读取某个key,例如name1,创建一个watcher1
  3. 由于触发name1的getters方法,便将watcher1添加到name1对应的dep1中
  4. 当name1更新时,触发set方法,便可以通过Dep通知其管理的所有watcher进行更新

WechatIMG6.png
创建watcher Kvue.js

  1. watchers = []; //用于临时保存watcher测试用
  2. //监听器 负责更新视图
  3. class Watcher{
  4. constructor(vm,key,updateFn){
  5. //Kue实例
  6. this.vm = vm;
  7. this.key = key;
  8. //更新函数
  9. this.updateFn = updateFn
  10. //临时放入watcher数组
  11. watcher.push(this);
  12. }
  13. // 更新函数
  14. function update(){
  15. this.updateFn.call(this.vm,this.vm[this.key]);
  16. }
  17. }

编写更新函数,创建watcher

  1. 调用update函数为插值文本赋值
  2. compileText(node){
  3. // node.textContent = this.$vm[RegExp.$1]
  4. this.update(node,RegExp.$1,'text')
  5. }
  6. function text(node,exp){
  7. this.update(node,exp,'text')
  8. }
  9. function html(node,exp){
  10. this.update(node,exp,'html')
  11. }
  12. function update(node,exp,dir){
  13. const fn = this[dir+'updater'];
  14. fn && fn(node,this.$vm[exp]);
  15. // 创建Watcher 执行更新函数
  16. new Watcher(this.$vm,node,function(val){
  17. fn && fn(node,val);
  18. })
  19. }
  20. function textUpdater(node,val){
  21. node.textContent = val;
  22. }
  23. function htmlUpdater(node,val){
  24. node.innerHtml = val;
  25. }

声明Dep

  1. class Dep{
  2. 思路:创建Dep统一管理watcher,当数据更新时,触发set方法,通过Dep订阅通知对应watcher更新函数
  3. constructor(){
  4. this.deps = [];
  5. }
  6. function add(dep){
  7. this.deps.push(dep);
  8. }
  9. function notify(){
  10. this.deps.forEach(dep=>{
  11. dep.update()
  12. })
  13. }
  14. }

创建watcher时出发getter

  1. class watcher{
  2. construtor(vm,key,updateFn){
  3. Dep.target = this;
  4. this.vm[this.key];
  5. Dep.target = null;
  6. }
  7. }

依赖收集,创建Dep实例

  1. defineReactive(obj,key,val){
  2. this.observe(val);
  3. const dep = new Dep();
  4. Object.defineProperty(obj,key,{
  5. get(){
  6. Dep.target && dep.addDep(Dep.target);
  7. return val;
  8. },
  9. set(newVal){
  10. if(newVal !== val) return;
  11. dep.notify();
  12. }
  13. })
  14. }