目錄:
觀察者模式
發(fā)布-訂閱模式
觀察者模式和發(fā)布訂閱模式的區(qū)別
實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定
(1)觀察者模式
(1)概念
- 對(duì)程序中的某個(gè)對(duì)象的狀態(tài)進(jìn)行觀察殃恒,并且在其發(fā)生改變的時(shí)候得到通知
- 存在(觀察者)和(目標(biāo))兩種角色
(2)subject對(duì)象 和 observer對(duì)象
- 目標(biāo)對(duì)象 : subject
- 觀察者對(duì)象:observer
- 在目標(biāo)對(duì)象中存放觀察者對(duì)象的引用
- 一個(gè)目標(biāo)對(duì)象對(duì)應(yīng)多個(gè)觀察者對(duì)象(一對(duì)多)
(3)在觀察者模式中,目標(biāo)對(duì)象與觀察者對(duì)象堤器,相互獨(dú)立又相互聯(lián)系
- 兩者都是相互獨(dú)立的對(duì)象個(gè)體
觀察者對(duì)象在目標(biāo)對(duì)象中訂閱事件,目標(biāo)對(duì)象廣播發(fā)布事件
- pattern:是模式的意思 (design pattern設(shè)計(jì)模式)
- notify:通知
- Subject:擁有添加溜徙,刪除,通知(發(fā)布)等一系列觀察者對(duì)象Observer的方法
- Observer:擁有更新方法等
(4)ES5 代碼實(shí)現(xiàn)過程
觀察者模式 - ES5代碼實(shí)現(xiàn)過程
1. 新建兩個(gè)對(duì)象
- 目標(biāo)對(duì)象:------ subject
- 觀察者對(duì)象:---- observer
2. 在(目標(biāo)對(duì)象)中存放(觀察者對(duì)象)的引用
function Subject() { // 目標(biāo)對(duì)象
this.observers = new ObserverList()
}
function ObserverList() {
this.observerList = []
}
function Observer() {} // 觀察者對(duì)象 -- 函數(shù)都是Function構(gòu)造函數(shù)的實(shí)例伶选,即函數(shù)也是一個(gè)對(duì)象
3. 對(duì)于目標(biāo)對(duì)象中的引用隐岛,我們必須可以動(dòng)態(tài)的控制:
Subject:擁有添加猫妙,刪除,通知(發(fā)布)等一系列觀察者對(duì)象Observer的方法
Observer:擁有更新方法等
- add(obj) 添加訂閱者對(duì)象
- count() 訂閱者對(duì)象總數(shù)
- get(index) 獲取某個(gè)訂閱者對(duì)象
- indexOf(obj, startIndex) 某個(gè)訂閱者對(duì)象的位置
- removeAt(index) 刪除某個(gè)訂閱者對(duì)象
- addObserver
- removeObserver
4. 目標(biāo)對(duì)象廣播發(fā)布消息
- 注意:
- 目標(biāo)對(duì)象并不能指定觀察者對(duì)象做出什么相應(yīng)的變化聚凹,目標(biāo)對(duì)象只有通知的作用
- 我們將具體的觀察者對(duì)象該作出的變化交給了觀察者對(duì)象自己去處理
- 即 觀察者對(duì)象擁有自己的 update(context) 方法來作出改變
- 同時(shí)該方法不應(yīng)該寫在原型鏈上割坠,因?yàn)槊恳粋€(gè)實(shí)例化后的 Observer 對(duì)象所做的響應(yīng)都是不同的,需要獨(dú)立存儲(chǔ) update(context)方法
Subject.prototype.notify = function(context) { // 目標(biāo)對(duì)象發(fā)布消息事件
var observerCount = this.observers.count()
for (var i = 0; i < observerCount; i++) {
this.observers.get(i).update(context)
// 取出所有訂閱了目標(biāo)對(duì)象的觀察者對(duì)象妒牙,并執(zhí)行他們各自的收到消息后的更新方法
// 注意每個(gè)觀察者對(duì)象的update()方法都是定義在自身的實(shí)例上的彼哼,各個(gè)觀察者之間互不影響
}
}
function Observer() {
this.update = function() {
// ...
};
}
--------------
5. 完整代碼
function ObserverList() { -------------------- 目標(biāo)對(duì)象的引用
this.observerList = [];
}
ObserverList.prototype = {
add: function(obj) { // 添加觀察者,方法掛載在ObserverList的原型連鏈上
return this.observerList.push(obj); // this指向的是調(diào)用add方法時(shí)所在的對(duì)象湘今,這里是 ObserverList的實(shí)例在調(diào)用
},
count: function() { // 總數(shù)量
return this.observerList.length;
},
get: function(index) { // 獲取某個(gè)觀察者
if (index > -1 && index < this.observerList.length) {
return this.observerList[index];
}
},
indexOf: function(obj, startIndex) { // 獲取某個(gè)觀察者對(duì)象的下標(biāo)
// obj某個(gè)觀察者對(duì)象
// startIndex 開始搜索的起始位置
var i = startIndex;
while (i < this.observerList.length) {
if (this.observerList[i] === obj) {
return i;
}
i++;
}
return -1; // 找到返回下標(biāo)敢朱,沒找到返回-1
},
removeAt: function(index) { // 刪除某個(gè)觀察者對(duì)象
this.observerList.splice(index, 1);
}
}
function Subject() { -------------------------------- 目標(biāo)對(duì)象
this.observers = new ObserverList(); // 實(shí)例化
}
Subject.prototype = { // 目標(biāo)對(duì)象上包含添加,刪除摩瞎,通知觀察者對(duì)象的方法
addObserver: function(observer) {
this.observers.add(observer); // --------------------------------調(diào)用ObserverList原型上的add方法
},
removeObserver: function(observer) {
this.observers.removeAt(this.observers.indexOf(observer, 0)); // 調(diào)用ObserverList原型上的removeAt方法
},
notify: function(context) { // ---------------- 目標(biāo)對(duì)象發(fā)布消息
var observerCount = this.observers.count();
for (var i = 0; i < observerCount; i++) {
this.observers.get(i).update(context);
}
},
}
function Observer(content) { ------------------------- 觀察者對(duì)象
this.update = function() {
console.log(content)
}
}
const observer1 = new Observer('111')
const observer2 = new Observer('222') // 觀察者對(duì)象中一般掛載收到通知后的 更新方法
const subject = new Subject()
subject.observers.add(observer1)
subject.observers.add(observer2)
subject.notify() // 目標(biāo)對(duì)象上一般都掛載添加拴签,刪除,通知 觀察者對(duì)象的方法
詳細(xì) https://juejin.im/post/5cc57704e51d456e5a072975
精簡 https://juejin.im/post/5bb1bb616fb9a05d2b6dccfa
實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定 https://juejin.im/post/5bce9a35f265da0abd355715
(5)ES6 代碼實(shí)現(xiàn)過程
// 觀察者模式 es6
// 目標(biāo)類
class Subject {
constructor() {
this.observers = []
}
// 添加
add = (obj) => {
this.observers.push(obj)
}
// 刪除
remove(index) {
if (index > -1 && index < this.observers.length) {
this.observers.splice(index, 1)
}
}
// 通知(發(fā)布消息)
notify() {
for(let i = 0; i < this.observers.length; i++) {
this.observers[i].update()
}
}
}
// 觀察者類
class Observer {
constructor(content) {
this.update = () => {
console.log(content)
}
}
}
const subject = new Subject()
const observer1 = new Observer('111')
const observer2 = new Observer('222')
subject.add(observer1)
subject.add(observer2)
subject.notify()
splice()
- splice()用于刪除數(shù)組的一部分成員旗们,并可以在刪除的位置添加新的數(shù)組成員蚓哩,返回值是刪除的元素
- 返回值:刪除的元素組成的數(shù)組
- 參數(shù):
- 第一個(gè)參數(shù):刪除的起始位置,默認(rèn)從0開始
- 第二個(gè)參數(shù):被刪除的元素個(gè)數(shù)
- 如果有更多的參數(shù):表示要插入數(shù)組的元素
- 該方法改變?cè)瓟?shù)組
const arr = [1, 2, 3]
const res = arr.splice(1, 2, 222, 333) // 從下標(biāo)為1的位置開始刪除2個(gè)元素上渴,并將222杖剪,333插入到被刪除的位置之后
console.log(res) // [2,3] 返回值是被刪除的元素組成的數(shù)組
console.log(arr) // [1, 222, 333]改變?cè)瓟?shù)組
for of
for in
1. 數(shù)組
- for(let i of arr) ----------- i表示值
- for(let i in arr) ----------- i 表示下標(biāo)
2. 對(duì)象
- 對(duì)象只能用for in循環(huán),不能用for of循環(huán)
- 因?yàn)閷?duì)象沒有部署iterator接口驰贷,不用使用for of循環(huán)
- for (let i in obj) ----------- i表示下標(biāo)
http://www.reibang.com/p/3e3451708143
(2)發(fā)布訂閱模式
(1)角色
- 發(fā)布者:Publisher
- 訂閱者:Subscriber
- 中介:Topic/Event Channel
中介既要接收發(fā)布者所發(fā)布的消息事件盛嘿,又要將消息派發(fā)給訂閱者
- 所以中介要根據(jù)不同的事件,儲(chǔ)存相應(yīng)的訂閱者信息
- 通過中介對(duì)象括袒,完全解偶了發(fā)布者和訂閱者
(2)實(shí)例
1. 創(chuàng)建中介對(duì)象 ------------------------------ pubsub
2. 給中介對(duì)象的每個(gè)訂閱者對(duì)象一個(gè)標(biāo)識(shí) ---------- subUid
- 每當(dāng)有一個(gè)新的訂閱者對(duì)象 訂閱事件的時(shí)候次兆,就給新的訂閱者對(duì)象一個(gè) subUid
3. topics對(duì)象的結(jié)構(gòu):
- key: 對(duì)應(yīng)不同的事件名稱
- value:是一個(gè)數(shù)組,每個(gè)成員是一個(gè)對(duì)象锹锰,存放訂閱該事件的(訂閱者對(duì)象)及發(fā)生事件之后作出的(響應(yīng))
4. 完整代碼:
// 發(fā)布-訂閱模式
var pubsub = {} // 中介
(function(myObject) {
var topics = {} // 存放(訂閱了某事件)對(duì)應(yīng)的 (訂閱者對(duì)象數(shù)組)
var subUid = -1 // 每個(gè)訂閱者的唯一ID
// 發(fā)布
myObject.publish = function(topic, args) {
if (!topics[topic]) {
return false
}
var subscribes = topics[topic]
var len = subscribes ? subscribes.length : 0
for (let i = len; i >= 0; i--) {
subscribes[i].func(args)
}
// 上面的for循環(huán)芥炭,也可以用for of 實(shí)現(xiàn)
// for(let obj of topics[topic]) {
// obj.func.call(this, args)
// }
return this // 這里可以不return this,不需要鏈?zhǔn)秸{(diào)用
}
// 訂閱
myObject.subscribe = function(topic, func) {
// topic:指訂閱的事件名稱
// func: 訂閱者對(duì)象收到消息通知后的響應(yīng)事件
if (!topics[topic]) {
// 如果事件不存在恃慧,則新建該事件园蝠,值是數(shù)組,數(shù)組中存放訂閱該事件的 訂閱者對(duì)象相關(guān)信息
// 相關(guān)信息包括
// 1. token -----訂閱者對(duì)象的唯一標(biāo)識(shí)符ID
// 2. func ------發(fā)生該事件(訂閱了該事件的訂閱者對(duì)象痢士,收到發(fā)布的該事件的通知時(shí)彪薛,執(zhí)行的響應(yīng)函數(shù))
topics[topic] = []
}
topics[topic].push({
token: (++subUid).toString(),
func: func // 這里可以簡寫
})
// 返回訂閱者對(duì)象的唯一標(biāo)識(shí),用于取消訂閱時(shí)是根據(jù)token來刪除該訂閱者對(duì)象
return token
}
// 取消訂閱
myObject.unsubscribe = function(token) {
// 利用token刪除該事件對(duì)應(yīng)的 訂閱者對(duì)象
for (let j in topics) {
if (topics[j]) {
for(let i = 0; i < topics[j].length; i++) {
if (topics[j][i].token === token) {
topics[j].splice(i, 1)
return token
}
}
}
}
return this // 可以不return this
}
})(pubsub)
--------------
1. 多個(gè)訂閱者對(duì)象,訂閱了同一個(gè)事件 go 事件
pubsub.subscribe('go', function(args) {
console.log(args)
})
pubsub.subscribe('go', function(args) {
console.log(args + 'oher subscriber')
})
pubsub.publish('go', 'home')
--------------
2. 取消訂閱善延,發(fā)布的go事件少态,將不會(huì)觸發(fā) 訂閱者的響應(yīng)函數(shù)
pubsub.subscribe('go', function(args) {
console.log(args)
})
pubsub.unsubscribe('0')
pubsub.publish('go', 'home')
(3)觀察者模式,發(fā)布-訂閱模式的區(qū)別和聯(lián)系
(1)區(qū)別
- 觀察者模式:需要觀察者自己定義事件發(fā)生時(shí)的響應(yīng)函數(shù)
- 發(fā)布-訂閱模式:在(發(fā)布者對(duì)象)易遣,和(訂閱者對(duì)象)之間彼妻,增加了(中介對(duì)象)
(2)聯(lián)系
- 二者都降低了代碼的(耦合性)
- 都具有消息傳遞的機(jī)制,以(數(shù)據(jù)為中心)的設(shè)計(jì)思想
(4)vue數(shù)據(jù)雙向綁定
- directive:指令
- compile:編譯
前置知識(shí):
1. Element.children
- 返回一個(gè)類似數(shù)組的對(duì)象(HTMLCollection實(shí)例)
- 包括當(dāng)前元素節(jié)點(diǎn)的所有子元素
- 如果當(dāng)前元素沒有子元素豆茫,則返回的對(duì)象包含0個(gè)成員
2. Node.childNodes
- 返回一個(gè)類似數(shù)組的對(duì)象(NodeList集合)侨歉,成員包括當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)
- NodeList是一個(gè)動(dòng)態(tài)集合
3. Node.childNodes 和 Element.children 的區(qū)別
- Element.children只包含元素類型的子節(jié)點(diǎn),不包含其他類型的子節(jié)點(diǎn)
-----
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="name" id="text">
<div v-text="name"></div>
</div>
<script>
// const tex = document.getElementById('text')
// tex.addEventListener('input', function() {
// console.log('input')
// })
</script>
<script>
class Watcher {
constructor(name, el, vm, exp, attr) {
this.name = name
this.el = el
this.vm = vm
this.exp = exp
this.attr = attr
this._update()
}
_update() {
this.el[this.attr] = this.vm.$data[this.exp]
}
}
class MyVue {
constructor(options) {
this.option = options
this.$el = document.querySelector(options.el)
this.$data = options.data
this._directive = {}
this._observes(this.$data)
this._compile(this.$el)
}
// 數(shù)據(jù)攔截
// get set 獲取和重寫
// 并發(fā)布通知
_observes(data) {
let val;
for (let key in data) {
if ( data.hasOwnProperty(key) ) {
this._directive[key] = []
}
val = data[key]
// val 可能是對(duì)象揩魂,和數(shù)組
if (typeof val === 'object') {
this._observes(val)
}
let _dir = this._directive[key]
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get() {
return val
},
set(newValue) {
if (val !== newValue) {
val = newValue
_dir.forEach(item => item._update())
}
}
})
}
}
_compile(el) {
// 子元素
let nodes = el.children
for (let i in Array.from(nodes)) {
const node = nodes[i]
console.log(node, 'xxx')
if (node.children.length) {
this._compile(node)
}
if (node.hasAttribute('v-text')) {
const attrValue = node.getAttribute('v-text')
this._directive[attrValue].push(new Watcher('text', node, this, attrValue, 'innerHTML'))
}
if (node.hasAttribute('v-model') && ( node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
let _this = this
node.addEventListener('input', (function() {
let attrValue = node.getAttribute('v-model')
_this._directive[attrValue].push(new Watcher('input', node, _this, attrValue, 'value'))
return function() {
console.log(typeof attrValue)
_this.$data[attrValue] = node.value
}
})())
}
}
}
}
const ins = new MyVue({
el: '#app',
data: {
name: 'wang'
}
})
</script>
</body>
</html>
實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定 https://juejin.im/post/5bce9a35f265da0abd355715