用+1功能的代码实现带你感受MVC

MVC

MVC的解释千千万,唯一统一的认识就是MCV分别指model、view、controller,至于其它的咱也不知道对错。

我理解的MVC,就像行为与样式分离,是把业务按照M、V、C的功能进行解耦:

  • Model用来管理业务逻辑相关的数据以及对数据的处理方法
  • View=render(data)用来展示Model里数据的当下状态,并实时跟随Model更新
  • Controller负责接受并响应View上用户的交互行为以及对Model内部数据进行操作

刚学编程时,为了完成一个功能,我们想到什么写什么,只要最后能凑出来就成。这种代码常称为“意大利面条”代码,就是一坨*的意思,而且面条之间没有章法可循,你不知道它从哪来,也不知道它到哪去,一旦项目大起来,改代码的工作量比从头写的工作量还大,事情朝着不可控的方向发展了去,改代码的人就很惨。

于是,理清业务逻辑,形成代码规范,方便日后维护,就显得异常重要,so前端开始借鉴后端的架构设计模式,实现了各种MV*框架。

这里以一个实现+1功能的小例子来演示下:

step1

入门级代码

step2

M、V、C初步分离,这块代码不能单独运行,看看MVC的意思就行

import './app.css'
import $ from 'jquery'

// 数据相关都放到m
const m = {
  data: {
    n: parseInt(localStorage.getItem('n'))
  }
}
// 视图相关都放到v
const v = {
  el: null,
  html: `
  <div id="add">{{n}}</div>
  <button id="btn">+1</button>
  `,
  render(n) {
    $(v.html.replace('{{n}}', n))
      .appendTo($('body>#app'))
  }
}
// 其他都放c
const c = {
  init() {
    c.ui = {
      $number: $('add'),
        $btn: $('btn')
    }
    c.bindEvents()
  },
  bindEvents(){
    c.ui.$btn.on('click', ()=>{
      let n = m.data.n
      n+=1
      localStorage.setItem('n', n)
      c.ui.$number.text(n)
    })
  }
}

v.render()
c.init()

V和C其实还可以进一步合并,这里就跳过了~

step3

之后,你又觉得M、V好像可以再封装一下,搞个类什么的,把一样的东西都塞进去,事件监听什么的所有地方都会用,让所有类继承吧,也方便了下次用。

import "./app.css"
import $ from "jquery"
import Model from "./base/Model.js"
import View from "./base/View"


const m = new Model({
  data: {
    //初始化数据
    n: parseFloat(localStorage.getItem('n')) || 100,
  },
  update: function(data){
    Object.assign(m.data, data)
    m.trigger('m:updated')
    localStorage.setItem('n', m.data.n.toString())
  }
})

const init = (el)=>{
  new View({
    el: el,
    data: m.data,
    // 初始化html
    html: `
    <div class="output">
      <span id="number">{{n}}</span>
    </div>
    <div class="actions">
      <button id="add">+1</button>
    </div>`,
    render(data){
      if(this.el.children.length !== 0) this.el.empty()
      $(this.html.replace('{{n}}', data.n)).appendTo(this.el)
    },
    events: {
      'click #add': 'add',
    },
    add(){
      m.update({n: m.data.n + 1})
    },
  })
}

export default init

完整代码看这里

这里是不是已经和Vue的样子有那么点像了?创建一个实例,再在里面填上你要的data、method啥的,还有render、template啥的,只不过Vue里看上去你只用new一个实例就行,还有单文件模板啥的,一上来你只用填自己要写的东西,脏活累活全被Vue干了,总之很方便。

是不是感觉这不是咱平常学的JS了?刚接触Vue的时候我也这么想,感觉自己JS学了跟白学似的。那是因为我们不知道,意大利面代码=>MVC模式=>框架这中间的过程,多去看看相关的教程讲解,一步步跟着来,你就懂了~

MVC绝不仅是一道与MVVM、MVP绑一块,解释下每个层的关系和事件流的面试题,而是深入了解框架的一块敲门砖。

啥?你问我10行代码能搞定的事,为什么要写这么多乱七八糟看不懂的东西?来来来,甲方爸爸再给你加几个功能需求试试?复制粘贴到你哭,这时候你是不是又要自嘲自己是搬砖工了?

表驱动编程

说到代码重复,就要说说表驱动编程。表驱动编程就像封装函数,函数参数(包括)与存在映射关系的对象一一对应,来减少大量重复代码。
再拿上面的例子来说,加上减、乘、除的功能:

events: {
  'click #add': 'add',
  'click #sub': 'sub',
  'click #mul': 'mul',
  'click #div': 'div',
},
add(){
  m.update({n: m.data.n + 1})
},
sub(){
  m.update({n: m.data.n - 1})
},
mul(){
  m.update({n: m.data.n * 2})
},
div(){
  m.update({n: m.data.n / 2})
}

再加上下面这波这样的操作,简直不要太爽,以后再加新的类似功能,你只用加4行代码就完事啦!是不是很爽~这样一来,就再也不用写一堆重复无用的烂代码了

for(let item in this.events) {
  const operate = this[this.events[item]]
  const spaceIndex = item.indexOf(' ')
  const eventName = item.slice(0, spaceIndex)
  const element = item.slice(spaceIndex+1)
  this.el.on(eventName, element, operate)
}

EventBus

上面的例子可以看到,View既要从M中取数据,还会根据用户操作来改变M中的数据,这中间少不了两个层之间的交互,以及监听,这就要EventBus来镇场子了。如何监听data的改变的呢?

Model:m.trigger('m:updated')
View:m.update({n: m.data.n + 1})

当data改变时,会调用m.update()方法,此方法里的m.tigger('m:updated')会有一个专门的机制来监听它,就是下面的this.on,监听到m:updated就立即从新渲染页面那一块的数据。

this.on('m:updated', ()=>{
  this.render(this.data)
})

在层层封装之下,底层都离不开EventBus的事件监听,因为数据是活的,会被传递。这里我们可以自己造一个事件,来满足不同条件下的监听需求。然后通过继承,让每个实例都有监听功能。

在jQuery中,有$on,$off,$trigger()来实现监听、解除监听、触发;

在Vue中,则是$on,$off,$emit来实现此功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值