[JavaScript]面向对象编程浅析之XJB讲

面向对象编程浅析

关于面向对象编程(Object Oriented Programming),每个人都有关于自己的理解,我们来参考一下维基的定义:

维基百科

是不是觉得很拗口?反正我是这么觉得的...

因为他足够抽象,抽象到只有那种资深、拥有一定编程经验的程序员才能总结、归纳出来。

作为(或者说是着手)一篇技术文章,理应做到客观中立、条理清晰,但是以本人的功力,自认尚未做得到抽丝剥茧层层递进,所以我把文章名叫面向对象编程浅析之XJB讲(当然只是调侃哈哈,还是要认真写的),说说自己对于OOP的粗浅认知,以及通过方老师讲课的例子记录一下怎么从MVP过渡到OOP,让代码更加便于分析和理解...反正我一度对着一连串的this笑了,学习可真有乐趣啊不是吗?


从MVP说起

在上一篇文章中,我们把代码根据不同的功能划分为三个部分:

View代表视图

Model代表对数据的操作(存储和获取等)

Controller代表了View和Model两者之间的交互逻辑以及其他

//message.js

!function(){
    var view = $('section.messages')

    var model = {
        init : function(){
            var APP_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'
            var APP_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
            AV.init({appId: APP_ID,appKey: APP_KEY})
        },
        //获取数据
        fetch:function(){
            var query = new AV.Query('Message');
            return query.find() //Promise对象
        },

        // 保存数据
        save:function(name,content){
            var Message = AV.Object.extend('Message')
            var messages = new Message();
            return messages.save({
                "name":name,
                "content":content ,
            })
        },
    }

    var controller = {
        view : null,
        model: null,
        init : function(view,model){
            this.model = model
            this.view = view
            this.myForm = document.querySelector('#postMessage')
            this.model.init()
            this.loadMessages()
            this.bindEvents()
        },
        loadMessages : function () {
            this.model.fetch().then((messages) => {
                messages.forEach((item) => {
                    let span = $('<span></span>').text('发布于:'+ this.parseTime(item.createdAt))[0]
                    let li = $('<li></li>').text(item.attributes.name + ':' + item.attributes.content)
                    li.append(span)
                    $('#comments').append(li)
                });
            }).then(function (messages) {
                // 更新成功
            }, function (error) {
                // 异常处理
            });
        },
        bindEvents : function(){
            this.myForm.addEventListener('submit',(e) => {
                // 阻止传播,防止点击刷新
                e.preventDefault()

                this.saveMessages()
            })
        },
        saveMessages : function(){
            //获取输入框内的值
            let content = $('input[name="content"]').val()
            let name = $('input[name="name"]').val()
            this.model.save(name,content).then((object) => {
                let span = $('<span></span>').text('发布于:'+ this.parseTime(object.createdAt))[0]
                let li = $('<li></li>').text(object.attributes.name +':'+ object.attributes.content)
                li.append(span)
                $('#comments').append(li)
                document.querySelector('#postMessage input[name="content').value = ''
            },function(error){
                console.log(error)
            })
        },
        parseTime:function(d){
            const newDate = d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' '
                            + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
            return newDate;
        }
    }
    controller.init(view,model)
}.call()

通过MVC,代码各司其职,逻辑自洽,而且便于维护:

用户对View视图做出了操作,监听着View视图的controller接收到了View的变化通知,然后按需求去调用Model,Model向server发出请求;server返回响应给Model,Model返回数据给Controller,Controller根据接收到的数据更新View视图。

现在这个js文件的结构是这样的:

messages.js

他通过提交表单让数据库保存用户留言、获取用户留言并展示在页面上,负责了留言功能的实现。

但是,一旦我们把视角放大,我们就会发现,类似messages.js这样的文件在项目中有很多,比如a.js负责用户登录,b.js负责topbar样式切换,c.js负责轮播:

all.js

虽然,他们负责的功能都不同,但是!

我们经过观察发现,每一个js文件中都存在同样的结构,即MVC!既然结构相同,我们就有了优化的可能。


再说OOP

OOP的重要思想之一就是:

它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。

还是很拗口啊...

在参考了此问题的众多答案后,我总结了一个简单,易于理解的:

OOP也是一种代码组织形式,他把相同的代码逻辑和行为封装成一个对象,简称‘父’;每当有类似的代码和行为需要得到应用,则可以通过传入参数new一个’父‘对象返回一个新对象,简称’子‘,‘子’在各方面都有着父的影子,他可以做‘父’能做的事情,但是‘子’和‘父’又有一点不同,‘子’可以被赋予更多的能力。

that's it.

概念就是这么简单,但是要执行起来的话就有点难度了,为什么?因为他很抽象。

那么,就以message.js为例,从浅到略浅,练习一下怎么逐渐OOP。


View

既然每个js文件中都有view视图,我们就从view开始,分析view的结构和作用:

var view = $('section.messages')

view视图的作用是接受一个选择器,回文档中与指定选择器或选择器组匹配的第一个html元素。

我们完全可以指定一个函数,把选择器传入函数内,函数就会返回对应的元素;这样每当有同样的需要返回选择器元素的功能时,我们便可以调用这个函数,而不用另外写view了。

// 新建View.js,通过script标签中引入

window.View = function(selector){
    return this.document.querySelector(selector)
}

massage.js中的view就可以写成:

var view = View('section.message')

我们基本已经摸到点头脑了,我们把相同的功能(接受选择器,返回一个元素对象)封装成了一个函数,一旦我们需要在另一个文件中创建另一个view,则调用这个函数即可。


Model

接下来我们尝试把所有的model也给封装一个函数。

在此之前,先来明确一下model的职责:

1.初始化数据库

2.保存数据到数据库

3.从数据库获取数据

所以我们可以通过调用函数,传入参数,返回数据对象。

//新建一个Model.js文件,通过script标签引入
// Model.js
window.Model = function(options){

    var resourceName = options.resourceName

    return {
        //初始化
        init:function(){
            var APP_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'
            var APP_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
            AV.init({appId: APP_ID,appKey: APP_KEY})
        },

        // 获取数据
        fetch:function(){
            var query = new AV.Query(resourceName);
            return query.find() //Promise对象
        },

        // 通过传入需要保存的对象 保存数据
        save:function(object){
            var Message = AV.Object.extend('Message')
            var messages = new Message();
            return messages.save(object)
        }
    }
}

构建好Model函数之后,以后每次需要新的model,我们就调用Model,传入参数,done。

//message.js
var model = Model({resourceName:"Message"})

之前差不多20多行的有效代码,现在1行就搞定了,而且别的js文件也需要model的话,直接一句话调用,都不带含糊的。


Controller

controller我觉得是MVC里面最难搞的部分,因为他设计到的交互逻辑最多,需要处理的事情也最多最杂:

1.负责监听、更新view

2.负责调用、处理model返回的数据

3.anything else...

先和其他文件对比一下,init()一定是要有的,因为controller需要处理的事情很多,所以他加入的参数必然会变多且都不一样,所以我们让Controller返回的是一个容易操作的对象:

// 新建一个Controller.js,并通过script标签引入
// Controller.js

window.Controller = function(options){
    var object = {
        init:function(view,model){},
    }
    return object
}

由于不是每一个controller都需要(绑定事件的)的bindEvents()、(获取留言信息的)loadMessages()和(保存留言信息的)saveMessages()以及其他乱七八糟的函数,为了更方便地加载通过参数传进来的函数,所以我们可以使用遍历的方法,把参数遍历,然后加入到返回的object中:

window.Controller = function(options){
    var object = {
        init:function(view,model){},
    }
    for(let key in options){
        if(options[key] !== "init"){
            object[key] = options[key]
        }
    }
    return object
}

我们的view和model是由传进来的参数决定的,一开始view和model是null;然后初始化model:

// Controller.js

window.Controller = function(options){
    var object = {
        view = null
        model = null
        init:function(view,model){
            this.view = view
            this.model = model

            // 初始化model
            this.model.init()
        },
    }
    for(let key in options){
        if(options[key] !== "init"){
            object[key] = options[key]
        }
    }
    return object
}

两个init():

// Controller.js

window.Controller = function(options){

    var init = options.init // 1

    var object = {
        view = null
        model = null

        // 2
        init:function(view,model){
            this.view = view
            this.model = model

            // 初始化model
            this.model.init()
            init.call(this,view,moel)   // 1
        },
    }
    for(let key in options){
        if(options[key] !== "init"){
            object[key] = options[key]
        }
    }
    return object
}

本来我也有点懵逼,为什么会有两个 init,这不重复了吗?

非也,此 init 非彼 init,为了区分不同已在上面注释标记:

init1 指的是我们通过调用函数 Controller 的 options 参数传进去的 init,指的是每一个 controller 所不同的部分。即前面所说的‘子’与‘子’之间不同的地方。

init2 指的是调用函数 Controller 返回的对象 object 中的 init,是必须执行的 init。是每个‘子’一定会相同的地方。

他们的关系就是,每当执行 controller.init(view,model) 这句代码的时候,init2 必须执行;如果参数中没有传入 init1,init2 执行;如果参数中传入了 init1,则在执行完 init2 后,执行 init1。

接着执行 bindEvents():

// Controller.js

window.Controller = function(options){

    var init = options.init

    var object = {
        view:null,
        model:null,
        init:function(view,model){
            //this.view就是view:null,通过传入参数view后this.view就等于传入的view
            this.view = view
            this.model = model
            this.model.init()
            init.call(this,view,model)
            this.bindEvents.call(this)
            //this.bindEvents.call(this)
        },
    }
    for(let key in options){
        if(key !== "init"){
            object[key]=options[key]
        }
    }
    return object
}

我们的 Controller OOP 就完成了!

我们通过传参的方式将需要的函数都打包成一个对象,传给 Controller,而 Controller 会返回一个新的对象,这个对象里面包括了 init 和其他定制的函数,这个 init 会帮我们做任何一个 controller 都会做的事,再通过我们传进去的 init 做任何一个 controller 想单独做的事,done。

接着我们就改一下 messages.js 中的 controller :

var controller = Controller({
        init:function(view,controller){
            this.myForm = document.querySelector('#postMessage')
            this.loadMessages()
        },
        loadMessages : function () {
            this.model.fetch().then((messages) => {
                messages.forEach((item) => {
                    let span = $('<span></span>').text('发布于:'+ this.parseTime(item.createdAt))[0]
                    let li = $('<li></li>').text(item.attributes.name + ':' + item.attributes.content)
                    li.append(span)
                    $('#comments').append(li)
                });
            }).then(function (messages) {
            }, function (error) {
            });
        },
        bindEvents : function(){
            this.myForm.addEventListener('submit',(e) => {
                e.preventDefault()
                this.saveMessages()
            })
        },
        saveMessages : function(){
            let content = $('input[name="content"]').val()
            let name = $('input[name="name"]').val()
            this.model.save({"name":name,"content":content}).then((object) => {
                let span = $('<span></span>').text('发布于:'+ this.parseTime(object.createdAt))[0]
                let li = $('<li></li>').text(object.attributes.name +':'+ object.attributes.content)
                li.append(span)
                $('#comments').append(li)
                document.querySelector('#postMessage input[name="content').value = ''
            },function(error){
                console.log(error)
            })
        },
        parseTime:function(d){
            const newDate = d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' '
                            + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
            return newDate;
        }
    })
controller.init(view,model)

注意事项

this的问题:

1.message.js 中 controller.init(view,model)中的 this :controller 是调用函数 Controller() 返回的对象 object,所以 controller 的 this 是他本身,也就是 object 。

这句代码也就等同于 controller.init.call(controller,view,model)

2.Controller.js 中 init:function(view,model){...} 中的 init 的 this 就是 object 。

这句代码也就等同于 object.init:function(view,model){...}

3.Controller.js 中 init.call(this,view,model) 中的 this 是我们通过 controller.init.call(controller,view,model)传进去的 this ,也就是 object 。

done。

真是写得又臭又长...但是没办法,学习还是要总结归纳的,关于this的问题会另外总结一下。

原文地址:https://www.cnblogs.com/No-harm/p/9682384.html