从MVC到MVVM(为什么要用vue)

axios

功能类似于jQuery.ajax

axios.post()
axios.get()
axios.put()
axios.patch()
axios.delete()
  1. jQuery.ajax功能更多
  2. 除了ajax功能之外没有其他功能(更庄专注)

ajax操作使用axios,dom操作使用vue,从此可以不使用jquery

Mock

使用axios模拟后台请求与响应就是Mock,也有专门的Moc库例如:
http://mockjs.com/

从MVC到MVVM(为什么要用vue)

生成随机数据,拦截 Ajax 请求

使用axios和jQuery完成简单的前后台交互(请求与响应)

要求从后台获取数据,初始化书的数量。加减书的时候也发送请求与响应,同时更新后台数据。

从MVC到MVVM(为什么要用vue)

演示地址:
https://jsbin.com/jipewutagi/...

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>使用axios和jQuery完成简单的前后台交互(请求与响应)</title>
  
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div id='app'>
  <div>
    书的名称:《__name__》 <br>
  书的数量:<span id='number'>__number__</span>
  </div>
  <button id='addOne'>加一</button>
  <button id='minusOne'>减一</button>
  <button id='reset'>归零</button>
</div>
</body>
</html>
let book = {
  name:'JavaScriptBook',
  number:10,
  id:'1'
}
// 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
axios.interceptors.response.use(function(response){
  let {config: {url, method, data}} = response
  //   ES6语法,从response里的config拿出url method data,并声明
  if(url === '/book/1' && method === 'get'){//如果,就把初始book的数据响应过来
    response.data = book
  } else if(url === '/book/1' && method === 'put'){
    let dataObj = JSON.parse(data)//先把拿到的请求转化为对象
    Object.assign(book,dataObj)
    //     Object.assign这个函数的作用是局部更新对象,把接收到的请求(book)里面的number局部更新
    console.log(book)
    response.data = book ;//局部更新后,再次赋值给响应,在这里略过存储数据库,因为这只是假的后台
  }

  return response;
})
// -------------上面是假的后台-----------------
// 先声明好变量(有时候变量不能固定,比如更新了html的代码后,这个变量就是旧的,就得重新取)
let $app = $('#app')
// let $number = $('#number')
// let $addOne = $('#addOne')
// let $minusOne = $('#minusOne')
// let $reset = $("#reset")

// 请求初始值
axios.get('/book/1')
  .then(({data})=>{//获取响应成功后更新html的代码
  //   这里使用的是es6语法,实际上是let data = response.data
  let originalHtml = $app.html()
  let newHtml = originalHtml.replace('__name__',data.name).replace('__number__', data.number)
  $app.html(newHtml)
})

// 进行加一个的请求
$app.on('click', '#addOne', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})
//下面减一和重置同理
$app.on('click', '#minusOne', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})

$app.on('click', '#reset', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = 0
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})

事件委托:点击父元素,如果同时点到了子元素就把事件委托给他。
通过事件委托,监听app的点击事件,如果点的是委托的子元素,就执行监听的函数

上面的代码很乱

使用 MVC模式 改写上面的代码

上面的代码很乱。使用MVC模式重构代码,把代码分成视图,数据,控制数据和视图三块,分别用一个对象表示,下面是过程

添加model,分离控制数据的方法

演示代码
https://jsbin.com/ceyukirube/...

fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let $app = $('#app')

model.fetch(1).then(({data})=>{//这里把操作数据的方法写在了model里

  let originalHtml = $app.html()
  let newHtml = originalHtml.replace('__name__',data.name).replace('__number__', data.number)
  $app.html(newHtml)
})


$app.on('click', '#addOne', function(){
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }

  model.update(1,book).then(({data})=>{//这里把操作数据的方法写在了model里
    $('#number').text(data.number)
  })
})

$app.on('click', '#minusOne', function(){
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }

  model.update(1,book)
    .then(({data})=>{
    $('#number').text(data.number)
  })
})

$app.on('click', '#reset', function(){
  let newNumber = 0
  let book = {
    number:newNumber
  }

  model.update(1,book)
    .then(({data})=>{
    $('#number').text(data.number)
  })
})




function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

添加View,所有跟html相关的操作都给view来做

演示地址
https://jsbin.com/fakegurono/...

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>使用axios和jQuery完成简单的前后台交互(请求与响应)</title>
  
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div id='app'>
  
</div>
</body>
</html>
fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let view = {
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`,
  render(data){//渲染
    let newhtml = this.template.replace('__name__',data.name).replace('__number__', data.number)
    $(this.el).html(newhtml)
  }

}

let $app = $('#app')

model.fetch(1).then(({data})=>{//es6语法,response里的data
  //这里把操作数据的方法写在了model里
  view.render(data)
  //    或者view.render(model.data)因为上面model在操纵数据的时候,获取响应的时候,把data传给了model.data,所以response.data 和model.data一样,两个都可以用
})


$app.on('click', '#addOne', function(){
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }
  model.update(1,book).
  then(({data})=>{//这里把操作数据的方法写在了model里
    view.render(data)
  })
})

$app.on('click', '#minusOne', function(){
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }
  model.update(1,book)
    .then(({data})=>{
    view.render(data)
  })
})

$app.on('click', '#reset', function(){
  let newNumber = 0
  let book = {
    number:newNumber
  }
  model.update(1,book)
    .then(({data})=>{
    view.render(data)
  })
})



//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

代码开始变得条理清晰

添加Controller (操纵Model和View)

把操纵model和view的操作封装成controller 对象
演示地址:
https://jsbin.com/sezuquxuko/...

fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let view = {
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`,
  render(data){//渲染
    let newhtml = this.template.replace('__name__',data.name).replace('__number__', data.number)
    $(this.el).html(newhtml)
  }

}
let controller = {
  init:function(options){
    this.view = options.view
    this.model = options.model
    this.view.render(this.model.data)
    this.bindEvents()
    
    this.model.fetch(1).then(({data})=>{
      this.view.render(data)
    })
  },
  bindEvents:function(){
    //到这一层,this还指的是controller这个对象,但是到点击事件那一层,addOne函数里,this代表点击的那个元素(jQuery规定的)。
    //     所以要使用addOne.bind(this)把controller这一层的this绑定到addOne那更深入的一层去,使this同一为controller这个对象
    $(this.view.el).on('click', '#addOne', this.addOne.bind(this))
    $(this.view.el).on('click', '#minusOne',this.minusOne.bind(this) )
    $(this.view.el).on('click', '#reset', this.reset.bind(this))
  },
  addOne:function(){
    let newNumber = $('#number').text()-0+1
    let book = {number:newNumber}
    this.model.update(1,book).//这个this已经被bind为controller
    then(({data})=>{
      this.view.render(data)//这个this已经被bind为controller
    })
  },
  minusOne:function(){
    let newNumber = $('#number').text()-0-1
    let book = {
      number:newNumber
    }
    this.model.update(1,book)//这个this已经被bind为controller
      .then(({data})=>{
      this.view.render(data)
    })
  },
  reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      this.model.update(1,book)
        .then(({data})=>{
        this.view.render(data)
      })
    }
}

controller.init({model:model,view:view})//初始化,并把Model和View传进去


//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

把Model和View抽象成类

因为这个页面的Model和View只是这个页面特有的,假如下个页面不是这个View和Model,那么还需要重新重写一遍代码,所以要把把Model,View和controller抽象成类。这样每有新的页面中的一块html需要操作,就new一个对象即可。一般来说MVC做成一个库,然后去引用他就好了

先写构造函数,然后把公有属性写在prototype里,最后new就可以了。
演示地址:
https://jsbin.com/mifameqona/...

controller类暂时不写

fakeData()
// -------------上面是假的后台-----------------

//从这开始写MVC类
function Model(options){//这里面写特有的属性
  this.data = options.data
  this.resource = options.resource//把请求的地址也写成特有的属性
}
Model.prototype.fetch = function(id){//共有属性
   return axios.get(`/${this.resource}/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
}
Model.prototype.update = function(id,data){
  return axios.put(`/${this.resource}/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
}

function View(options){
  this.el = options.el;
  this.template = options.template;
}
View.prototype.render = function(data){
  var newhtml = this.template
  for(let key in data){
    newhtml = newhtml.replace(`__${key}__`,data[key])//用循环替换data里面的字符串
  }
  $(this.el).html(newhtml)
}
// --------上面是MVC类-----------
//使用MVC类新生成的对象
let bookModel = new Model({data:{name:'',number:0,id:''},resource:'book'})
let bookView = new View({
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`
})

let controller = {
  init:function(options){
    this.view = options.view
    this.model = options.model
    this.view.render(this.model.data)
    this.bindEvents()
    
    this.model.fetch(1).then(({data})=>{
      this.view.render(data)
    })
  },
  bindEvents:function(){
    //到这一层,this还指的是controller这个对象,但是到点击事件那一层,addOne函数里,this代表点击的那个元素(jQuery规定的)。
    //     所以要使用addOne.bind(this)把controller这一层的this绑定到addOne那更深入的一层去,使this同一为controller这个对象
    $(this.view.el).on('click', '#addOne', this.addOne.bind(this))
    $(this.view.el).on('click', '#minusOne',this.minusOne.bind(this) )
    $(this.view.el).on('click', '#reset', this.reset.bind(this))
  },
  addOne:function(){
    let newNumber = $('#number').text()-0+1
    let book = {number:newNumber}
    this.model.update(1,book).//这个this已经被bind为controller
    then(({data})=>{
      this.view.render(data)//这个this已经被bind为controller
    })
  },
  minusOne:function(){
    let newNumber = $('#number').text()-0-1
    let book = {
      number:newNumber
    }
    this.model.update(1,book)//这个this已经被bind为controller
      .then(({data})=>{
      this.view.render(data)
    })
  },
  reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      this.model.update(1,book)
        .then(({data})=>{
        this.view.render(data)
      })
    }
}
  
controller.init({model:bookModel,view:bookView})//初始化,并把Model和View传进去


//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

在前端开始慢慢发展的时候,前端程序员就是这样进行技术迭代的,上面就是MVC迭代的过程。这就是MVVM出现之前的MVC。

使用vue改写上面的代码

从上面的代码来看,view类的作用是:

  1. 有一个没有填充数据的HTML模板template
  2. model发送请求获取数据后,view把数据填充到模板里,然后渲染填充后的html到页面

VUE框架的作用是可以把MVC里的view类使用VUE代替。
注意:

  1. 需要直接传入data,传入data后vue对象就有了这个data的属性
  2. VUE会有自动的render机制,VUE会帮我们做渲染html的过程,那我们怎么更新(渲染)HTML呢?直接改data数据就好了双向绑定()
  3. template只能有一个根元素

从传统MVC转到VUE的MVC就是忘掉render,把data放到vue上面,要更新数据,就直接更新vue里面的data即可。

从MVC到MVVM(为什么要用vue)

把render变成了简单的赋值操作。而且这种渲染只更新你改变的那个值所在的节点,不会渲染全部模板。
vue第一个特点是data归他管,第二就是会精细得更新该渲染的地方。
但vue的野心不仅于此,vue可以让你做到不需要controller。因为controller最重要的功能绑定事件,vue有一种语法可以绑定事件。具体用法是在html属性里添加v-on:click="f",然后在methods 里写f函数即可。

代码
演示地址:
https://jsbin.com/bocecuxaya/...

主要代码:

fakeData()
// -------------上面是假的后台-----------------

//从这开始写MVC类
function Model(options){
  this.data = options.data
  this.resource = options.resource
}
Model.prototype.fetch = function(id){
   return axios.get(`/${this.resource}/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
}
Model.prototype.update = function(id,data){
  return axios.put(`/${this.resource}/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
}


// --------上面是Model类-----------

let bookModel = new Model({data:{name:'',number:0,id:''},resource:'book'})
let bookView = new Vue({//不用写view类了,直接用vue充当view类,需要传入data,
  el:'#app',
  data:{//data里的属性会转变为vue对象的属性
    book:{
      name:'',
      number:0,
      id:'1'
    },
    n:1//两个数据,一个book对象,一个n的值
  },
  //template只能有一个根元素
  template:`
<div>
  <div>
    书的名称:《{{book.name}}》 <br>
    书的数量:<span id='number'>{{book.number}}</span>
  </div><br><br>

  双向绑定:<br>
  输入需要加或者减的数:<input type='text' v-model='n'><br>
  n的值为<span>{{n}}</span><br><br><br>

  <button v-on:click='addOne'>加n</button>
  <button v-on:click='minusOne'>减n</button>
  <button v-on:click='reset'>归零</button>
</div>
`,
  created:function(){//在创造vue时执行的函数,进行首次渲染
    bookModel.fetch(1).then(({data})=>{
      //vue会直接同步渲染html,所以直接赋值给view.name和number就好了

      this.book = bookModel.data;//或者this.book = data;因为data是传回来的response,在model里,也把传回来的数据放到了model里
//       this.view.render(this.model.data)这句不需要了,因为修改vue数据后会自动渲染
    })
  },
  methods:{//绑定的事件的函数
    addOne:function(){
      let newNumber = this.book.number + (this.n-0)//+n 
      //直接获取内存里的number,因为内存和页面是统一的,不需要获取dom了
      let book = {number:newNumber}
      bookModel.update(1,book).//直接用声明的bookModel对象里面的update方法,因为没有controller了
      then(({data})=>{
        //       this.view.render(data)
        this.book = data//返回的数据直接赋值给book,即可渲染
      })
    },
    minusOne:function(){
      let newNumber = this.book.number - (this.n-0)//-n
      let book = {
        number:newNumber
      }
       bookModel.update(1,book)
        .then(({data})=>{
        //       this.view.render(data)
        this.book = data
      })
    },
    reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      bookModel.update(1,book)
        .then(({data})=>{
        //         this.view.render(data)
        this.book = data
      })
    }
  }
})




//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'我是初始名称JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

从MVC到MVVM(为什么要用vue)

但是vue不管model层的事
vue做的事就是让mvc里的v更智能,且能合并mvc的c

双向绑定

从MVC到MVVM(为什么要用vue)

  1. 单向绑定:从内存里数据的更新到渲染页面的更新
  2. 双向绑定:不单单是从内存到页面,页面上的input修改了,还会反过来映射内存,内存会同时修改(只能input实现,因为只有input可以更改内存)

渲染是一种单向绑定,只单向得改变html的值。

vue就是自动化的mvc,既MVVM

MVVM

通过以上的分析,我们发现,我们不需要去绑定事件,也不需要去render了,我需要做的就是取值和赋值

什么是MVVM:
https://juejin.im/entry/59996...

用vue做三个小东西

Vue 浮层例子:http://jsbin.com/nabugot/1/ed...
Vue 轮播例子:https://jsbin.com/kerabibico/...
Vue tab切换例子:http://jsbin.com/hijawuv/1/ed...

相关推荐