>  기사  >  웹 프론트엔드  >  네이티브 js에서 MVVM 프레임워크를 구현하는 기본 원칙에 대한 자세한 설명

네이티브 js에서 MVVM 프레임워크를 구현하는 기본 원칙에 대한 자세한 설명

不言
不言원래의
2018-09-01 17:35:232683검색

이 글은 네이티브 JS에서 MVVM 프레임워크를 구현하는 기본 원칙에 대한 자세한 설명을 제공합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

프런트엔드 페이지에서 Model은 순수 JS 객체로 표현되고, View는 디스플레이를 담당합니다.

그 것. Model 을 연결하고 View 는 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...

얼핏 보면 좀 불필요한 것 같지만, 쓸모가 없지 않나요? ?

#🎜🎜#긴급하지 마세요. #🎜🎜#

설명자

#🎜🎜#설명자에는 데이터 설명자와 저장소 설명자의 두 가지 형식이 있습니다. 둘 다 속성을 공유합니다: # 🎜🎜##🎜🎜#구성 가능, 삭제 가능 여부, 기본값은 false, 정의 후에는 수정할 수 없음#🎜🎜# #🎜🎜#열거 가능, 통과 가능 여부, 기본값은 false, 나중에 수정할 수 없음 #🎜🎜#

공유 속성

# 🎜🎜#configurablefalse로 설정되어 있으며, 해당 내부 속성을 삭제하려면 delete를 사용하여 삭제할 수 없습니다. true구성 가능합니다. #🎜🎜#
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로 설정되면 내부 속성을 순회할 수 없습니다. 순회해야 하는 경우 열거 가능을 설정하세요. code>가 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...

데이터 설명자

#🎜🎜#: 이 속성에 해당하는 값, 기본값은 정의되지 않음. <br><code>쓰기 가능: true인 경우 은 할당 연산자에 의해 변경될 수 있습니다. 기본값은 false입니다. #🎜🎜#rrreee#🎜🎜#쓰기 가능구성 가능의 차이점은 전자는 을 수정할 수 있는지 여부이고 후자는 을 삭제할 수 있습니다. #🎜🎜#

저장소 설명자

#🎜🎜#get(): 속성에 대해 getter를 제공하는 메서드, 기본값은 정의되지 않음 .
set(): 속성에 대한 setter를 제공하는 메서드입니다. 기본값은 정의되지 않음입니다. #🎜🎜#rrreee#🎜🎜#obj.age를 호출하면 실제로 age 속성에 대해 obj 개체를 요청하는 것입니다. 그럴까요? obj.get() 메서드를 호출하여 전역 변수 age를 찾아 undefine을 가져옵니다. #🎜🎜##🎜🎜#obj.age = 20을 설정하면 obj.set() 메서드가 호출되고 전역 변수 age가 설정됩니다. 20으로 설정되었습니다. #🎜🎜##🎜🎜#이때 obj.age를 호출하면 20을 얻습니다. #🎜🎜##🎜🎜#참고: 데이터 설명자와 저장소 설명자는 동시에 존재할 수 없습니다. 그렇지 않으면 오류가 보고됩니다. #🎜🎜#rrreee#🎜🎜#데이터 차단#🎜🎜# #🎜🎜# Object.defineProperty를 사용하여 데이터 가로채기와 데이터 모니터링을 구현하세요. #🎜🎜##🎜🎜#먼저 객체가 있습니다#🎜🎜#rrreee#🎜🎜#아래에 data 객체를 모니터링하는 함수를 작성하고 내부적으로 뭔가를 할 수 있습니다#🎜🎜 #rrreee #🎜🎜#즉, data 내부의 속성은 모두 우리가 모니터링합니다. 속성을 호출할 때 속성을 설정할 때 반환된 값을 변경하기 위해 몇 가지 트릭을 수행할 수 있습니다. 그것. #🎜🎜##🎜🎜#물론 이렇게 하는 것이 지루하지만, 우리가 원하는 결과를 얻기 위해 내부적으로도 할 수 있다는 것을 보여주고 싶을 뿐입니다. #🎜🎜##🎜🎜#그럼 observe 함수는 어떻게 작성해야 할까요? #🎜🎜#rrreee#🎜🎜#두 가지 사항에 유의하세요. #🎜🎜#
  1. #🎜🎜#let val = data[key]에서는 각 속성을 여기에서 모니터링해야 하므로 <code>var를 사용할 수 없습니다. let를 사용하면 탐색할 때마다 새로운 val가 생성됩니다. . >, 값 할당; var를 사용하면 첫 번째 선언만 이루어지고 후속 선언인 val에는 순회가 완료된 후 최종 결과가 지정됩니다. 속성을 얻었습니다. 분명히 이것은 우리에게 필요한 것이 아닙니다. #🎜🎜##🎜🎜#
  2. #🎜🎜#get 메서드에서 return은 이전에 선언한 val이며 변경할 수 없습니다. 여기에 data[key]를 사용하면 오류가 보고됩니다. 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教程

위 내용은 네이티브 js에서 MVVM 프레임워크를 구현하는 기본 원칙에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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