关于observers:技术分享-observer-资源水位介绍

作者:郭斌斌 爱可生 DBA 团队成员,负责我的项目日常问题解决及公司平台问题排查。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 OceanBase 集群界面会展现 Observer 的资源水位,明天简略理解一下资源水位的数值代表的含意以及关联参数 现有 test_1 集群,只有一个sys租户 Sys租户的资源配置: Cpu:2.5-5 Memory: 3G-3G Unit:1 集群的资源水位信息 以10.186.63.198为例,浅看一下cpu、内存、磁盘的含意以及相关联参数 cpu:2.5 / 17 核 2.5 代表 observer 上曾经调配给租户的 cpu 核数,该数值是 租户的 Min CPU 17 代表以后的observer可用的cpu数,通常observer会预留一部分cpu给操作系统,由参数cpu_reserved进行管制 验证:以后sys租户的 min cpu为 2.5,并且以后集群只有一个sys租户,因而资源水位cpu显示的是 2.5 10.186.63.198的服务器cpu 19个外围 集群的 cpu_reserved 因而除去操作系统应用cpu,以后 observer的cpu最多可用17核 内存:6.0/15.0GB 6.0GB 代表observer曾经调配进来的内存,该局部内存为租户内存 + 零碎外部内存(所有租户共享内存),零碎外部内存由 system_memory管制大小,租户内存由资源单元管制15GB 代表以后observer可用的内存总量,通常会预留一部分内存给操作系统应用,由 memory_limit / memory_limit_percentage管制。 验证:零碎外部内存大小 租户内存 零碎外部内存 + 租户内存=6G ...

April 11, 2023 · 1 min · jiezi

关于observers:OBS鉴权实现的宝典秘籍速拿

摘要:OBS提供了REST(Representational State Transfer)格调API,反对您通过HTTP/HTTPS申请调用。本文将带你理解OBS API鉴权实现的宝典秘籍。OBS提供了REST(Representational State Transfer)格调API,反对您通过HTTP/HTTPS申请调用。在调用OBS的API前,须要理解OBS的鉴权认证形式。 签名计算篇本文就将带您理解OBS的两种常见的鉴权形式——Header携带签名和URL携带签名。 1、在Header中携带签名计算官网链接:https://support.huaweicloud.c... 1.1、签名的计算原理和计算方法 原理图示 计算方法 1.结构申请字符串(StringToSign)。 申请字符串的构造方法如下: StringToSign = HTTP-Verb + "n" + Content-MD5 + "n" + Content-Type + "n" + Date + "n" + CanonicalizedHeaders + CanonicalizedResource 2.对第一步的后果进行UTF-8编码。 3.应用SK对第二步的后果进行HMAC-SHA1签名计算。 4.对第三步的后果进行Base64编码,失去签名。 签名如以下模式(28位长度的BASE64编码的字符串): JONydLd9zpf+Eu3IYiUjNmukHN0= 计算示例 例:须要获取桶”obs-test”下的对象log.conf的对象ACL,如何结构申请并计算签名? 1、首先明确StringToSign的各字段: 申请办法:GET; 申请MD5:空 Content-Type:空 申请工夫:Tue, 28 Jul 2020 06:29:47 GMT(即北京工夫2020年7月28日14:29:47) 自定义头域(CanonicalizedHeaders):空 规范化资源(CanonicalizedResource):/obs-test/log.conf?acl 2、结构申请字符串StringToSign如下: StringToSign = ‘’’GET Tue, 28 Jul 2020 06:29:47 GMT /obs-test/log.conf?acl’’’ 3、依据签名算法,将StringToSign进行HMAC-SHA1计算后进行BASE64编码取得签名后果:xYlcrwT9jSaCtY0OnBE01OBR+aA= 1.2、签名计算的实现形式 以Python计算签名代码为例,供参考: 1. import hashlib 2. import hmac 3. import binascii 4. from datetime import datetime 5. 6. # 验证信息 7. AK = '您的access_key_id' 8. SK = '您的secret_access_key_id' 9. 10. # 指定HTTP办法,可选GET/PUT/DELETE/POST/OPTIONS 11. httpMethod = "GET" 12. 13. # 指定申请的Header:Content-Type和Content-MD5 14. contentType = "" 15. conten**5 = "" 16. 17. # 应用datetime库生成工夫,如果须要自定义申请工夫请放弃格局统一 18. date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') 19. 20. # 填写canonicalizedHeaders 21. # canonicalizedHeaders = "x-obs-acl:public-read" 22. # canonicalizedHeaders = "x-obs-acl:public-readn"+'x-obs-storage-class:WARMn' 23. canonicalizedHeaders = "" 24. 25. # 填写CanonicalizedResource 26. # CanonicalizedResource = "/BucketName/ObjectName" 27. # CanonicalizedResource = "/BucketName/ObjectName?acl" 28. # CanonicalizedResource = "/"29. CanonicalizedResource = "/BucketName/" 30. 31. # 生成StringToSign 32. canonical_string = httpMethod + "n" + conten**5 + "n" + contentType + "n" + date + "n" + canonicalizedHeaders + CanonicalizedResource 33. 34. # 计算签名并进行BASE64编码 35. hashed = hmac.new(SK.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1) 36. encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8') 37. 38. # 打印StringToSign以便呈现问题时进行验证 39. print(canonical_string) 40. 41. # 打印签名 42. print(encode_canonical) C语言签名算法示例: ...

September 17, 2020 · 5 min · jiezi

vue响应式系统--observe、watcher、dep

Vue的响应式系统Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操作,提高了开发效率。vue 的响应式系统依赖于三个重要的类:Dep 类、Watcher 类、Observer 类,然后使用发布订阅模式的思想将他们揉合在一起(不了解发布订阅模式的可以看我之前的文章发布订阅模式与观察者模式)。ObserverObserve扮演的角色是发布者,他的主要作用是调用defineReactive函数,在defineReactive函数中使用Object.defineProperty 方法对对象的每一个子属性进行数据劫持/监听。部分代码展示defineReactive函数,Observe的核心,劫持数据,在setter中向Dep(调度中心)添加观察者,在getter中通知观察者更新。function defineReactive(obj, key, val, customSetter, shallow){ //监听属性key //关键点:在闭包中声明一个Dep实例,用于保存watcher实例 var dep = new Dep(); var getter = property && property.get; var setter = property && property.set; if(!getter && arguments.length === 2) { val = obj[key]; } //执行observe,监听属性key所代表的值val的子属性 var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { //获取值 var value = getter ? getter.call(obj) : val; //依赖收集:如果当前有活动的Dep.target(观察者–watcher实例) if(Dep.target) { //将dep放进当前观察者的deps中,同时,将该观察者放入dep中,等待变更通知 dep.depend(); if(childOb) { //为子属性进行依赖收集 //其实就是将同一个watcher观察者实例放进了两个dep中 //一个是正在本身闭包中的dep,另一个是子属性的dep childOb.dep.depend(); } } return value }, set: function reactiveSetter(newVal) { //获取value var value = getter ? getter.call(obj) : val; if(newVal === value || (newVal !== newVal && value !== value)) { return } if(setter) { setter.call(obj, newVal); } else { val = newVal; } //新的值需要重新进行observe,保证数据响应式 childOb = observe(newVal); //关键点:遍历dep.subs,通知所有的观察者 dep.notify(); } });}DepDep 扮演的角色是调度中心/订阅器,主要的作用就是收集观察者Watcher和通知观察者目标更新。每个属性拥有自己的消息订阅器dep,用于存放所有订阅了该属性的观察者对象,当数据发生改变时,会遍历观察者列表(dep.subs),通知所有的watch,让订阅者执行自己的update逻辑。部分代码展示Dep的设计比较简单,就是收集依赖,通知观察者//Dep构造函数var Dep = function Dep() { this.id = uid++; this.subs = [];};//向dep的观察者列表subs添加观察者Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub);};//从dep的观察者列表subs移除观察者Dep.prototype.removeSub = function removeSub(sub) { remove(this.subs, sub);};Dep.prototype.depend = function depend() { //依赖收集:如果当前有观察者,将该dep放进当前观察者的deps中 //同时,将当前观察者放入观察者列表subs中 if(Dep.target) { Dep.target.addDep(this); }};Dep.prototype.notify = function notify() { // 循环处理,运行每个观察者的update接口 var subs = this.subs.slice(); for(var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};//Dep.target是观察者,这是全局唯一的,因为在任何时候只有一个观察者被处理。Dep.target = null;//待处理的观察者队列var targetStack = [];function pushTarget(_target) { //如果当前有正在处理的观察者,将他压入待处理队列 if(Dep.target) { targetStack.push(Dep.target); } //将Dep.target指向需要处理的观察者 Dep.target = _target;}function popTarget() { //将Dep.target指向栈顶的观察者,并将他移除队列 Dep.target = targetStack.pop();}WatcherWatcher扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖(如计算属性computed,vue会把该属性所依赖数据的dep添加到自身的deps中),当被观察的值发生变化时,会接收到来自dep的通知,从而触发回调函数。,部分代码展示Watcher类的实现比较复杂,因为他的实例分为渲染 watcher(render-watcher)、计算属性 watcher(computed-watcher)、侦听器 watcher(normal-watcher)三种, 这三个实例分别是在三个函数中构建的:mountComponent 、initComputed和Vue.prototype.$watch。normal-watcher:我们在组件钩子函数watch 中定义的,都属于这种类型,即只要监听的属性改变了,都会触发定义好的回调函数,这类watch的expression是我们写的回调函数的字符串形式。computed-watcher:我们在组件钩子函数computed中定义的,都属于这种类型,每一个 computed 属性,最后都会生成一个对应的 watcher 对象,但是这类 watcher 有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。这类watch的expression是计算属性中的属性名。render-watcher:每一个组件都会有一个 render-watcher, 当 data/computed 中的属性改变的时候,会调用该 render-watcher 来更新组件的视图。这类watch的expression是 function () {vm._update(vm._render(), hydrating);}。除了功能上的区别,这三种 watcher 也有固定的执行顺序,分别是:computed-render -> normal-watcher -> render-watcher。这样安排是有原因的,这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-render 前面,就会导致页面更新的时候 computed 值为旧数据。这里我们只看其中一部分代码function Watcher(vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm; if(isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if(options) { this.deep = !!options.deep; //是否启用深度监听 this.user = !!options.user; //主要用于错误处理,侦听器 watcher的 user为true,其他基本为false this.lazy = !!options.lazy; //惰性求职,当属于计算属性watcher时为true this.sync = !!options.sync; //标记为同步计算,三大类型暂无 } else { this.deep = this.user = this.lazy = this.sync = false; } //初始化各种属性和option //观察者的回调 //除了侦听器 watcher外,其他大多为空函数 this.cb = cb; this.id = ++uid$1; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // 解析expOrFn,赋值给this.getter // 当是渲染watcher时,expOrFn是updateComponent,即重新渲染执行render(_update) // 当是计算watcher时,expOrFn是计算属性的计算方法 // 当是侦听器watcher时,expOrFn是watch属性的名字,this.cb就是watch的handler属性 //对于渲染watcher和计算watcher来说,expOrFn的值是一个函数,可以直接设置getter //对于侦听器watcher来说,expOrFn是watch属性的名字,会使用parsePath函数解析路径,获取组件上该属性的值(运行getter) //依赖(订阅目标)更新,执行update,会进行取值操作,运行watcher.getter,也就是expOrFn函数 if(typeof expOrFn === ‘function’) { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); } this.value = this.lazy ? undefined : this.get();}; //取值操作Watcher.prototype.get = function get() { //Dep.target设置为该观察者 pushTarget(this); var vm = this.vm; //取值 var value = this.getter.call(vm, vm); //移除该观察者 popTarget(); return value};Watcher.prototype.addDep = function addDep(dep) { var id = dep.id; if(!this.newDepIds.has(id)) { //为观察者的deps添加依赖dep this.newDepIds.add(id); this.newDeps.push(dep); if(!this.depIds.has(id)) { //为dep添加该观察者 dep.addSub(this); } }};//当一个依赖改变的时候,通知它updateWatcher.prototype.update = function update() { //三种watcher,只有计算属性 watcher的lazy设置了true,表示启用惰性求值 if(this.lazy) { this.dirty = true; } else if(this.sync) { //标记为同步计算的直接运行run,三大类型暂无,所以基本会走下面的queueWatcher this.run(); } else { //将watcher推入观察者队列中,下一个tick时调用。 //也就是数据变化不是立即就去更新的,而是异步批量去更新的 queueWatcher(this); }};//update执行后,运行回调cbWatcher.prototype.run = function run() { if(this.active) { var value = this.get(); if( value !== this.value || isObject(value) || this.deep ) { var oldValue = this.value; this.value = value; //运行 cb 函数,这个函数就是之前传入的watch中的handler回调函数 if(this.user) { try { this.cb.call(this.vm, value, oldValue); } catch(e) { handleError(e, this.vm, (“callback for watcher "” + (this.expression) + “"”)); } } else { this.cb.call(this.vm, value, oldValue); } } }};//对于计算属性,当取值计算属性时,发现计算属性的watcher的dirty是true//说明数据不是最新的了,需要重新计算,这里就是重新计算计算属性的值。Watcher.prototype.evaluate = function evaluate() { this.value = this.get(); this.dirty = false;};//收集依赖Watcher.prototype.depend = function depend() { var this$1 = this; var i = this.deps.length; while(i–) { this$1.deps[i].depend(); }};总结Observe是对数据进行监听,Dep是一个订阅器,每一个被监听的数据都有一个Dep实例,Dep实例里面存放了N多个订阅者(观察者)对象watcher。被监听的数据进行取值操作时(getter),如果存在Dep.target(某一个观察者),则说明这个观察者是依赖该数据的(如计算属性中,计算某一属性会用到其他已经被监听的数据,就说该属性依赖于其他属性,会对其他属性进行取值),就会把这个观察者添加到该数据的订阅器subs里面,留待后面数据变更时通知(会先通过观察者id判断订阅器中是否已经存在该观察者),同时该观察者也会把该数据的订阅器dep添加到自身deps中,方便其他地方使用。被监听的数据进行赋值操作时(setter)时,就会触发dep.notify(),循环该数据订阅器中的观察者,进行更新操作。 ...

April 8, 2019 · 3 min · jiezi

Python版设计模式之监听者模式

监听模式又名观察者模式、发布/订阅模式、源-监听器(Source/Listener)模式,模式的核心是:设计时要区分谁是被观察者,谁是观察者。被观察者至少有三个方法,添加观察者、删除观察者、监听目标变化并通知观察者;观察者这至少包含一个方法,当接收到被观察者的通知时,做出相应的处理(即在被观察者的监听中调用)。模式框架’‘‘观察者模式’‘‘class Observable(object): ’’’ 被监听的对象,实现类需要具体增加被监听的资源 ’’’ def init(self): self.__observers = [] @property def observers(self): return self.__observers def has_observer(self): return False if not self.__observers else True def add_observer(self, observer): self.__observers.append(observer) def remove_observer(self, observer): self.__observers.remove(observer) def listener(self, obj=None): for observer in self.__observers: observer.update(self, obj)class Observer(object): ’’’ 观察者,当观察的对象发生变化时,依据变化情况增加处理逻辑 ’’’ def update(self, observable, obj): passUML图示例’‘‘基于观察者模式,实现一个简单的消息队列,当队列中有消息时,将消息发送给监听者’‘‘class MyQueue(Observable): def init(self): super().init() self.__resource = [] def has_message(self): return True if self.__resource else False def queue_size(self): return len(self.__resource) def add_resource(self, res): self.__resource.append(res) print(“新消息进入,推送…”) self.listener(obj=res)class MySubOdd(Observer): def update(self, observable, obj): if isinstance(observable, MyQueue) and int(obj) % 2 != 0: print(“I’m MySubOdd, Get Message {} from MyQueue.".format(obj))class MySubEven(Observer): def update(self, observable, obj): if isinstance(observable, MyQueue) and int(obj) % 2 == 0: print(“I’m MySubEven, Get Message {} from MyQueue.".format(obj))if name == “main”: my_queue = MyQueue() # 初始化一个队列 my_sub_odd = MySubOdd() # 初始化奇数监听者 my_sub_even = MySubEven() # 初始化偶数监听者 # 将两个监听者加入监听队列 my_queue.add_observer(my_sub_odd) my_queue.add_observer(my_sub_even) # 添加资源进队列 my_queue.add_resource(“1”) my_queue.add_resource(“3”) my_queue.add_resource(“2”) my_queue.add_resource(“4”) ...

February 15, 2019 · 1 min · jiezi

数据双向绑定的探究和实现

data-binding简单版vue的双向绑定实现成果图vue实现上诉成果用vue来实现其实很简单,我们先创建一个文件夹v0,在v0文件夹内见一个index.html:<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>data-binding</title></head><style> #app { text-align: center; }</style><body> <div id=“app”> <h2>{{title}}</h2> <input v-model=“name”> <h1>{{name}}</h1> <button v-on:click=“clickMe”>click me!</button> </div></body><!– vue源码cdn地址 https://cn.vuejs.org/v2/guide/ –><script src=“https://cdn.jsdelivr.net/npm/vue"></script><script type=“text/javascript”> new Vue({ el: ‘#app’, data: { title: ‘hello world’, name: ‘yujiyang’ }, methods: { clickMe: function () { this.title = ‘hello world’; } }, mounted: function () { window.setTimeout(() => { this.title = ‘你好’; }, 1000); } });</script></html>那么vue是怎么实现的呢?数据双向绑定{{ }}双括号变量解析v-model和事件指令解析现在从简到难一步一步来实现这个’vue’。ps:由于本文只是为了学习和分享,所以只是简单实现下原理,并没有考虑太多情况和设计,如果大家有什么建议,欢迎提出来。vue数据双向绑定原理看过vue的源码的同学都知道,vue是通过数据劫持结合发布者-订阅者模式的方式来实现的~首先来看下vue是如何进行数据劫持的?new Vue({ el: ‘#app’, data: { message: { name:‘yjy’, } }, created: function () { console.log(this.message); }});结果:我们可以看到属性name有两个相对应的get和set方法,为什么会多出这两个方法呢?因为vue是通过Object.defineProperty()来实现数据劫持的。Object.defineProperty( )是用来做什么的?它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举,这里我们主要先来研究下它对应的两个描述属性get和set,如果还不熟悉其用法,请点击这里阅读更多用法。举个例子:var People = { name: ‘jiyang’};console.log(People.name); // yjy如果我们想要在执行console.log(People.name)的同时,直接给名字加个姓,那要怎么处理呢?或者说要通过什么监听对象People的属性值。这时候Object.defineProperty( )就派上用场了,代码如下:var People = {}var name = ‘’;Object.defineProperty(People, ’name’, { set: function (value) { name = value; console.log(‘你取了一个名叫做’ + value); }, get: function () { return ‘yu’ + name }}) People.name = ‘jiyang’; // 你取了一个名叫做jiyangconsole.log(People.name); // yujiyang我们通过Object.defineProperty( )设置了对象People的name属性,对其get和set进行重写操作,顾名思义,get就是在读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数,所以当执行 People.name = ‘jiyang’ 这个语句时,控制台会打印出 “你取了一个名叫做jiyang”,紧接着,当读取这个属性时,就会输出 “yujiyang”,因为我们在get函数里面对该值做了加工了。如果这个时候我们执行下下面的语句,控制台会输出什么?console.log(People)结果:是不是跟我们在上面打印vue数据长得有点类似,这也进一步验证vue确实是通过这种方法来进行数据劫持的。接下来我们通过其原理来实现一个简单版的mvvm双向绑定代码。mvvm思路分析实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据:view更新data其实可以通过事件监听即可,比如input标签监听 ‘input’ 事件就可以实现了。所以我们着重来分析下,当数据改变,如何更新视图的?其实上文我们已经给出答案了,就是通过Object.defineProperty()对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。现在思路有了,接下去就是实现过程了。实现过程监听器Observer首先我们需要实现一个数据监听器Observer,用来监听所有属性,如果属性发上变化了,就执行相应方法。function defineReactive(data, key, val) { observe(val); // 递归遍历所有子属性 Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { return val; }, set: function(newVal) { val = newVal; console.log(‘属性’ + key + ‘已经被监听了,现在值为:“’ + newVal.toString() + ‘”’); } });} function observe(data) { if (!data || typeof data !== ‘object’) { return; } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); });}; var people = { person1: { name: ’’ }, person2: ‘’};observe(people);people.person1.name = ‘yjy’; // 属性name已经被监听了,现在值为:“yjy”people.person2 = ‘没有此人’; // 属性person2已经被监听了,现在值为:“没有此人”订阅器Dep与订阅者Watcher由于一个属性的getter可能在多处触发(也就是被多个dom使用),所以属性setter的时候需要执行多个改变view的方法,我们把通知执行一个改变view的方法抽象成一个订阅者Watcher。有一个容器来专门收集Watcher,叫做订阅器Dep。Watcher初始化的时候,添加一个Watcher到Dep。属性每次setter的时候,执行Dep中所有Watcher。有了这个思路我们结合observer,来实现一下Dep和Watcher// observer.js// observer+depfunction Observer(data) { this.data = data; this.walk(data);}Observer.prototype = { walk: function(data) { var self = this; Object.keys(data).forEach(function(key) { self.defineReactive(data, key, data[key]); }); }, defineReactive: function(data, key, val) { var dep = new Dep(); var childObj = observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) { dep.addSub(Dep.target); } return val; }, set: function(newVal) { if (newVal === val) { return; } val = newVal; dep.notify(); } }); }};function observe(value, vm) { if (!value || typeof value !== ‘object’) { return; } return new Observer(value);};function Dep () { this.subs = [];}Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); }};Dep.target = null;// watcher.jsfunction Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 将自己添加到订阅器的操作}Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 缓存自己 var value = this.vm.data[this.exp] // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; }};‘vue’的v1版本了解了Observer、Dep、Watcher后,我们只要将Observer和Watcher关联起来,就可以实现一个简单的数据双向绑定了。因为这里没有还没有设计解析器Compile,所以对于模板绑定的属性数据,我们都进行写死处理,假设模板上有一个节点,且id号为’name’,并且双向绑定的绑定的变量也为’name’,且是通过两个大双括号包起来(这里只是为了掩饰,暂时没什么用处)。先建立一个文件夹v1,目录结构如下:observer和watcher我们已经实现,还需要实现一个关联Observer和Watcher的index.js和模板index.html<!–index.html–><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>self-vue</title></head><style> #name { text-align: center; }</style><body> <h1 id=“name”></h1></body><script src=“js/observer.js”></script><script src=“js/watcher.js”></script><script src=“js/index.js”></script><script type=“text/javascript”> var ele = document.querySelector(’#name’); var vue = new Vue({ name: ‘hello world’ }, ele, ’name’); window.setTimeout(function () { console.log(’name值改变了’); vue.name = ‘yjy’; }, 2000);</script></html>// index.jsfunction Vue (data, el, exp) { var self = this; this.data = data; // 知道为什么还要这个操作??? Object.keys(data).forEach(function(key) { self.proxyKeys(key); }); observe(data); el.innerHTML = this.data[exp]; // 初始化模板数据的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this;}Vue.prototype = { proxyKeys: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); }}解析器Compile虽然上面已经实现了一个双向数据绑定的例子,但是整个过程都没有去解析dom节点,而是直接固定某个节点进行替换数据的,所以接下来需要实现一个解析器Compile来做解析和绑定工作。解析器Compile实现步骤:解析模板指令,并替换模板数据,初始化视图将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器为了解析模板,首先需要获取到dom元素,然后对含有dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理:function nodeToFragment (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment;}接下来需要遍历各个节点,对含有相关指定的节点进行特殊处理,这里咱们先处理最简单的情况,只对带有 ‘{{变量}}’ 这种形式的指令进行处理,后面再考虑更多指令情况:function compileElement (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /{{(.)}}/; var text = node.textContent; //获取该节点以及其子节点所包含文本内容 if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令且节点类型是文本类型 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } });},function compileText (node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); });},function updateText (node, value) { node.textContent = typeof value == ‘undefined’ ? ’’ : value;},function isTextNode (node) { return node.nodeType == 3;}获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,对应上面所说的步骤1,接下去需要生成一个并绑定更新函数的订阅器,对应上面所说的步骤2。这样就完成指令的解析、初始化、编译三个过程,一个解析器Compile也就可以正常的工作了。为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下‘Vue类’:// index.jsfunction Vue (options) { var self = this; this.vm = this; this.data = options.data; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options.el, this.vm); // 初始化视图和watcher初始化都封装在compile里面 return this;}Vue.prototype = { proxyKeys: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); }}更改后,我们就不要像之前通过传入固定的元素值进行双向绑定了,只要传入一个根节点就可以对所有子节点进行双向绑定了:<body> <div id=“app”> <h2>{{title}}</h2> <h1>{{name}}</h1> </div></body><script src=“js/observer.js”></script><script src=“js/watcher.js”></script><script src=“js/compile.js”></script><script src=“js/index.js”></script><script type=“text/javascript”> var Vue = new Vue({ el: ‘#app’, data: { title: ‘hello world’, name: ‘ddvdd’ } }); window.setTimeout(function () { Vue.title = ‘你好’; }, 2000); window.setTimeout(function () { Vue.name = ‘yjy’; }, 2500); </script>完整的Compile.js代码如下:function Compile(el, vm) { this.vm = vm; this.el = document.querySelector(el); this.fragment = null; this.init();}Compile.prototype = { init: function () { if (this.el) { this.fragment = this.nodeToFragment(this.el); this.compileElement(this.fragment); this.el.appendChild(this.fragment); } else { console.log(‘Dom元素不存在’); } }, nodeToFragment: function (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment; }, compileElement: function (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /{{(.)}}/; var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } }); }, compileText: function(node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); }); }, updateText: function (node, value) { node.textContent = typeof value == ‘undefined’ ? ’’ : value; }, isTextNode: function(node) { return node.nodeType == 3; }}思考到这里,一个数据双向绑定功能已经基本完成了,接下去就是需要完善更多指令的解析编译,在哪里进行更多指令的处理呢?如何添加一个v-model指令和事件指令v-on:click的解析编译?所有例子和最终思考答案源码地址如果对你有帮助,麻烦star下~ ...

October 31, 2018 · 5 min · jiezi