首頁  >  文章  >  web前端  >  原生js實作MVVM框架的基本原理詳解

原生js實作MVVM框架的基本原理詳解

不言
不言原創
2018-09-01 17:35:232708瀏覽

這篇文章帶給大家的內容是關於原生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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn