• 技术文章 >web前端 >js教程

    原生js实现MVVM框架的基本原理详解

    不言不言2018-09-01 17:35:23原创1365
    本篇文章给大家带来的内容是关于原生JS实现MVVM框架的基本原理详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

    在前端页面中,把 Model 用纯 JS 对象表示,View 负责显示,两者做到了最大化的分离

    把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 中显示出来,还负责把 View 的修改同步回 Model。

    MVVM 的设计思想:关注 Model 的变化,让 MVVM 框架去自动更新 DOM 的状态,从而把开发者从操作 DOM 的繁琐步骤中解脱出来。

    了解了 MVVM 思想后,自己用原生 JS 实现一个 MVVM 框架。

    实现 MVVM 框架前先来看几个基本用法:

    Object.defineProperty

    普通声明对象,定义和修改属性

    let obj = {}
    obj.name = 'zhangsan'
    obj.age = 20

    ObjectdefineProperty声明对象
    语法:

    Object.defineProperty(obj,prop,descriptor)

    obj:要处理的目标对象

    prop:要定义或修改的属性的名称

    descriptor:将被定义或修改的属性描述符

    let obj = {}
    Object.defineProperty(obj,'age',{
        value = 14,
    })

    咋一看有点画蛇添足,这不很鸡肋嘛

    别急,往下看

    描述符

    descriptor有两种形式:数据描述符和存储描述符,他们两个共有属性:

    configurable,是否可删除,默认为false,定义后无法修改

    enumerable,是否可遍历,默认为false,定以后无法修改

    共有属性

    configurable设置为false时,其内部属性无法用delete删除;如要删除,需要把configurable设置为true

    let obj = {}
    Object.defineProperty(obj,'age',{
        configurable:false,
        value:20,
    })
    delete obj.age         //false

    enumerable设置为false时,其内部属性无法遍历;如需遍历,要把enumerable设置为true

    let obj = {name:'zhangsan'}
    Object.defineProperty(obj,'age',{
        enumerable:false,
        value:20,
    })
    for(let key in obj){
        console.log(key)    //name
    }

    数据描述符

    value:该属性对应的值,默认为undefined
    writable:当且紧当为true时,value才能被赋值运算符改变。默认为false

    let obj = {}
    Object.defineProperty(obj,'age',{
        value:10,
        writable:false
    })
    obj.age = 11
    obj.age        //10

    writableconfigurable的区别是前者是value能否被修改,后者是value能否被删除。

    存储描述符

    get():一个给属性提供getter的方法,默认为undefined
    set():一个给属性提供setter的方法,默认为undefined

    let obj = {}
    let age
    Object.defineProperty(obj,'age',{
        get:function(){
            return age
        },
        set:function(newVal){
            age = newVal
        }
    })
    obj.age = 20
    obj.age        //20

    当我调用obj.age时,其实是在向obj对象要age这个属性,它会干嘛呢?它会调用obj.get()方法,它会找到全局变量age,得到undefined

    当我设置obj.age = 20时,它会调用obj.set()方法,将全局变量age设置为20

    此时在调用obj.age,得到20

    注意:数据描述符和存储描述符不能同时存在,否则会报错

    let obj = {}
    let age
    Object.defineProperty(obj,'age',{
        value:10,        //报错
        get:function(){
            return age
        },
        set:function(newVal){
            age = newVal
        }
    })

    数据拦截

    使用Object.defineProperty来实现数据拦截,从而实现数据监听。

    首先有一个对象

    let data = {
        name:'zhangsan',
        friends:[1,2,3,4]
    }

    下面写一个函数,实现对data对象的监听,就可以在内部做一些事情

    observe(data)

    换句话说,就是data内部的属性都被我们监控的,当调用属性时,就可以在上面做些手脚,使得返回的值变掉;当设置属性时,不给他设置。

    当然这样做很无聊,只是想说明,我们可以在内部做手脚,实现我们想要的结果。

    observe这个函数应该怎么写呢?

    function observe(data){
        if(!data || typeof data !== 'object')return //如果 data 不是对象,什么也不做,直接跳出,也就是说只对 对象 操作
        for(let key in data){    //遍历这个对象
            let val = data[key]    //得到这个对象的每一个`value`
            if(typeof val === 'object'){    //如果这个 value 依然是对象,用递归的方式继续调用,直到得到基本值的`value`
                observe(val)
            }
            Object.defineProperty(data,key,{    //定义对象
                configurable:true,    //可删除,原本的对象就能删除
                enumerable:true,    //可遍历,原本的对象就能遍历
                get:function(){
                    console.log('这是假的')    //调用属性时,会调用 get 方法,所以调用属性可以在 get 内部做手脚
                    //return val    //这里注释掉了,实际调用属性就是把值 return 出去
                },
                set:function(newVal){
                    console.log('我不给你设置。。。')    //设置属性时,会调用 set 方法,所以设置属性可以在 set 内部做手脚
                    //val = newVal    //这里注释掉了,实际设置属性就是这样写的。
                }
            })
        }
    }

    注意两点:

    1. 我们在声明let val = data[key]时,不能用var,因为这里需要对每个属性进行监控,用let每次遍历都会创建一个新的val,在进行赋值;如果用var,只有第一次才是声明,后面都是对一次声明val进行赋值,遍历结束后,得到的是最后一个属性,显然这不是我们需要的。

    2. get方法里,return就是前面声明的val,这里不能用data[key],会报错。因为调用data.name,就是调用get方法时,得到的结果是data.name,又继续调用get方法,就随变成死循环,所以这里需要用一个变量来存储data[key],并将这个变量返回出去。

    观察者模式

    一个典型的观察者模式应用场景——微信公众号

    1. 不同的用户(我们把它叫做观察者:Observer)都可以订阅同一个公众号(我们把它叫做主体:Subject)

    2. 当订阅的公众号更新时(主体),用户都能收到通知(观察者)

    用代码怎么实现呢?先看逻辑:

    Subject 是构造函数,new Subject()创建一个主题对象,它维护订阅该主题的一个观察者数组数组(举例来说:Subject 是腾讯推出的公众号,new Subject() 是一个某个机构的公众号——新世相,它要维护订阅这个公众号的用户群体)

    主题上有一些方法,如添加观察者addObserver、删除观察者removeObserver、通知观察者更新notify(举例来说:新世相将用户分为两组,一组是忠粉就是 addObserver,一组是黑名单就是:removeObserver,它在忠粉组可以添加用户,可以在黑名单里拉黑一些杠精,如果有福利发放,它就会统治忠粉里的用户:notify)

    Observer 是构造函数,new Observer() 创建一个观察者对象,该对象有一个update方法(举例来说:Observer 是忠粉用户群体,new Observer() 是某个具体的用户——小王,他必须要打开流量才能收到新世相的福利推送:updata)

    当调用notify时实际上调用全部观察者observer自身的update方法(举例来说:当新世相推送福利时,它会自动帮忠粉组的用户打开流量,这比较极端,只是用来举例)

    ES5 写法:

    function Subject(){
        this.observers = []
    }
    Subject.prototype.addObserver = function(observer){
        this.observers.push(observer)
    }
    Subject.prototype.removeObserver = function(observer){
        let index = this.observers.indexOf(observer)
        if(index > -1){
            this.observers.splice(index,1)
        }
    }
    Subject.prototype.notify = function(){
        this.observers.forEach(observer=>{
            observer.update()
        })
    }
    function Observer(name){
        this.name = name
        this.update = function(){
            console.log(name + ' update...')
        }
    }
    
    let subject = new Subject()    //创建主题
    let observer1 = new Observer('xiaowang')    //创建观察者1
    subject.addObserver(observer1)    //主题添加观察者1
    let observer2 = new Observer('xiaozhang')    //创建观察者2
    subject.addObserver(observer2)    //主题添加观察者2
    subject.notify()    //主题通知观察者
    
    /**** 输出 *****/
    hunger update...
    valley update...

    ES6 写法:

    class Subject{
        constructor(){
            this.observers = []
        }
        addObserver(observer){
            this.observers.push(observer)
        }
        removeObserver(observer){
            let index = this.observers.indexOf(observer)
            if(index > -1){
                this.observers.splice(index,1)
            }
        }
        notify(){
            this.observers.forEach(observer=>{
                observer.update()
            })
        }
    }
    class Observer{
        constructor(name){
            this.name = name
            this.update = function(){
                console.log(name + ' update...')
            }
        }
    }
    let subject = new Subject()    //创建主题
    let observer1 = new Observer('xiaowang')    //创建观察者1
    subject.addObserver(observer1)    //主题添加观察者1
    let observer2 = new Observer('xiaozhang')    //创建观察者2
    subject.addObserver(observer2)    //主题添加观察者2
    subject.notify()    //主题通知观察者
    
    /**** 输出 *****/
    hunger update...
    valley update...

    ES5 和 ES6 写法效果一样,ES5 的写法更好理解,ES6 只是个语法糖

    主题添加观察者的方法subject.addObserver(observer)很繁琐,直接给观察者下方权限,给他们增加添加进忠粉组的权限

    class Observer{
      constructor() {
        this.update = function() {
            console.log(name + ' update...')
        }
      }
      subscribeTo(subject) {    //只要用户订阅了主题就会自动添加进忠粉组
        subject.addObserver(this)    //这里的 this 是 Observer 的实例
      }
    }
    
    let subject = new Subject()
    let observer = new Observer('lisi')
    observer.subscribeTo(subject)  //观察者自己订阅忠粉分组
    subject.notify()
    
    /****** 输出 *******/
    lisi update...

    MVVM 框架的内部基本原理就是上面这些。

    相关推荐:

    js实现一个简单的MVVM框架示例分享

    PHP的MVC框架 深入解析_PHP教程

    以上就是原生js实现MVVM框架的基本原理详解的详细内容,更多请关注php中文网其它相关文章!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    专题推荐:javascript
    上一篇:教你自己创建一个Vue的UI组件库类似Element 下一篇:javascript原型是什么?javascript原型的详细解说
    线上培训班

    相关文章推荐

    • 浅谈JavaScript中anchor()方法的用法• vue,angular,avalon这三种MVVM框架优缺点_其它• JavaScript数据绑定实现一个简单的 MVVM 库_javascript技巧• MVVM模式中ViewModel和View、Model有什么区别?_javascript技巧• Vue.js 和 MVVM 的注意事项

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网