>  기사  >  백엔드 개발  >  MVC 및 MVVM의 간단한 구현 방법

MVC 및 MVVM의 간단한 구현 방법

小云云
小云云원래의
2018-02-28 15:40:043701검색

MVC는 애플리케이션을 데이터(모델), 프레젠테이션 계층(뷰), 사용자 상호 작용 계층의 세 부분으로 나누는 디자인 패턴입니다. 아래 사진과 함께 보면 세 사람의 관계를 더 잘 이해할 수 있습니다.
MVC 및 MVVM의 간단한 구현 방법
즉, 이벤트 발생은 프로세스입니다.

  1. 사용자가 애플리케이션과 상호작용합니다

  2. 컨트롤러의 이벤트 핸들러가 트리거됩니다

  3. 컨트롤러가 모델에 데이터를 요청하고, 뷰에 제공

  4. 뷰는 사용자에게 데이터를 표시합니다

모델: 애플리케이션의 모든 데이터 개체를 저장하는 데 사용됩니다. 모델은 뷰와 컨트롤러의 세부 사항을 알 필요가 없습니다. 모델은 데이터와 해당 데이터와 직접 관련된 논리만 포함하면 됩니다. 모델과 관련되지 않은 모든 이벤트 처리 코드, 뷰 템플릿 및 로직은 모델에서 격리되어야 합니다.
View: 뷰 레이어가 사용자에게 표시되고 사용자는 이와 상호 작용합니다. JavaScript 애플리케이션에서 뷰는 대부분 html, css 및 JavaScript 템플릿으로 구성됩니다. 뷰에는 템플릿의 간단한 조건문 이외의 논리가 포함되어서는 안 됩니다. 실제로 모델과 유사하게 뷰도 애플리케이션의 다른 부분과 분리되어야 합니다.
컨트롤러: 컨트롤러는 모델과 뷰 사이의 링크입니다. 컨트롤러는 뷰에서 이벤트와 입력을 받아 이를 처리하고 그에 따라 뷰를 업데이트합니다. 페이지가 로드되면 컨트롤러는 양식 제출 및 버튼 클릭 수신 대기와 같은 이벤트 리스너를 뷰에 추가합니다. 그런 다음 사용자가 애플리케이션과 상호 작용하면 컨트롤러의 이벤트 트리거가 작동하기 시작합니다.
예를 들어 초기 JavaScript 프레임워크 백본은 MVC 패턴을 채택했습니다.

위의 예는 너무 공허한 것 같습니다. 실제 사례를 살펴보겠습니다.
1. 사용자가 새 채팅 메시지를 제출합니다.
2. 컨트롤러의 이벤트 핸들러가 트리거됩니다.
3. 4. 그런 다음 컨트롤러는 뷰를 업데이트합니다
5. 사용자는 채팅 창에서 새로운 채팅 정보를 봅니다
실제 예를 들어 MVC를 더 깊이 이해하기 위해 코드를 사용합니다. MVC의

Model

M은 모델을 나타내며, 데이터 연산 및 동작과 관련된 로직을 모델에 넣어야 합니다. 예를 들어 Model 객체를 생성하는 경우 모든 데이터 작업은 이 네임스페이스에 배치되어야 합니다. 다음은 간단한 코드입니다. 먼저 새 모델을 생성하고 인스턴스

var Model = {
    create: function() {
        this.records = {}
        var object = Object.create(this)
        object.prototype = Object.create(this.prototype)
        return object
    }
}
create를 사용하여 모델로 프로토타입된 객체를 생성한 다음 검색 및 저장을 포함한 데이터 작업을 포함한 몇 가지 기능이 있습니다.

var Model = {
    /*---代码片段--*/
    find: function () {
        return this.records[this.id]
    },
    save: function () {
        this.records[this.id] = this 
    }
}
이를 아래에서 사용할 수 있습니다. 모델:

user = Model.create()
user.id = 1
user.save()
asset = Model.create()
asset.id = 2
asset.save()
Model.find(1)
=> {id:1}
우리가 이 물건을 발견한 것을 보실 수 있습니다. 데이터 부분인 모델을 완성했습니다.

Control

mvc의 컨트롤러에 대해 이야기해 보겠습니다. 페이지가 로드되면 컨트롤러는 이벤트 핸들러를 뷰에 바인딩하고 콜백을 적절하게 처리하며 필요에 따라 모델과 인터페이스합니다. 다음은 컨트롤러의 간단한 예입니다.

var ToggleView = {
    init: function (view) {
        this.view = $(view)
        this.view.mouseover(this.toggleClass, true)
        this.view.mouseout(this.toggleClass, false)
    },
    this.toggleClass: function () {
        this.view.toggleClass('over', e.data)
    }
}
이러한 방식으로 마우스를 요소로 이동하면 over 클래스가 추가되고 마우스를 제거하면 over 클래스가 추가됩니다. 클래스가 제거됩니다. 그런 다음

    ex:
        .over {color: red}
        p{color: black}

这样控制器就和视图建立起了连接。在MVC中有一个特性就是一个控制器控制一个视图,随着项目体积的增大,就需要一个状态机用于管理这些控制器。先来创建一个状态机
var StateMachine = function() {}
SateMachine.add = function (controller) {
    this.bind('change', function (e, current) {
        if (controller == current) {
            controller.activate()
        } else {
            controller.deactivate()
        }
    })

    controller.active = function () {
        this.trigger('change', controller)
    }
}
// 创建两个控制器
var con1 = {
    activate: funtion() {
        $('#con1').addClass('active')
    },
    deactivate: function () {
        $('#con1').removeClass('active')
    }
}

var con2 = {
    activate: funtion() {
        $('#con2').addClass('active')
    },
    deactivate: function () {
        $('#con2').removeClass('active')
    }
}

// 创建状态机,添加状态
var sm = new StateMachine
sm.add(con1)
sm.add(con2)

// 激活第一个状态
con1.active()
와 같은 몇 가지 간단한 스타일을 추가하여 간단한 컨트롤러 관리를 구현하고 마지막으로 몇 가지 CSS 스타일을 추가합니다.

#con1, #con2 { display: none }
#con2.active, #con2.active { display: block }
con1이 활성화되면 스타일이 변경됩니다. 즉 뷰가 변경됩니다.

컨트롤러에 대한 내용은 여기까지입니다. MVC의 뷰(View) 부분을 살펴보겠습니다. 뷰

View

뷰는 사용자에게 시각적인 표현을 제공하고 사용자와 상호 작용하는 애플리케이션의 인터페이스입니다. JavaScript에서 뷰는 로직이 없는 HTML 조각으로, 애플리케이션 컨트롤러에 의해 관리되며, 뷰는 이벤트 콜백과 내장된 데이터를 처리합니다. 간단히 말해서, javaScript로 HTML 코드를 작성한 다음 HTML 조각을 HTML 페이지에 삽입하는 것입니다. 다음은 두 가지 방법입니다.

동적 렌더링 보기

document.createElement를 사용하여 DOM 요소를 만들고 해당 내용을 설정한 다음 추가합니다. 페이지 예를 들어,

var views = document.getElementById('views')
views.innerHTML = '' // 요소가 지워졌습니다
var wapper = document.createElement('p')
wrapper.innerText = '뷰에 추가 '
views .appendChild(wrapper)
이렇게 하면 createElement로 요소를 생성한 후 HTML 페이지에 추가하는 작업이 완료됩니다.

템플릿

이전 백엔드 개발 경험이 있다면 템플릿에 익숙해야 합니다. 예를 들어 ejs는 nodejs에서 흔히 사용됩니다. 다음은 ejs의 작은 예입니다. ejs가 javascript를 HTML

str = '

<%= title %>

'
ejs.render(str, {

title: 'ejs'

});
那么这个渲染后的结果就是

ejs


当然实际中ejs的功能更强大,我们甚至可以在其中加入函数,模板语言是不是觉得跟vue,React的书写方式特别像,我也觉得像。那么view的作用就显而易见了,就是将HTML和javaScript连接起来。剩下一个问题就是在mvc原理图我们看到了视图和模型之间的关系,当模型更改的时候,视图也会跟着更新。那么视图和模型就需要进行绑定,它意味着当记录发生改变时,你的控制器不需要处理视图的更新,因为这些更新是在后台自动完成的。为了将javaScript对象和视图绑定在一起,我们需要设置一个回调函数,当对象的属性发生改变时发送一个更新视图的通知。下面是值发生变化的时候调用的回调函数,当然现在我们可以使用更简单的set,get进行数据的监听,这在我们后面的MVVM将会讲到。

var addChange = function (ob) {
    ob.change = function (callback) {
        if (callback) {
            if (!this._change) this._change = {}
            this._change.push(callback)
        } else {
            if (!this._change) return 
            for (var i = this._change.length - 1; i >= 0; i--) {
                this._change[i].apply(this)
            }
        }
    }
}

我们来看看一个实际的例子

var addChange = function (ob) {
    ob.change = function (callback) {
        if (callback) {
            if (!this._change) this._change = {}
            this._change.push(callback)
        } else {
            if (!this._change) return 
            for (var i = this._change.length - 1; i >= 0; i--) {
                this._change[i].apply(this)
            }
        }
    }
}

var object = {}
object.name = 'Foo'

addChange(object)
object.change(function () {
    console.log('Changed!', this)
    // 更新视图的代码
})
obejct.change()
object.name = 'Bar'
object.change()

这样就实现了执行和触发change事件了。
我相信大家对MVC有了比较深刻的理解,下面来学习MVVM模式。

MVVM

如今主流的web框架基本都采用的是MVVM模式,为什么放弃了MVC模式,转而投向了MVVM模式呢。在之前的MVC中我们提到一个控制器对应一个视图,控制器用状态机进行管理,这里就存在一个问题,如果项目足够大的时候,状态机的代码量就变得非常臃肿,难以维护。还有一个就是性能问题,在MVC中我们大量的操作了DOM,而大量操作DOM会让页面渲染性能降低,加载速度变慢,影响用户体验。最后就是当Model频繁变化的时候,开发者就主动更新View,那么数据的维护就变得困难。世界是懒人创造的,为了减小工作量,节约时间,一个更适合前端开发的架构模式就显得非常重要。这时候MVVM模式在前端中的应用就应运而生。
MVVM让用户界面和逻辑分离更加清晰。下面是MVVM的示意图,可以看到它由Model、ViewModel、View这三个部分组成。
MVC 및 MVVM의 간단한 구현 방법
下面分别来讲讲他们的作用

View

View是作为视图模板,用于定义结构、布局。它自己不处理数据,只是将ViewModel中的数据展现出来。此外为了和ViewModel产生关联,那么还需要做的就是数据绑定的声明,指令的声明,事件绑定的声明。这在当今流行的MVVM开发框架中体现的淋淋尽致。在示例图中,我们可以看到ViewModel和View之间是双向绑定,意思就是说ViewModel的变化能够反映到View中,View的变化也能够改变ViewModel的数据值。那如何实现双向绑定呢,例如有这个input元素:

<input type=&#39;text&#39; yg-model=&#39;message&#39;>

随着用户在Input中输入值的变化,在ViewModel中的message也会发生改变,这样就实现了View到ViewModel的单向数据绑定。下面是一些思路:

  1. 扫描看哪些节点有yg-xxx属性

  2. 自动给这些节点加上onchange这种事件

  3. 更新ViewModel中的数据,例如ViewModel.message = xx.innerText

那么ViewModel到View的绑定可以是下面例子:

<p yg-text=&#39;message&#39;></p>

渲染后p中显示的值就是ViewModel中的message变量值。下面是一些思路:

  1. 首先注册ViewModel

  2. 扫描整个DOM Tree 看哪些节点有yg-xxx这中属性

  3. 记录这些被单向绑定的DOM节点和ViewModel之间的隐射关系

  4. 使用innerText,innerHTML = ViewModel.message进行赋值

ViewModel

ViewModel起着连接View和Model的作用,同时用于处理View中的逻辑。在MVC框架中,视图模型通过调用模型中的方法与模型进行交互,然而在MVVM中View和Model并没有直接的关系,在MVVM中,ViewModel从Model获取数据,然后应用到View中。相对MVC的众多的控制器,很明显这种模式更能够轻松管理数据,不至于这么混乱。还有的就是处理View中的事件,例如用户在点击某个按钮的时候,这个行动就会触发ViewModel的行为,进行相应的操作。行为就可能包括更改Model,重新渲染View。

Model

Model 层,对应数据层的域模型,它主要做域模型的同步。通过 Ajax/fetch 等 API 完成客户端和服务端业务 Model 的同步。在层间关系里,它主要用于抽象出 ViewModel 中视图的 Model。

MVVM简单实现

实现效果:

<p id="mvvm">
    <input type="text" v-model="message">
    <p>{{message}}</p>
    <button v-click=&#39;changeMessage&#39;></button>
</p>
<script type="">
    const vm = new MVVM({
        el: '#mvvm',
        methods: {
            changeMessage: function () {
                this.message = 'message has change'
            }
        },
        data: {
            message: 'this is old message'
        }
    })
</script>

这里为了简单,借鉴了Vue的一些方法

Observer

MVVM为我们省去了手动更新视图的步骤,一旦值发生变化,视图就重新渲染,那么就需要对数据的改变就行检测。例如有这么一个例子:

hero = {
    name: 'A'
}

这时候但我们访问hero.name 的时候,就会打印出一些信息:

hero.name 
// I'm A

当我们对hero.name 进行更改的时候,也会打印出一些信息:

hero.name = 'B'
// the name has change

这样我们是不是就实现了数据的观测了呢。
在Angular中实现数据的观测使用的是脏检查,就是在用户进行可能改变ViewModel的操作的时候,对比以前老的ViewModel然后做出改变。
而在Vue中,采取的是数据劫持,就是当数据获取或者设置的时候,会触发Object.defineProperty()。
这里我们采取的是Vue数据观测的方法,简单一些。下面是具体的代码

function observer (obj) {
    let keys = Object.keys(obj)
    if (typeof obj === 'object' && !Array.isArray(obj)) {
        keys.forEach(key => {
            defineReactive(obj, key, obj[key])
        })    
    }
}

function defineReactive (obj, key, val) {
    observer(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            console.log('I am A')
            return val
        },
        set: function (newval) {
            console.log('the name has change')
            observer(val)
            val = newval
        }
    }) 
}

把hero带入observe方法中,结果正如先前预料的一样的结果。这样数据的检测也就实现了,然后在通知订阅者。如何通知订阅者呢,我们需要实现一个消息订阅器,维护一个数组用来收集订阅者,数据变动触发notify(),然后订阅者触发update()方法,改善后的代码长这样:

function defineReactive (obj) {
    dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            console.log('I am A')
            Dep.target || dep.depend()
            return val
        },
        set: function (newval) {
            console.log('the name has change')
            dep.notify()
            observer(val)
            val = newval
        }
    }) 
}

var Dep = function Dep () {
    this.subs = []
}
Dep.prototype.notify = function(){
    var subs = this.subs.slice()
    for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
    }
}
Dep.prototype.addSub = function(sub){
    this.subs.push(sub)
}
Dep.prototype.depend = function(){
    if (Dep.target) {
        Dep.target.addDep(this)
    }
}

这跟Vue源码差不多,就完成了往订阅器里边添加订阅者,和通知订阅者。这里以前我看Vue源码的时候,困扰了很久的问题,就是在get方法中Dep是哪儿来的。这里说一下他是一个全局变量,添加target变量是用于向订阅器中添加订阅者。这里的订阅者是Wacther,Watcher就可以连接视图更新视图。下面是Watcher的一部分代码

Watcher.prototype.get = function(key){
    Dep.target = this
    this.value = obj[key] // 触发get从而向订阅器中添加订阅者
    Dep.target = null // 重置
};

Compile

在讲MVVM概念的时候,在View -> ViewModel的过程中有一个步骤就是在DOM tree中寻找哪个具有yg-xx的元素。这一节就是讲解析模板,让View和ViewModel连接起来。遍历DOM tree是非常消耗性能的,所以会先把节点el转换为文档碎片fragment进行解析编译操作。操作完成后,在将fragment添加到原来的真实DOM节点中。下面是它的代码

function Compile (el) {
    this.el = document.querySelector(el)
    this.fragment = this.init()
    this.compileElement()
}

Compile.prototype.init = function(){
    var fragment = document.createDocumentFragment(), chid 
    while (child.el.firstChild) {
        fragment.appendChild(child)
    }
    return fragment
};

Compile.prototype.compileElement = function(){
    fragment = this.fragment 
    me = this 
    var childNodes = el.childNodes 
    [].slice.call(childNodes).forEach(function (node) {
        var text = node.textContent 
        var reg = /\{\{(.*)\}\}/ // 获取{{}}中的值
        if (reg.test(text)) {
            me.compileText(node, RegExp.$1)
        }

        if (node.childNodes && node.childNodes.length) {
            me.compileElement(node)
        }
    })
}
Compile.prototype.compileText = function (node, vm, exp) {
    updateFn && updateFn(node, vm[exp])
    new Watcher(vm, exp, function (value, oldValue) {
        // 一旦属性值有变化,就会收到通知执行此更新函数,更新视图
        updateFn() && updateFn(node, val)
    })
}
// 更新视图
function updateFn (node, value) {
    node.textContent = value 
}

这样编译fragment就成功了,并且ViewModel中值的改变就能够引起View层的改变。接下来是Watcher的实现,get方法已经讲了,我们来看看其他的方法。

Watcher

Watcher是连接Observer和Compile之间的桥梁。可以看到在Observer中,往订阅器中添加了自己。dep.notice()发生的时候,调用了sub.update(),所以需要一个update()方法,值发生变化后,就能够触发Compile中的回调更新视图。下面是Watcher的具体实现

var Watcher = function Watcher (vm, exp, cb) {
    this.vm = vm 
    this.cb = cb 
    this.exp = exp 
    // 触发getter,向订阅器中添加自己
    this.value = this.get()
}

Watcher.prototype = {
    update: function () {
        this.run()
    },
    addDep: function (dep) {
        dep.addSub(this)
    },
    run: function () {
        var value = this.get()
        var oldVal = this.value 
        if (value !== oldValue) {
            this.value = value 
            this.cb.call(this.vm, value, oldValue) // 执行Compile中的回调
        }
    },
    get: function () {
        Dep.target = this 
        value = this.vm[exp] // 触发getter
        Dep.target = null 
        return value 
    }
}

在上面的代码中Watcher就起到了连接Observer和Compile的作用,值发生改变的时候通知Watcher,然后Watcher调用update方法,因为在Compile中定义的Watcher,所以值发生改变的时候,就会调用Watcher()中的回调,从而更新视图。最重要的部分也就完成了。在加一个MVVM的构造器就ok了。推荐一篇文章自己实现MVVM,这里边讲的更加详细。

相关推荐:

PHP学习MVC框架之路由

TP框架多层MVC用法分析

MVC框架是什么 这里为你解答_实用技巧


위 내용은 MVC 및 MVVM의 간단한 구현 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.