Vue 中的 MVVM

MVVM 框架前生 (MVC 框架)

在介绍 MVVM 框架 之前, 先让我们一起了解一下 MVC 框架

image
  • MVC 框架 将整个前端页面分成 View, Controller, Modal

  • 当视图发生变化, 通过 Controller(控件) 将响应传入到 Model(数据源) 中, 由数据源改变 View 上面的数据

  • 整个过程看起来是行云流水, 业务逻辑放在 Model 中, 页面渲染逻辑放在 View

什么是 “MVVM 框架”

image
image

可以看到 MVVM 分别指 View, Model, View-Model

View 通过 View-ModelDOM Listeners 将事件绑定到 Model

Model 则通过 Data Bindings 来管理 View 中的数据

View-Model 从中起到一个连接桥的作用

  • View 层: 也叫视图层, 在前端开发中, 通常就是 DOM 层, 主要的作用是给用户展示各种信息

  • Model 层: 也叫数据层, 数据可能是我们固定的死数据, 更多的是来自我们服务器, 从网络上请求下来的数据

  • Vue-Model 层: 也叫视图模型层, 视图模型层是 ViewModel 沟通的桥梁, 一方面实现了 Data Binding(数据绑定), 将 Model 的改变实时的反应到 View 中, 另一方面它实现了 DOM Listener(DOM监听), 当 DOM 发生一些事件 (点击、输入、滚动、touch等) 时, 可以监听到, 并在需要的情况下改变对应的 Data

MVVM 与 MVC 的区别

  • 如果你看对 MVC 框架 有所了解的话, 你会发现 MVC 框架 实际运用上却存在一个问题: 那就是 MVC 框架 允许 ViewModel 直接进行通信!!!

  • 这会造成 ViewModel 之间随着业务量的不断庞大, 会出现蜘蛛网一样难以处理的依赖关系, 完全背离了开发所应该遵循的 “开放封闭原则”

  • 面对这个问题, MVVM 框架 就出现了, 它与 MVC 框架 的主要区别以下两点:

    1. 实现数据与视图的分离
    2. 通过数据来驱动视图, 开发者只需要关心数据变化, DOM 操作被封装了

MVVM 原理

MVVM 的实现主要是三个核心点:

  1. 响应式: Vue 如何监听 data 的属性变化
  2. 模板解析: Vue 的模板是如何被解析的
  3. 渲染: Vue 模板是如何被渲染成 HTML 的

响应式

对于 MVVM 来说, data 一般是放在一个对象当中, 例如:

let obj = {
  rp: 0
}

当我们访问或修改 obj 的属性的时候, 例如:

console.log(obj.rp) // 访问
obj.rp = 10         // 修改

但是这样的操作 Vue 本身是没有办法感知到的, 那么应该如何让 Vue 知道我们进行了访问或是修改的操作呢?

那就要使用 Object.defineProperty

let VueModel = {}
let data = {
  name: "zhangsan",
  age: 20
}

for (let key in data) {
  Object.defineProperty(VueModel, key, {
    get: () => {
      console.log("get", data[key]) // 监听
      return data[key]
    },
    set: value => {
      console.log("set", value)    // 监听
      data[key] = value
    }
  })
}

通过 Object.defineProperty 将 data 里的每一个属性的访问与修改都变成了一个函数, 在函数 getset 中我们即可监听到 data 的属性发生了改变

模版解析

首先模板是什么?

模板本质上是一串字符串, 它看起来和 HTML 的格式很相像, 实际上有很大的区别, 因为模板本身还带有逻辑运算, 比如 v-if, v-for 等等, 但它最后还是要转换为 HTML 来显示

<div id="app">
  <div>
    <input v-model="title">
    <button @click="submit">submit</button>
  </div>
  <div>
    <ul>
        <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
  </div>
</div>

模板在 Vue 中必须转换为 JS 代码

  • 原因在于: 在前端环境下, 只有 JS 才是一个图灵完备语言, 才能实现逻辑运算, 以及渲染为 HTML 页面

这里就引出了 Vue 中一个特别重要的函数 —— render

render 函数中的核心就是 with 函数

  • with 函数将某个对象添加到作用域链的顶部

  • 如果在 statement 中有某个未使用命名空间的变量, 跟作用域链中的某个属性同名, 则这个变量将指向这个属性值

例如:

let obj = {
    name: "feng",
    age: 20,
    getAddress: () => {
        alert("shanghai")
    }
}

function fnc() {
    with(obj) {
        alert(age)
        alert(name)
        getAddress()
    }
}

fnc()

with 将 obj 这个对象放在了自己函数的作用域链的顶部, 当执行下列函数时, 就会自动到 obj 这个对象去寻找同名的属性

而在 render 函数中, with 的用法是这样

<div id="app">
  <div>
    <input v-model="title">
    <button @click="submit">submit</button>
  </div>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
  </div>
</div>

<script>
  let data = {
      title: '',
      list: []
  }
  
  let app = new Vue({
    el: '#app',
    data,
    methods: {
      submit() {
        this.list.push(this.title)
        this.title = ''
      }
    }
  })
  
  with(this) {
    return _c(
      'div',
      {
        attrs: { "id": "app" }
      },
      [
        _c(
          'div',
            [
              _c(
                'input',
                  {
                    directives: [
                      {
                        name: "model",
                        rawName: "v-model",
                        value: (title),
                        expression: "title"
                      }
                    ],
                    domProps: {
                      "value": (title)
                    },
                    on: {
                      "input": function ($event) {
                        if ($event.target.composing) return;
                        title = $event.target.value
                      }
                    }
                  }
              ),
              _v(" "),
                _c(
                  'button',
                  {
                    on: {
                      "click": submit
                    }
                  },
                  [_v("submit")]
                )
            ]
        ),
        _v(" "),
        _c('div',
          [
            _c(
              'ul',
              _l((list), function (item) { return _c('li',[_v(_s(item))]) })
            )
          ]
        )
      ]
    )
  }
</script>

在一开始, 因为 new 操作符, 所以 this 指向了 app, 通过 with 我们将 app 这个对象放在作用域链的顶部, 因为在函数内部我们会多次调用 app 内部的属性, 所以使用 with 可以缩短变量长度, 提供系统运行效率

其中的 _c 函数表示的是创建一个新的 HTML 元素, 其基本用法为:

_c(element, { attrs }, [ children... ])
  • element 表示所要创建的 HTML 元素类型

  • attrs 表示所要创建的元素的属性

  • children 表示该 HTML 元素的子元素

  • _v 函数表示创建一个文本节点

  • _l 函数表示创建一个数组

  • 最终 render 函数返回的是一个虚拟 DOM

如何将模板渲染为 HTML

模板渲染为 HTML 分为两种情况

  1. 第一种是初次渲染的时候
  2. 第二种是渲染之后数据发生改变的时候, 它们都需要调用 updateComponent

其形式如下:

app._update(vnode) {
  const prevVnode = app._vnode
  app._vnode = vnode
  if (!prevVnode) {
    app.$el = app.__patch__(app.$el, vnode)
  } else {
    app.$el = app.__patch__(prevVnode, vnode)
  }
}

function updateComponent() {
  app._update(app._render())
}

首先读取当前的虚拟 DOM——app._vnode, 判断其是否为空

  • 若为空, 则为初次渲染, 将虚拟 DOM 全部渲染到所对应的容器当中(app.$el)
  • 若不为空, 则是数据发生了修改, 通过响应式我们可以监听到这一情况, 使用 diff 算法完成新旧对比并修改









参考文章: zhuanglog

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容