const Vue = (function () {
let uid = 0;
// 消息管理员,用于存储订阅者并发布消息
class Dep {
constructor () {
this.id = uid++; // 设置ID,用于区分新watcher
this.subs = []; // 存储订阅者的数组
}
depend () { // 触发target上的Watcher中的addDep方法,参数为dep的实例本身,方法作用就是将watcher放进subs数组
Dep.target.addDep(this);
}
addSub (sub) { // 添加订阅者
this.subs.push(sub);
}
notify () {
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null;
// 监听者,监听对象属性值的变化
class Observer {
constructor (value) {
this.value = value;
this.walk(value);
}
walk (value) { // 遍历属性值并监听
Object.keys(value).forEach(key => this.convert(key, value[key]))
}
convert (key, val) { // 执行监听的具体方法
defineReactive(this.value, key, val);
}
}
function defineReactive (obj, key, val) {
const dep = new Dep(); // 一个属性一个消息管理器?
let childOb = observer(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我不是get了吗');
// 如果Dep类存在target属性,将其添加至dep实例的subs数组中,target指向一个watcher实例
// 每个watcher都是一个订阅者,watcher实例在实例化的过程中,会读取data中的某个属性,从而触发当前的get方法
if (Dep.target) {
dep.depend();
}
return val;
},
set: newVal => {
console.log('我不是set了吗', newVal, val);
if (newVal !== val) { // 发生改变了,可能由一个普通变量转变为对象,所以需要继续监听
val = newVal;
childOb = observer(newVal);
dep.notify();
}
}
})
}
function observer (val) {
if (val && typeof val === 'object') {
return new Observer(val);
}
return '';
}
// 订阅者,指的是它需要知道某个对象发生改变而做出反应,需要先订阅
class Watcher {
constructor (vm, expOrFn, callback) {
this.depIds = {}; // hash储存订阅者的ID,避免重复的订阅者
this.vm = vm; // 被订阅的数据一定要来自于当前的vue实例
this.callback = callback; // 回调函数
this.exOrFn = expOrFn; // 被订阅的数据
this.val = this.get(); // 维护更新之前的数据
}
// 订阅中心在监听到数据发生改变时会触发notify方法,方法中调用update方法
update () {
this.run();
}
run () {
let val = this.get();
this.val = val;
this.callback.call(this.vm, val);
}
addDep (dep) {
// 如果在depIds中的hash中没有当前ID,可以判断是新的Watcher,因此可以添加到dep的数组中存储,此判断是为了避免同ID的watcher被多次存储
// 一个新的watcher对象只可以被一个订阅中心储存一次
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
}
get () {
// 将当前的watcher传到Dep.target属性里面,方便获取
Dep.target = this;
const val = this.vm._data[this.exOrFn]; // 触发了observer的get方法,并将当前订阅者存放到了sups数组
Dep.target = null; // 置空
return val;
}
}
class vue {
constructor (options = {}) {
let data = (this._data = options.data); // vue绑定的数据
this.methods = options.methods; // vue绑定的方法
Object.keys(data).forEach(key => this._proxy(key)) // 将data的属性都代理到vue实例上,这样可以监听到他的改变
observer(data);
}
$watch (expOrFn, cb) {
new Watcher(this, expOrFn, cb)
}
_proxy (key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => {
return this._data[key]
},
set: val => {
this._data[key] = val;
}
})
}
}
return vue
})();
let demo = new Vue({
data: {
text: '',
},
});
const p = document.getElementById('p');
const input = document.getElementById('input');
input.addEventListener('keyup', function(e) {
demo._data.text = e.target.value;
});
demo.$watch('text', str => p.innerHTML = str);
// 基于proxy升级的订阅发布模式
const Vue = (function () {
let uid = 0
// 用于储存订阅者并发布消息
class Dep {
constructor () {
// 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher
this.id = uid++
// 储存订阅者的数组
this.subs = []
}
// 触发target上的Watcher中的addDep方法,参数为dep的实例本身
depend () {
Dep.target.addDep(this)
}
// 添加订阅者
addSub (sub) {
this.subs.push(sub)
}
notify () {
// 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
this.subs.forEach(sub => sub.update())
}
}
// 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher
Dep.target = null
// 监听者,监听对象属性值的变化
class Observer {
constructor (value) {
this.value = value
this.walk(value)
}
// 遍历属性值并监听
walk (value) {
Object.keys(value).forEach(key => this.convert(key, value[key]))
}
// 执行监听的具体方法
convert (key, val) {
defineReactive(this.value, key, val)
}
}
function defineReactive (obj, key, val) {
const dep = new Dep()
// 给当前属性的值添加监听
let chlidOb = observe(val)
if (typeof val === 'object') {
obj[key] = new Proxy(val, {
set (target, property, val2, receiver) {
if (val === val2) return
// 对新值进行监听
chlidOb = observe(val2)
// 通知所有订阅者,数值被改变了
let returnData = Reflect.set(target, property, val2, receiver)
dep.notify()
return returnData
},
get (target, key, receiver) {
if (Dep.target) {
dep.depend()
}
return Reflect.get(target, key, receiver)
}
})
} else {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
// 如果Dep类存在target属性,将其添加到dep实例的subs数组中
// target指向一个Watcher实例,每个Watcher都是一个订阅者
// Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
if (Dep.target) {
dep.depend()
}
return val
},
set: newVal => {
if (val === newVal) return
val = newVal
// 对新值进行监听
chlidOb = observe(newVal)
// 通知所有订阅者,数值被改变了
dep.notify()
}
})
}
}
function observe (value) {
// 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
if (!value || typeof value !== 'object') {
return
}
return new Observer(value)
}
class Watcher {
constructor (vm, expOrFn, cb) {
this.depIds = {} // hash储存订阅者的id,避免重复的订阅者
this.vm = vm // 被订阅的数据一定来自于当前Vue实例
this.cb = cb // 当数据更新时想要做的事情
this.expOrFn = expOrFn // 被订阅的数据
this.val = this.get() // 维护更新之前的数据
}
// 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
update () {
this.run()
}
addDep (dep) {
// 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存
// 此判断是避免同id的Watcher被多次储存
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this)
this.depIds[dep.id] = dep
}
}
run () {
const val = this.get()
this.val = val
this.cb.call(this.vm, val)
}
get () {
// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = this
let val = this.vm._data[this.expOrFn] // 触发一个get
if (typeof val === 'object') { // 没有被get到,没存进去
if (Object.prototype.toString.call(val) === '[object Array]') {
val = Object.assign([], this.vm._data[this.expOrFn])
} else if (Object.prototype.toString.call(val) === '[object Object]') {
val = Object.assign({}, this.vm._data[this.expOrFn])
}
}
Dep.target = null
return val
}
}
class Vue {
constructor (options = {}) {
// 简化了$options的处理
this.$options = options
// 简化了对data的处理
let data = (this._data = this.$options.data)
// 监听数据
observe(this._data)
// 将所有data最外层属性代理到Vue实例上
Object.keys(this._data).forEach(key => this._proxy(key, this._data[key]))
}
// 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
$watch (expOrFn, cb) {
new Watcher(this, expOrFn, cb)
}
// obj[key] = new Proxy(value,
// defineObjectReactive(data, k, data[k])
_proxy (key, value) {
let _this = this
if (typeof value === 'object') {
this[key] = new Proxy(value, {
set (target, key, val, receiver) {
return Reflect.set(target, key, val, receiver)
},
get (target, key, receiver) {
return Reflect.get(target, key, receiver)
}
})
} else {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => this._data[key],
set: val => {
this._data[key] = val
},
})
}
}
}
return Vue
})()
let demo = new Vue({
data: {
text: '',
list: []
},
})
const p = document.getElementById('p')
const input = document.getElementById('input')
input.addEventListener('keyup', function (e) {
demo.text = e.target.value
})
demo.$watch('text', str => p.innerHTML = str)
const list = document.getElementById('list')
const btn = document.getElementById('btn')
btn.addEventListener('click', function () {
demo.list.push(1)
})
const render = arr => {
const fragment = document.createDocumentFragment()
if (document.querySelectorAll('li').length === arr.length) { // 说明只是改了数据内容,没改长度
let spanArray = document.querySelectorAll('span')
for (let i = 0; i < arr.length; i++) {
spanArray[i].innerHTML = arr[i]
}
} else {
let li = document.createElement('li')
let span = document.createElement('span')
span.innerHTML = arr[arr.length - 1]
let input = document.createElement('input')
input.value = arr[arr.length - 1]
input.className = 'testInput'
li.appendChild(span)
li.appendChild(input)
fragment.appendChild(li)
list.appendChild(fragment)
let allInput = document.querySelectorAll('.testInput')
allInput[arr.length - 1].oninput = function (e) {
demo.list[arr.length - 1] = e.target.value
}
}
}
demo.$watch('list', list => {
render(list)
})