美文网首页
MVC的故事

MVC的故事

作者: _刘小c | 来源:发表于2018-09-21 16:04 被阅读0次

MVC的故事

现在的前端学习者,会有一个断层,知识的断层,为什么vue, react 会出现,好像他一开始就出现在哪里一样

所谓的老前端,眼见这些年前端怎么发展,怎么逐步完善,怎么逐步变成今天所谓的框架

然而,在一定程度上,两者表现上看起来没有什么区别,对一些前端新人来说,他们更年轻更有精力,用相对成熟的vue,react拧着螺丝的经历甚至比老前端更为丰富。

当然,这只是表象,当脱离了所谓的框架,新人还会写出具有mvc思想的代码吗?

前端MVC的开始

2010 年,Backbone.js 第一版发布,它是基于MVX思想的(X可以是任何东西),那时候用Backbone的人,用意大利面条来形容没有组织的代码,因为这些代码长长短短,还互相交织,你中有我,我中有你。
然而似乎在三年后国内,才开始使用这个东西。可见英文世界的前端知识,一直都是领先于中文世界的。
当然现在Backbone已经没人用了。 大家从backbone到了ng到了vue.js 0.8

意大利面条代码: 如果不从第一行看到最后一行,就不知道干什么的

function fetchDb() {
  return axios.get('/books/1')
}

function saveDb(newData) {
  return axios.put('/books/1', newData)
}

var template = `
<div>
  书名:《__name__》,
  数量:<span id="number">__number__</span>
</div>
<div class="actions">
  <button id="increaseByOne">加1</button>
  <button id="decreaseByOne">减1</button>
</div>
`

fetchDb().then((response) => {
  let result = response.data
  $('#app').html(
    template.replace('__number__', result.number)
      .replace('__name__', result.name)
  )

  //加1
  $('#increaseByOne').on('click', (e) => {
    let oldResult = parseInt($('#number').text(), 10)
    let newResult = oldResult + 1
    saveDb({number: newResult}).then(function({data}) {
      console.log(data)
      $('#number').text(data.number)
    })
  })
  
  //减1
  $('#decreaseByOne').on('click', (e) => {
    let oldResult = parseInt($('#number').text(), 10)
    let newResult = oldResult - 1
    saveDb({number: newResult}).then(({data}) => {
      $('#number').text(data.number)
    })
  })

如果一段代码,你从头到尾一行行看的很流畅,额那这基本就是面条代码了

MVC来了

一些高端的程序员发现,意大利面条代码总是可以分为三类:

  1. 专门操作远程数据的代码(fetchDb 和 saveDb 等等)
  2. 专门呈现页面元素的代码(innerHTML 等等)
  3. 其他控制逻辑的代码(点击某按钮之后做啥的代码)

为什么分成这三类呢?因为我们前端抄袭了后端的分类思想,后端代码也经常分为三类:

  1. 专门操作 MySQL 数据库的代码
  2. 专门渲染 HTML 的代码
  3. 其他控制逻辑的代码(用户请求首页之后去读数据库,然后渲染 HTML 作为响应等等)

这些思路经过慢慢的演化,最终被广大程序员完善为 MVC 思想。

  1. M 专门负责数据
  2. V 专门负责表现
  3. C 负责其他逻辑

如果我们来反思一下,会发现这个分类是无懈可击的:

  1. 每个网页都有数据
  2. 每个网页都有表现(具体为 HTML)
  3. 每个网页都有其他逻辑
于是乎,MVC 成了经久不衰的设计模式(设计模式就是「套路」的意思)

让我们看看以上代码经过MVC洗涤后变成什么样子:

let model = {
  data: {
    number: 0,
    name: ''
  },
  fetch(id) {
    return axios.get(`/books/${id}`).then((response)=>{
      this.data = response.data
    })
  },
  update(newData) {
    let id = this.data.id
    return axios.put(`/books/${id}`, newData).then(({data})=>{
      this.data = data
    })
  }
}

let view = {
  el: '#app',
  template: `
    <div>
      书名:《__name__》,
      数量:__number__
    </div>
    <div class="actions">
      <button id="increaseByOne">加1</button>
    </div>
  `,
  render(data) {
    let html = this.template.replace('__name__', data.name)
      .replace('__number__', data.number)
    console.log(data)
    $(this.el).html(html)
  }
}

let controller = {
  init({ view, model}) {
    this.view = view
    this.model = model
    this.view.render(this.model.data)
    this.bindEvents()
    console.log(1)
    this.fetchModel()
    console.log(2)
  },
  events: [
    { type: 'click', selector: '#increaseByOne', fnName: 'add' },
    { type: 'click', selector: '#decreaseByOne', fnName: 'minus' },
    { type: 'click', selector: '#square', fnName: 'square' },
    { type: 'click', selector: '#cube', fnName: 'cube' },
  ],
  bindEvents() {
    this.events.map((event)=>{
      $(this.view.el).on(event.type, event.selector, this[event.fnName].bind(this))
    })
  },
  add(){
    let newData = {number: this.model.data.number + 1}
    this.updateModel(newData)
  },
  fetchModel(){
    this.model.fetch(1).then(() => {
      this.view.render(this.model.data)
    })
  },
  updateModel(newData){
    this.model.update(newData).then(()=>{
      this.view.render(this.model.data)
    })
  }
}

是不是看命名就很MVC

它改进了以下几点:

  1. 把意大利面条变成三块有结构有组织的对象:model、view 和 controller
  2. model 只负责存储数据、请求数据、更新数据
  3. view 只负责渲染 HTML(可接受一个 data 来定制数据)
  4. controller 负责调度 model 和 view
看起来代码变多了,但是我们似乎写了很多公共方法,可以用类封装起来,成为所谓的模版代码(俗称面向对象)
  let model = new Model()
  let controller = new Controller()
  let view = new View()

等等! 好像发现了新姿势, 好像读音都一样的

   let view = new View() ===  let view = new Vue()

没有错,其实vue就是这么来的。

var view = new Vue({
  el: '#app',
  data: {
    name: 'vue',
  },
  template: `<div id='app'>hello {{data.name}}</div>`
})

双向绑定

其实仔细看看,以上改进的mvc代码有一个很严重的BUG,每次 render 都会更新 #app 的 innerHTML,这可能会丢失用户的写在页面某个 input 里面的数据。

这有两个解决办法:

  1. 用户只要输入了什么,就记录在 JS 的 data 里(数据绑定的初步思想出现了)
  2. 不要粗暴的操作 innerHTML,而是只更新需要更新的部位(虚拟 DOM 的初步思想出现了)

后来的后来,vue0.8出现了,他就是简化版的Angular,他借鉴了Angular的双向绑定思想,真的就把new View变成了new Vue

Vue 的双向绑定(也是 Angular 的双向绑定)有这些功能:

  1. 只要 JS 改变了 view.number 或 view.name 或 view.n (注意 Vue 把 data 里面的 number、name 和 n 放到了 view 上面,没有 view.data 这个东西), HTML 就会局部更新
  2. 只要用户在 input 里输入了值,JS 里的 view.n 就会更新

看起来很魔法,其实还是思想,其实我们自己也可以轻松实现个双向绑定。不就是检测到数据变动后,自动render了一次,对吧。发布订阅者模式了解一下

Vue 还有很多其他功能,使得 Controller 显得多余:

  created(){ },
  methods: {
    add() {}
  }

从此以后,事情变得容易了很多。 vue已经magic一般的帮我们实现了双向绑定,你也了解了双绑的原理,用起来游刃有余。人们称之为MVVM

单向绑定

又有一批程序员,他们发现「双向绑定」有一点点反「组件化」,在跨组件的双向绑定变得很奇怪。于是,他们发明了单向绑定,在跨组件时使用,保证了数据的流向,让数据变得可控,组件耦合度降低。

但是不得不提,局部使用双向绑定是非常爽的。这就是为什么我们在用react的时候,input还是喜欢让他双绑起来更舒服一点。

双绑怎么玩

顺带提一提双向绑定,其实也走过了好几代。

  1. dirty check,也就是将触发数据变化的onChange改写了,在改写的onChange中render(Angular 1.X)
  2. getter setter, 利用ECMAScript 2015中提出的getter setter,可以轻松实现在get和set Value的时候顺便render一下。 但有一个Bug,你无法监听不存在的key (Vue)
  3. proxy, 利用ECMAScript 2017中新增的proxy,可以为对象定义基本操作的自定义行为,也可以完美解决上一代setter的bug,下一代Vue就要用这个重写了哦
好吧,到这儿故事讲完了。有啥用,理解MVX后,拧螺丝更加自信了呗。

相关文章

网友评论

      本文标题:MVC的故事

      本文链接:https://www.haomeiwen.com/subject/kwxznftx.html