MVC的故事

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

推荐阅读更多精彩内容

  • 大家好,我是IT修真院深圳分院第3期的学员,一枚正直纯洁善良的前端程序员,今天给大家分享一下,修真院官网前端工程师...
    大大头大阅读 6,065评论 0 4
  • 一:什么是闭包?闭包的用处? (1)闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就 是将函数内部和函数外...
    xuguibin阅读 9,600评论 1 52
  • 曾經在訓練所的AM認證的課程聽到一句話:「把自己定位成顧問、問題解決者,而不是推銷東西的業務。」 覺得這句話說的很...
    rusty6kimo阅读 183评论 0 1
  • 安装 1. Update Homebrew’s package database. brew update 2. ...
    ROBIN2015阅读 314评论 1 1
  • 有几个关系比较好的舍友,现在我们都已经毕业一年了。不例外的被家长拖到相亲的行列了,说起来我们宿舍也挺奇葩的,整个宿...
    清风一笑阅读 309评论 0 0