对于vue.js中的属性值我们要格外关注:
$attrs 获取当前传递的参数
$listeners 获取当前组件的自定义事件
$children 获取当前组件所有子组件
$parent 获取当前组件所有父组件
$options 获取当前vue实例参数信息
$refs 获取ref所有的引用节点
设计原则:单一职责,一个类或一个函数,只做一件事。
添加数据劫持方法
// Vue.js
//Observer专门用于数据劫持
class Observer {
data;
constructor(data) {
this.data = data
this.walk()
}
defineProperty(data, key, value) {
Object.defineProperty(data, key, {
get() {
console.log(`使用了${key}这个属性`);
return value
},
set(val) {
console.log(`修改了${key}属性`, val);
value = val
}
})
}
walk() {
Object.keys(this.data).forEach(el => {
this.defineProperty(user, el, this.data[el])
})
}
}
const user = {
username:'xiaowang',
age:11
}
new Observer(user)
console.log(user.username);
对于data来说,我们自己数据劫持存了一份在$data,并且还将$data中每个数据都再次存在了vue实例上,创建Vue类。
class Vue {
constructor(options) {
this.$options = options
this.$data = options.data()
this.$el = options.el
//$data上的所有数据都要数据劫持
new Observer(this.$data)
//$data存放所有的数据
//会将$data的数据挂并挂载到this身上
this.proxy()
}
proxy() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(val) {
this.$data[key] = val
}
})
})
}
}
进行模版渲染,这里仅仅渲染一层,如果多层可以执行递归。
//模版渲染
class Compile{
constructor(el,data){
this.$el = document.querySelector(el)
this.$data = data
this.compiler()
}
compiler(){
[...this.$el.children].forEach(item=>{
if(/\{\{([a-zA-z0-9]+)\}\}/.test(item.innerHTML)){
const key =RegExp.$1.trim()
item.innerHTML=this.$data[key]
}
})
}
}
观察者模式是一种设计模式,就是一种代码规范。有两个非常重要的元素:发布者和订阅者。一个发布者可能对应多个订阅者。
对应到我们的代码,使用{{}}的标签,意味着是我们需要标记的标签,作为订阅者,在项目中提供一个发布者,一旦数据发生变化,发布者通知订阅者更新页面。
//订阅者
class Watcher {
constructor(callback) {
Dep.target = this
this.callback = callback
this.update()
Dep.target = null
}
update() {
//这一步并不是直接修改,而是更新虚拟dom,这里只是简化了
this.callback()
}
}
//发布者
class Dep {
constructor() {
this.subs = []
}
notify() {
this.subs.forEach(item => {
item.update()
})
}
}
于此同时,在set get中需要收集wacher以及通知watcher更新数据,那么也要修改
Object.defineProperty(data, key, {
get() {
if (Dep.target) { //只收集编译时的watcher
//依赖收集,使用这个属性就生成一个watcher
dep.subs.push(Dep.target)
}
return value
},
set(val) {
//一旦数据更新,Dep通知watcher更新
value = val
dep.notify()
}
})
此刻,我们简易的响应式就算完成了。
附完整代码:
//vue.js
// Observer专门用于数据劫持
class Observer {
data;
constructor(data) {
this.data = data
this.walk()
}
defineProperty(data, key, value) {
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
if (Dep.target) { //只收集编译时的watcher
//依赖收集,使用这个属性就生成一个watcher
dep.subs.push(Dep.target)
}
return value
},
set(val) {
//一旦数据更新,Dep通知watcher更新
value = val
dep.notify()
}
})
}
walk() {
Object.keys(this.data).forEach(el => {
this.defineProperty(this.data, el, this.data[el])
})
}
}
class Vue {
constructor(options) {
this.$options = options
this.$data = options.data()
this.$el = options.el
//$data上的所有数据都要数据劫持
new Observer(this.$data)
//$data存放所有的数据
//会将$data的数据挂并挂载到this身上
this.proxy()
new Compile(this.$el, this.$data)
}
proxy() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(val) {
this.$data[key] = val
}
})
})
}
}
//模版渲染
class Compile {
constructor(el, data) {
this.$el = document.querySelector(el)
this.$data = data
this.compiler()
}
compiler() {
[...this.$el.children].forEach(item => {
if (/\{\{([a-zA-z0-9]+)\}\}/.test(item.innerHTML)) {
const key = RegExp.$1.trim()
//实际上vue底层不是直接innerHTML
// item.innerHTML=this.$data[key]
const render = () => item.innerHTML = this.$data[key]
new Watcher(render)
}
})
}
}
//订阅者
class Watcher {
constructor(callback) {
Dep.target = this
this.callback = callback
this.update()
Dep.target = null
}
update() {
//这一步并不是直接修改,而是更新虚拟dom,这里只是简化了
this.callback()
}
}
//发布者
class Dep {
constructor() {
this.subs = []
}
notify() {
this.subs.forEach(item => {
item.update()
})
}
}
//HTML
Document
{{username}}
{{age}}