参考视频链接
1、html
引入自己写的vue.js
<body>
<div id="app">
<span>阿婆主:{{name}}</span>
<input type="text" v-model="name">
<span>更多:{{more.like}}</span>
<input type="text" v-model="more.like">
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue(
{
el:'#app',
data:{
name:'技术蛋老师',
more:{
like:'一键三连'
}
}
}
);
// console.log('vue实例:',vm);
</script>
</body>
2、vue.js
2.1
class Vue{
//实例
constructor(obj){
this.$data = obj.data;
//Observer函数进行数据劫持 监听实例里的数据
Observer(this.$data)
//vue实例挂载的元素 vue实例
Compile(obj.el,this)
}
}
2.2数据劫持
取实例监听的属性值时,触发getter。
触发getter 时添加订阅者到订阅者数组中。
在setter通知每一个订阅者更新dom。
//数据劫持
function Observer(data_instance){
//递归出口 结束条件
if(!data_instance ||typeof data_instance !=='object'){
return
}
//创建后 在getter中 添加订阅者
const dep = new Dep();
//以数组的方式 获取对象的key
Object.keys(data_instance).forEach(key =>{
let value = data_instance[key]
//属性值可能是对象 对子属性也要数据劫持
Observer(value)
//数据代理
Object.defineProperty(data_instance,key,{
enumerable:true,//可枚举
configurable:true,//可修改
get(){
console.log(`get访问了属性:${key}-->值为:${value}`);
//temp 不为空时执行 '与操作'
Dep.temp&&dep.addSub(Dep.temp)
return value
},
set(newValue){
console.log(`set属性:${key}修改为-->为${newValue}`);
value = newValue;
//新赋值的属性也要
Observer(value)
dep.notify()
}
})
})
}
2.3模板解析
模板解析时,把节点值 替换内容后,需要告诉订阅者要更新自己:创建实例。
在update中获取最新值 传给回调函数。
//HTML模板解析 替换DOM内
//vue实例挂载的元素 Vue实例
function Compile(element,vm){
vm.$el = document.querySelector(element)
//创建文档碎片 提高dom性能
const fragment = document.createDocumentFragment();
let child;
//while中child = vm.$el.firstChild 再判断child是否为空
while(child = vm.$el.firstChild){
fragment.append(child) //节点添加到文档碎片后 这时页面没有数据了
}
fragment_compile(fragment)
//修改fragment里的内容
function fragment_compile(node){
// \s :匹配空格 *:0个或多个 (\S+): 至少一个字符
//{{ xxx }}
const pattern = /\{\{\s*(\S+)\s*\}\}/;
if(node.nodeType=== 3){// 文本节点
const value = node.nodeValue;
console.log(value);// 阿婆主:{{name}} 更多:{{more.like}}
//exec() 返回数组形式
const res_regex = pattern.exec(node.nodeValue);
if(res_regex){
//(2) ['{{name}}', 'name', index: 4, input: '阿婆主:{{name}}', groups: undefined]
//(2) ['{{more.like}}', 'more.like', index: 3, input: '更多:{{more.like}}', groups: undefined]
// console.log(res_regex);
const val = res_regex[1].split('.').reduce((total,current)=> total[current],vm.$data);
//这里替换了 插值表达式 value 的内容
//如由' 阿婆主:{{ name}}' 变为 '阿婆主:技术蛋老师'
node.nodeValue = value.replace(pattern,val)
// 继续遍历节点 节点里面可能还会有节点 递归
//把节点值 替换内容后,需要告诉订阅者要更新自己。
//创建订阅者
//回调函数 在update中执行时 传来参数
new Watcher(vm,res_regex[1],(newValue)=>{
//node.nodeValue = ' 阿婆主:{{ name}}' 插值表达式
//在回调时已经是替换过的内容 '阿婆主:技术蛋老师' 了,不是我们要的结果
//所以需要在最上面暂存const value = node.nodeValue;
node.nodeValue = value.replace(pattern,newValue)
})
}
// 递归出口 终止
return
}
if(node.nodeType===1 &&node.nodeName==='INPUT'){
const attr = Array.from(node.attributes);
attr.forEach(i=>{
if(i.nodeName==='v-model'){
const value = i.nodeValue.split('.').reduce(
(total,current)=>total[current],vm.$data
)
node.value = value;
//数据改变视图
//数据更新后 订阅者更新自己
//回调函数 在update中执行时 传来参数
new Watcher(vm,i.nodeValue,(newValue)=>{
node.value = newValue;
});
//视图改变数据
node.addEventListener('input',(e)=>{
const arr1 = i.nodeValue.split('.');// ['more','like']
const arr2 = arr1.slice(0,arr1.length - 1);//['more']
//vm.$data.more
const final = arr2.reduce((total,current)=>total[current],vm.$data);
final[arr1[arr1.length-1]] = e.target.value
})
}
})
}
node.childNodes.forEach(child => fragment_compile(child))
}
//把文档碎片应用到页面 页面显示出数据了
vm.$el.appendChild(fragment)
}
2.4Dep
构造watcher实例时 把实例存到Dep实例中。
//构造watcher实例时 把实例存到Dep实例中
//触发getter 时添加订阅者到订阅者数组中
//依赖 收集和通知订阅者
class Dep{
//构造函数
constructor(){
//数组: 存放所有订阅者的方法
this.subs = []
}
// 添加 订阅者的方法
addSub(watcher) {
this.subs.push(watcher)
}
// 发布订阅的方法
notify() {
this.subs.forEach(watcher => {
//调用Watcher实例的update方法,相当于通知它更新自己的dom
watcher.update()
})
}
}
2.5watcher
在update中获取最新值 传给回调函数。
收集完watcher实例的依赖后,置空,防止订阅者 多次加入到依赖数组中。
//什么时候创建订阅者实例
//在修改文档内容 —— 模板解析时,把节点值 替换内容后,需要告诉订阅者要更新自己。
// 订阅者
class Watcher{
//vm: vue实例 key :vue实例对应的属性 name、more.like
//callback:创建实例时传进来的回调函数。函数内部定义更新dom的一些操作
constructor(vm,key,callback) {
this.vm = vm;
this.key = key
//将回调函数 挂载到 生成的 Watcher实例上
this.callback = callback
//temp 临时变量
Dep.temp = this;
key.split('.').reduce((total,current)=>total[current],vm.$data);//触发getter 会执行get()函数 获取watcher实例 添加订阅者
Dep.temp = null;//防止订阅者 多次加入到依赖数组中
}
update() {
//获取最新值 传给回调函数
const value = this.key.split('.').reduce((total,current)=>total[current],this.vm.$data);
//更新dom操作 也就是去执行它
this.callback(value)
}
}
总的vue.js
class Vue{
//实例
constructor(obj){
this.$data = obj.data;
//Observer函数进行数据劫持 监听实例里的数据
Observer(this.$data)
//vue实例挂载的元素 vue实例
Compile(obj.el,this)
}
}
//数据劫持
function Observer(data_instance){
//递归出口 结束条件
if(!data_instance ||typeof data_instance !=='object'){
return
}
//在getter 添加订阅者
const dep = new Dep();
//以数组的方式 获取对象的key
Object.keys(data_instance).forEach(key =>{
let value = data_instance[key]
//属性值可能是对象 对子属性也要数据劫持
Observer(value)
//数据代理
Object.defineProperty(data_instance,key,{
enumerable:true,//可枚举
configurable:true,//可修改
get(){
console.log(`get访问了属性:${key}-->值为:${value}`);
//temp 不为空时执行 '与操作'
Dep.temp&&dep.addSub(Dep.temp)
return value
},
set(newValue){
console.log(`set属性:${key}修改为-->为${newValue}`);
value = newValue;
//新赋值的属性也要
Observer(value);
dep.notify()
}
})
})
}
//HTML模板解析 替换DOM内
//vue实例挂载的元素 Vue实例
function Compile(element,vm){
vm.$el = document.querySelector(element)
//创建文档碎片 提高dom性能
const fragment = document.createDocumentFragment();
let child;
//while中child = vm.$el.firstChild 再判断child是否为空
while(child = vm.$el.firstChild){
fragment.append(child) //节点添加到文档碎片后 页面没有数据了
}
fragment_compile(fragment)
//修改fragment里的内容
function fragment_compile(node){
// \s :匹配空格 *:0个或多个 (\S+): 至少一个字符
//{{ xxx }}
const pattern = /\{\{\s*(\S+)\s*\}\}/;
if(node.nodeType=== 3){// 文本节点
const value = node.nodeValue;
console.log(value);// 阿婆主:{{name}} 更多:{{more.like}}
//exec() 返回数组形式
const res_regex = pattern.exec(node.nodeValue);
if(res_regex){
//(2) ['{{name}}', 'name', index: 4, input: '阿婆主:{{name}}', groups: undefined]
//(2) ['{{more.like}}', 'more.like', index: 3, input: '更多:{{more.like}}', groups: undefined]
// console.log(res_regex);
const val = res_regex[1].split('.').reduce((total,current)=> total[current],vm.$data);
//这里替换了 插值表达式 value 的内容
//如由' 阿婆主:{{ name}}' 变为 '阿婆主:技术蛋老师'
node.nodeValue = value.replace(pattern,val)
// 继续遍历节点 节点里面可能还会有节点 递归
//把节点值 替换内容后,需要告诉订阅者要更新自己。
//创建订阅者
//回调函数 在update中执行时 传来参数
new Watcher(vm,res_regex[1],(newValue)=>{
//node.nodeValue = ' 阿婆主:{{ name}}' 插值表达式
//在回调时已经是替换过的内容 '阿婆主:技术蛋老师' 了,不是我们要的结果
//所以需要在最上面暂存const value = node.nodeValue;
node.nodeValue = value.replace(pattern,newValue)
})
}
// 递归出口 终止
return
}
if(node.nodeType===1 &&node.nodeName==='INPUT'){
const attr = Array.from(node.attributes);
attr.forEach(i=>{
if(i.nodeName==='v-model'){
const value = i.nodeValue.split('.').reduce(
(total,current)=>total[current],vm.$data
)
node.value = value;
//数据改变视图
//数据更新后 订阅者更新自己
//回调函数 在update中执行时 传来参数
new Watcher(vm,i.nodeValue,(newValue)=>{
node.value = newValue;
});
//视图改变数据
node.addEventListener('input',(e)=>{
const arr1 = i.nodeValue.split('.');// ['more','like']
const arr2 = arr1.slice(0,arr1.length - 1);//['more']
//vm.$data.more
const final = arr2.reduce((total,current)=>total[current],vm.$data);
final[arr1[arr1.length-1]] = e.target.value
})
}
})
}
node.childNodes.forEach(child => fragment_compile(child))
}
//把文档碎片应用到页面
vm.$el.appendChild(fragment)
}
//构造watcher实例时 把实例存到Dep实例中
//触发getter 时添加订阅者到订阅者数组中
//依赖 收集和通知订阅者
class Dep{
//构造函数
constructor(){
//数组: 存放所有订阅者的方法
this.subs = []
}
// 添加 订阅者的方法
addSub(watcher) {
this.subs.push(watcher)
}
// 发布订阅的方法
notify() {
this.subs.forEach(watcher => {
//调用Watcher实例的update方法,相当于通知它更新自己的dom
watcher.update()
})
}
}
//什么时候创建订阅者实例
//在修改文档内容 —— 模板解析时,把节点值 替换内容后,需要告诉订阅者要更新自己。
// 订阅者
class Watcher{
//vm: vue实例 key :vue实例对应的属性 name、more.like
//callback:创建实例时传进来的回调函数。函数内部定义更新dom的一些操作
constructor(vm,key,callback) {
this.vm = vm;
this.key = key
//将回调函数 挂载到 生成的 Watcher实例上
this.callback = callback
//temp 临时变量
Dep.temp = this;
key.split('.').reduce((total,current)=>total[current],vm.$data);//触发getter 会执行get()函数 获取watcher实例 添加订阅者
Dep.temp = null;//防止订阅者 多次加入到依赖数组中
}
update() {
//获取最新值 传给回调函数
const value = this.key.split('.').reduce((total,current)=>total[current],this.vm.$data);
//更新dom操作 也就是去执行它
this.callback(value)
}
}
更详细的博文链接