美文网首页
前端 MVC、MVVM 与面向对象

前端 MVC、MVVM 与面向对象

作者: 千反田爱瑠爱好者 | 来源:发表于2018-08-19 16:47 被阅读52次

    按照《前端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
    

    相关文章

      网友评论

          本文标题:前端 MVC、MVVM 与面向对象

          本文链接:https://www.haomeiwen.com/subject/knwciftx.html