(精华2020年5月17日更新) vue实战篇 手写vue底层源码
MYvue.js 主要作用监听属性变化class MYvue {constructor(options) {this.$options = options;this.$data = options.data;//数据劫持this.observe(this.$data);this.$el = options.el;//包含Watcher创建new Complie(optio
·
MYvue.js 主要作用监听属性变化
class MYvue {
constructor(options) {
this.$options = options;
this.$data = options.data;
//数据劫持
this.observe(this.$data);
this.$el = options.el;
//包含Watcher创建
new Complie(options.el, this)
}
//数据劫持
observe(data) {
if (!data || typeof data != 'object') {
return;
}
Object.keys(data).forEach((key) => {
//让数据可观测
//this.$data.test 改变
this.defineReactive(data, key, data[key]);
//this.test 代理改变
this.proxyData(key);
})
}
//让数据可观测
defineReactive(obj, key, value) {
var dept = new Dep();
//value值为对象递归遍历
this.observe(value);
Object.defineProperty(obj, key, {
get() {
//属性被读取了,将来需要添加订阅
console.log('我被读取了');
//将Dep.target(当前的watcher对象存入Dep的deps)
Dep.target && dept.addDep(Dep.target)
return value;
},
set(newVal) {
if (newVal == value) {
return;
}
value = newVal;
console.log(key + '属性更行了,他更新的值是' + newVal);
//如果我被改变, 我将来会在这里通知的
dept.notify(); //变化的数据,让watcher的update方法执行
},
enumerable: true,
configurable: true
})
}
//代理data中的属性到vue实例上
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal;
}
})
}
}
//Dep 用来管理wather,管理者的角色
class Dep {
constructor() {
//存放所有的依赖(watcher),一个watcher对应一个属性
this.deps = [];
}
//收集订阅者
addDep(sub) {
this.deps.push(sub);
}
//通知订阅更新
notify() {
this.deps.forEach((sub) => {
//你要更新了
sub.update() //update是watchr里面的一个函数
})
}
}
//监听器对象
class Watcher {
constructor(vm, key, cb) {
// vm :Vue实例化对象
// key: 需要监听的属性
// cb: 是Watccher绑定的更新函数
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;//this指Watcher本身
this.vm[key] // 触发getter ,添加依赖
Dep.target = null;
}
update() {
console.log('你的属性要更新了');
this.cb.call(this.vm, this.vm[this.key]);
}
}
Complie.js 把属性变化重新渲染html
class Complie {
constructor(el, vm) {
//遍历节点
this.$el = document.querySelector(el);
this.$vm = vm;
//编译
if (this.$el) {
//转换内容为片段fragment
this.$fragment = this.node2Fragment(this.$el);
//执行编译
this.replaceTemplate(this.$fragment)
//将编译完的html结果追加到$el
this.$el.appendChild(this.$fragment);
}
}
node2Fragment(el) {
// createDocumentFragment 用来创建虚拟dom节点
var frag = document.createDocumentFragment();
let child;
//讲el中所有的元素搬家到frag
while (el.firstChild && (child = el.firstChild)) {
frag.appendChild(child);
}
return frag;
}
//对el立面的内容进行替换
// 对el里面的内容进行替换
replaceTemplate(frag) {
// console.log('frag.childNodes',frag.childNodes);
var childNodes = Array.from(frag.childNodes);
if (childNodes.length == 0) return;
childNodes.forEach(node => {
let txt = node.textContent;
let reg = /\{\{(.*?)\}\}/g; // 正则匹配{{}}
// 只读属性 Node.nodeType 表示的是该节点的类型
// nodeType 属性可用来区分不同类型的节点,比如 元素, 文本 和 注释。
// 即是文本节点又有大括号的情况{{}}
if (node.nodeType === 3 && reg.test(txt)) {
// console.log(RegExp.$1); // 匹配到的第一个分组 如: a.b, c
let arr = RegExp.$1.split('.');
let valTest = this.$vm;
arr.forEach(key => {
valTest = valTest[key]; // 如this.a.b
});
// 用trim方法去除一下首尾空格
node.textContent = txt.replace(reg, valTest).trim();
// 监听变化,第二步加的
// 给Watcher再添加两个参数,用来取新的值(newVal)给回调函数传参
new Watcher(this.$vm, RegExp.$1, newVal => {
//这里是有属性改变的时候会更新数据
node.textContent = txt.replace(reg, newVal).trim();
});
}
// v-modle数据的双向绑定
if (node.nodeType === 1) { // 元素节点
let nodeAttr = Array.from(node.attributes); // 获取dom上的所有属性,是个类数组
nodeAttr.length > 0 && nodeAttr.forEach(attr => {
let name = attr.name; // v-model type
let exp = attr.value; // c text
if (name.includes('v-')) {
node.value = this.$vm[exp]; // this.c 为 2
}
// oninput .onclick
node.addEventListener('input', e => {
let newVal = e.target.value;
// 相当于给this.c赋了一个新值
// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
this.$vm[exp] = newVal;
});
});
}
// 如果还有子节点,继续递归replaceTemplate
if (node.childNodes && node.childNodes.length) {
this.replaceTemplate(node);
}
});
}
}
使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="./MYvue.js"></script>
<script src="./compile.js"></script>
<body>
<div id="app">
<h1>{{song}}</h1>
<p>主打歌为{{album.theme}} </p>
<p> <span>作词人为{{singer}}等人。</span><em>qq</em></p>
<input type="text" v-model="song">
</div>
<script>
// 写法和Vue一样
let MYvue = new MYvue({
el: '#app',
data: {
song: '飞啊飞啊',
album: {
name: '一眼万年',
theme: '天外飞仙'
},
singer: '胡歌'
}
});
</script>
</body>
</html>
更多推荐
已为社区贡献26条内容
所有评论(0)