按照《前端MVC与模块化》中的方法把各模块代码分成MVC三层,仍然存在一些问题,如公共方法无法复用、每个模块MVC中都有大量重复代码:
- 此时可以考虑把Model、View、Controller抽象成公共模块(类),每编写一个模块通过这个模板实现;
- 在ES5中(没有class关键字)可通过调用全局函数返回对象的方式实例化Model、View和Controller。
model.js
window.Model = Model(options){
this.data = options.data
this.resource = options.resource
}
window.Model.prototype.fetch = function(id){
return axios.get(`/${this.resource}s/${id}`).then((response) => {
this.data = response.data
return response
})
}
window.Model.prototype.update = function(data){
let id = this.data.id
return axios.put(`/${this.resource}s/${id}`, data).then((response) => {
this.data = response.data
return response
})
}
view.js
window.View = function ({el, template}){
this.el = el
this.template = template
}
window.View.prototype.render = function(data){
let html = this.template
for(let key in data){
html = html.replace(`__${key}__`, data[key])
}
$(this.el).html(html)
}
controller.js
window.Controller = function (options) {
let init = options.init
let object = {
init: function (view, model) {
this.view.render(this.model.data)
init.call(view, model)
options.bindEvents.call()
}
}
for (let key in options) { // 写入实现对象特有的方法
object[key] = options[key]
}
return object
}
实现一个功能模块:
!function () {
let model = new Model({
data: {
name: '',
number: 0,
id: ''
},
resource: 'Message'
})
let view = View({
el: '.messageBoard',
template: `
<div>
书名:《__name__》
数量:<span id=number>__number__</span>
</div>
<div>
<button id="addOne">加1</button>
<button id="minusOne">减1</button>
<button id="reset">归零</button>
</div>
`
})
let controller = Controller({
init: function(view, model) {
this.model.fetch(1).then(() => {
this.view.render(this.model.data)
})
},
bindEvents: function() {
$(this.view.el).on('click', '#reset', this.reset.bind(this))
$(this.view.el).on('click', '#addOne', this.addOne.bind(this))
// ...
},
addOne: function() {
var oldNumber = $('#number').text()
var newNumber = oldNumber - 0 + 1
this.model.update({
number: newNumber
}).then(() => {
this.view.render(this.model.data)
})
},
reset() {
this.model.update({
number: 0
}).then(() => {
this.view.render(this.model.data)
})
},
})
controller.init({view: view, model: model})
}.call()
MVVM
使用Vue自动实现MVC:
MVVM
其中ViewModel实现了MVC中Controller的功能:
- 当View层DOM发生变化时可通过监听器通知Model层修改数据;
- 当Model层数据发生变化,可通过与View层DOM的绑定修改DOM元素。
html
<div id="app">
</div>
script
initData()
// MVC类(C封装在V内)
function Model(options) {
this.data = options.data
this.resource = options.resource
}
Model.prototype.fetch = function (id) {
return axios.get(`/${this.resource}s/${id}`).then((response) => {
this.data = response.data
return response
})
}
Model.prototype.update = function (data) {
let id = this.data.id
return axios.put(`/${this.resource}s/${id}`, data).then((response) => {
this.data = response.data
return response
})
}
// 对象
let model = new Model({
data: {
name: '', number: 0, id: ''
},
resource: 'book'
})
let view = new Vue({
el: '#app',
data: {
book: {
name: '未命名',
number: 0,
id: ''
},
n: 1
},
template: `
<div>
<div>
书名:《{{book.name}}》
数量:<span id=number>{{book.number}}</span>
</div>
<div>
<input v-model="n" /> N 的值是 {{n}}
</div>
<div>
<button v-on:click="addOne">加N</button>
<button v-on:click="minusOne">减N</button>
<button v-on:click="reset">归零</button>
</div>
</div>
`,
created() {
model.fetch(1).then(() => {
this.book = model.data
})
},
methods: {
addOne() {
model.update({
number: this.book.number + (this.n - 0)
}).then(() => {
this.view.book = this.model.data
})
},
minusOne() {
model.update({
number: this.book.number - (this.n - 0)
}).then(() => {
this.view.book = this.model.data
})
},
reset() {
model.update({
number: 0
}).then(() => {
this.view.book = this.model.data
})
},
}
})
function initData() {
let book = {
name: 'JavaScript 高级程序设计', number: 2, id: 1
}
axios.interceptors.response.use(function (response) {
let {
config: {method, url, data}
} = response
if (url === '/books/1' && method === 'get') {
response.data = book
}
else if (url === '/books/1' && method === 'put') {
data = JSON.parse(data)
Object.assign(book, data)
response.data = book
}
return response
})
}
this 关键字
- this是call函数的第一个参数;
- 函数调用
controller.init(view, model)
的本质是controller.init.call(controller, view, model)
,在init内部this指的就是controller; - 因此无法理解函数内部this的指向时,应改用
func.call(this)
的方式调用(如定义一个没有参数的函数,实际调用时却传入了参数,则在该函数内部的this即为调用时传入的第一个参数)。
function X() {
return object = {
name: 'object',
options: null,
f1(x) {
this.options = x // 此处x实际上是外部传入的第二个参数,即options
this.f2()
},
f2() {
this.options.f2.call(this) // 此处this是x,即'object'
}
}
}
var options = {
name: 'options',
f1(){},
f2(){
console.log(this) // 此处this是由外部传入,即'object'
}
}
var x = X()
x.f1(options) // x.f1.call(x, options)
call、apply与bind
- apply、call和bind都是来改变this的指向的,其第一个参数都是相同的this指向的对象;
- apply接收的第二个参数为数组,call和bind接收的是多个参数;
- apply与call会立即被执行,bind只是先改变this的指向(绑定)。
var s = {
a:1,
b:2,
add(name) {
console.log(this.a + this.b)
console.log(name)
}
}
var a = {
a: 2, b: 2,
}
var b = {
a: 4, b: 2,
}
var c = {
a: 5, b: 6,
}
s.add.call(a,"call")
s.add.apply(b,["apply"])
s.add.bind(c,"bind")() // 等价于s.add.bind(c)("bind")
new 关键字
- 我们往往会不经意地创建很多对象,这些对象中很多方法都是一样的,只需要各自引用同一个函数即可,没有必要重复创建;
- 对于对象的公有属性(如某兵种的攻击力)可以抽象出来,而对象私有部分(如姓名和id)则可以单独创建;
- 使用new关键字可以不用创建和返回临时对象(使用this就可以访问到),而不需要绑定原型(名字为prototype);同时定义constructor属性可以记录“临时对象是由哪个函数创建的”:
function Human(options) { // Human类的构造函数,传入对象必须包括name、city参数
this.name = options.name
this.city = options.city
}
Human.prototype = {
constructor: Human, // 记录通过new Human()创建的对象类型即为Human
species: function(){},
walk: function(){},
useTools: function(){}
}
var human = new Human({name:'ywh', city: 'gz'})
human.__proto__ // 对应的对象(即原型)具有species、walk和useTools这几个属性
human.__proto__.constructor === Human // 表示human的类型为Human
网友评论