이 글은 네이티브 JS에서 MVVM 프레임워크를 구현하는 기본 원칙에 대한 자세한 설명을 제공합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.
프런트엔드 페이지에서 Model은 순수 JS 객체로 표현되고, View는 디스플레이를 담당합니다.
그 것. Model 을 연결하고 View 는 ViewModel 입니다. ViewModel은 표시를 위해 Model 데이터를 View에 동기화하는 역할을 담당하며, View 수정 사항을 Model에 다시 동기화하는 역할도 담당합니다.
MVVM의 디자인 아이디어: 모델의 변경 사항에 주의를 기울이고 MVVM 프레임워크가 자동으로 DOM 상태를 업데이트하도록 하여 개발자가 DOM을 운영하는 번거로운 단계에서 해방됩니다.
MVVM의 개념을 이해한 후 네이티브 JS를 사용하여 MVVM 프레임워크를 구현했습니다.
MVVM 프레임워크를 구현하기 전에 몇 가지 기본 사용법을 살펴보겠습니다.
객체의 일반적인 선언, 속성 정의 및 수정# 🎜🎜 #
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
writable
和configurable
的区别是前者是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 //这里注释掉了,实际设置属性就是这样写的。 } }) } }
注意两点:
我们在声明let val = data[key]
时,不能用var
,因为这里需要对每个属性进行监控,用let
每次遍历都会创建一个新的val
,在进行赋值;如果用var
,只有第一次才是声明,后面都是对一次声明val
进行赋值,遍历结束后,得到的是最后一个属性,显然这不是我们需要的。
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
, 나중에 수정할 수 없음 #🎜🎜#configurable
이 false
로 설정되어 있으며, 해당 내부 속성을 삭제하려면 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...#🎜🎜#
enumerable
이 false
로 설정되면 내부 속성을 순회할 수 없습니다. 순회해야 하는 경우 열거 가능
을 설정하세요. 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#🎜🎜#두 가지 사항에 유의하세요. #🎜🎜#let val = data[key]에서는 각 속성을 여기에서 모니터링해야 하므로 <code>var
를 사용할 수 없습니다. let
를 사용하면 탐색할 때마다 새로운 val
가 생성됩니다. . >, 값 할당; var
를 사용하면 첫 번째 선언만 이루어지고 후속 선언인 val
에는 순회가 완료된 후 최종 결과가 지정됩니다. 속성을 얻었습니다. 분명히 이것은 우리에게 필요한 것이 아닙니다. #🎜🎜##🎜🎜#get
메서드에서 return
은 이전에 선언한 val
이며 변경할 수 없습니다. 여기에 data[key]
를 사용하면 오류가 보고됩니다. data.name
을 호출하는 것은 get
메서드를 호출하는 것을 의미하기 때문에 얻은 결과는 data.name
이고 get
)입니다. > 메소드가 호출되면 code> 메소드는 무한 루프가 되므로 data[key]
를 저장하고 이 변수를 반환하려면 변수가 필요합니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#관찰자 모드#🎜🎜##🎜🎜#일반적인 관전자 모드 적용 시나리오-WeChat 공개 계정#🎜🎜#不同的用户(我们把它叫做观察者:Observer)都可以订阅同一个公众号(我们把它叫做主体:Subject)
当订阅的公众号更新时(主体),用户都能收到通知(观察者)
用代码怎么实现呢?先看逻辑:
Subject 是构造函数,new Subject()创建一个主题对象,它维护订阅该主题的一个观察者数组数组(举例来说:Subject 是腾讯推出的公众号,new Subject() 是一个某个机构的公众号——新世相,它要维护订阅这个公众号的用户群体)
主题上有一些方法,如添加观察者addObserver
、删除观察者removeObserver
、通知观察者更新notify
(举例来说:新世相将用户分为两组,一组是忠粉就是 addObserver,一组是黑名单就是:removeObserver,它在忠粉组可以添加用户,可以在黑名单里拉黑一些杠精,如果有福利发放,它就会统治忠粉里的用户:notify)
Observer 是构造函数,new Observer() 创建一个观察者对象,该对象有一个update
方法(举例来说:Observer 是忠粉用户群体,new Observer() 是某个具体的用户——小王,他必须要打开流量才能收到新世相的福利推送:updata)
当调用notify
时实际上调用全部观察者observer
自身的update
方法(举例来说:当新世相推送福利时,它会自动帮忠粉组的用户打开流量,这比较极端,只是用来举例)
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...
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!