从MVC到MVVM(为什么要用vue)
axios
功能类似于jQuery.ajax
。
axios.post() axios.get() axios.put() axios.patch() axios.delete()
- 比
jQuery.ajax
功能更多 - 除了ajax功能之外没有其他功能(更庄专注)
ajax操作使用axios,dom操作使用vue,从此可以不使用jquery
Mock
使用axios模拟后台请求与响应就是Mock,也有专门的Moc库例如:
http://mockjs.com/
生成随机数据,拦截 Ajax 请求
使用axios和jQuery完成简单的前后台交互(请求与响应)
要求从后台获取数据,初始化书的数量。加减书的时候也发送请求与响应,同时更新后台数据。
演示地址:
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类的作用是:
- 有一个没有填充数据的HTML模板template
- model发送请求获取数据后,view把数据填充到模板里,然后渲染填充后的html到页面
VUE框架的作用是可以把MVC里的view类使用VUE代替。
注意:
- 需要直接传入data,传入data后vue对象就有了这个data的属性
- VUE会有自动的render机制,VUE会帮我们做渲染html的过程,那我们怎么更新(渲染)HTML呢?直接改data数据就好了双向绑定()
- template只能有一个根元素
从传统MVC转到VUE的MVC就是忘掉render,把data放到vue上面,要更新数据,就直接更新vue里面的data即可。
把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; }) }
但是vue不管model层的事
vue做的事就是让mvc里的v更智能,且能合并mvc的c
双向绑定
- 单向绑定:从内存里数据的更新到渲染页面的更新
- 双向绑定:不单单是从内存到页面,页面上的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...
相关推荐
结束数据方法的参数,该如何定义?-- 集合为自定义实体类中的结合属性,有几个实体类,改变下标就行了。<input id="add" type="button" value="新增visitor&quo