ホームページ  >  記事  >  ウェブフロントエンド  >  MVVM フレームワークをネイティブ JS で実装する基本原理の詳細な説明

MVVM フレームワークをネイティブ JS で実装する基本原理の詳細な説明

不言
不言オリジナル
2018-09-01 17:35:232707ブラウズ

この記事では、ネイティブ JS で MVVM フレームワークを実装するための基本原則について詳しく説明します。必要な方は参考にしていただければ幸いです。

フロントエンドページでは、モデルは純粋な JS オブジェクトで表され、ビューはその 2 つが分離されて最大化されます。

モデルとビューを関連付けるものは ViewModel です。 ViewModel は、表示のために Model データを View に同期する役割を果たします。また、View の変更を Model に同期して戻す役割も担います。

MVVM の設計思想: モデルの変更に注意を払い、MVVM フレームワークに DOM の状態を自動的に更新させることで、開発者は DOM を操作する面倒な手順から解放されます。

MVVMの考え方を理解した後、ネイティブJSを使用してMVVMフレームワークを実装しました。

MVVM フレームワークを実装する前に、いくつかの基本的な使用法を見てみましょう:

Object.defineProperty

一般的にオブジェクトを宣言し、プロパティを定義および変更します

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

ObjectdefineProperty を使用してオブジェクトを宣言します
構文: 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]

    Object.defineProperty(obj,prop,descriptor)
obj: 処理対象のターゲット オブジェクト

prop:定義または変更された属性の名前

descriptor: 定義または変更される属性記述子🎜
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...
🎜一見すると、少し余分ですが、役に立たないのではないでしょうか。続きを読む🎜

記述子

🎜記述子 にはデータ記述子とストレージ記述子の 2 つの形式があります。どちらも属性を共有します: 🎜🎜configurable (可能かどうか)。削除可能、デフォルトは false、定義後に変更できません🎜🎜enumerable、走査可能かどうか、デフォルトは false、後で変更することはできません🎜

共有属性 h4>🎜 configurablefalse に設定されている場合、その内部プロパティは delete を使用して削除できませんcode>; 削除したい場合は、<code>configurabletrue に設定する必要があります。 🎜
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...
🎜 enumerablefalse に設定されている場合、その内部プロパティをトラバースする必要がある場合は、enumerable に設定します。 true🎜
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...

データ記述子

🎜value: この属性に対応する値。デフォルトは unknown です。
writable: true の場合、value は代入演算子によって変更できます。デフォルトは false です。 🎜rrreee🎜 writableconfigurable の違いは、前者は value を変更できるかどうか、後者は value を変更できるかどうかです。 は変更・削除可能です。 🎜

ストレージ記述子

🎜get(): プロパティの getter を提供するメソッド。デフォルトは unknown です。 。
set(): プロパティの setter を提供するメソッド。デフォルトは unknown です。 🎜rrreee🎜 obj.age を呼び出すと、実際には obj オブジェクトに age 属性を問い合わせることになります。 obj.get() メソッドを呼び出し、グローバル変数 age を見つけて unknown を取得します。 🎜🎜obj.age = 20 を設定すると、obj.set() メソッドが呼び出され、グローバル変数 age が に設定されます。 >20。 🎜🎜この時点で、obj.age を呼び出すと、20 が返されます。 🎜🎜注: データ記述子とストレージ記述子は同時に存在できません。そうしないとエラーが報告されます 🎜rrreee🎜データインターセプト🎜🎜データを実装するには Object.defineProperty を使用してくださいデータ監視を実現するための傍受。 🎜🎜最初にオブジェクトがあります🎜rrreee🎜 data オブジェクトを監視するための関数を以下に記述します。その後、内部でいくつかの処理を行うことができます🎜rrreee🎜 つまり、それは dataコード > 内部属性はすべて私たちによって監視されます。属性を呼び出すときに、属性を設定するときに戻り値を変更するためのいくつかのトリックを実行できます。 🎜🎜もちろん、これを行うのは退屈ですが、内部的に操作して望む結果を達成できることを示したいだけです。 🎜🎜それでは、<code>observe 関数はどのように記述すべきでしょうか? 🎜rrreee🎜次の 2 つの点に注意してください: 🎜
  1. 🎜 let val = data[key] を宣言する場合、var では、ここで各属性を監視する必要があるため、<code>let を使用すると、各トラバーサルに対して新しい val が作成され、 を使用する場合は値が割り当てられます。 var は最初のみ宣言であり、その後の宣言 val はすべて値が割り当てられます。走査が完了すると、最後の属性が取得されますが、これは明らかに必要なものではありません。 。 🎜🎜
  2. 🎜 get メソッドでは、return は以前に宣言された val です。data[key] は使用できません。コード>を使用すると、エラーが報告されます。 <code>data.name を呼び出すことは、get メソッドを呼び出すことを意味するため、取得される結果は data.name であり、次に get です。 > メソッドが呼び出されます。 code> メソッドは無限ループになるため、data[key] を格納し、この変数を返す変数が必要です。 🎜🎜🎜🎜オブザーバーモード🎜🎜典型的なオブザーバーモードのアプリケーションシナリオ - WeChatパブリックアカウント🎜
    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教程

以上がMVVM フレームワークをネイティブ JS で実装する基本原理の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。