简单手动实现Vue

核心点:1.实现响应式、2,模版编译
cvue.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<p style='color: red'>{{count}}</p>
			<span c-text="title"></span>
			<span c-html="desc"></span>
		</div>
		<script src="./cvue.js"></script>
		<script>
			const app = new CVue({
				el: '#app',
				data: {
					count: 33,
					title: '编译c-text',
					desc: '<div>c-model编译</div>'
				}
			})
			setInterval(() => {
				app.count++
				// console.log(app.$data.count)
			}, 1000)
		</script>
	</body>
</html>

cvue.js


function defineReactive(obj, key, val) {
	// 回调过滤val为object得情况
	observe(val)
	Object.defineProperty(obj, key, {
		get() {
			console.log('get')
			return val;
		},
		set(newVal) {
			if (newVal !== val) {
				// 过滤新值为对象得情况
				observe(newVal)
				val = newVal;
				console.log('set')
			}
		}
	})
}

function observe(obj) {
	if(typeof obj !== 'object' || obj === null) {
		return obj;
	}
	console.log('observe')
	new Observer(obj)
	// Object.keys(obj).forEach(key => {
	// 	defineReactive(obj, key, obj[key])
	// })
}
function walk(obj) {
	Object.keys(obj).forEach(key => {
		defineReactive(obj, key, obj[key])
	})
}
// 数据响应式拦截  // 编译模版  

// observer监听所有属性,区分对象类型做出响应式处理,在属性set的时候就监听到改变然后通知更新
class Observer {
	constructor(obj) {
	    this.value = obj
		if (Array.isArray(obj)) {
			// 为数组时候的处理方法
		} else {
			walk(obj)
		}
	}
}
// 代理以便直接访问data中的数据
// vm 是vue实例
function proxy(vm) {
	Object.keys(vm.$data).forEach(key => {
		// 把data里面的属性代理到vue实例上,方便用户直接访问打他属性
		Object.defineProperty(vm, key, {
			get() {
				return vm.$data[key]
			},
			set(newVal) {
				vm.$data[key] = newVal
			}
		})
	})
}


// 编译
class Compile {
	constructor(el, vm) {
	    this.$el = document.querySelector(el) // 获取真实dom元素
		this.$vm = vm
		this.compile(this.$el)
	}
	compile(el) {
		// 遍历el 的dom树
		el.childNodes.forEach(node => {
			// 判断node是元素还是文本
			if(this.isElement(node)){
				// 为元素的情况需要处理属性和子节点
				this.compileElement(node)
				console.log(node.nodeName, '编译节点')
				// 递归遍历子节点
				this.compile(node)
			} else if (this.isInter(node)) { // 判断是否为文本模版{{}}
				// RegExp.$1拿出最近一次正则表达式里面分组1的值
				// 获取值并赋值给node
				console.log(node.textContent, '编译文本')
				this.compileText(node)
				
			}
		})
	}
	// 判断node是元素还是文本
	isElement(node) {
		// nodeType为1表示元素 为3表示文本
		return node.nodeType === 1
	}
	// 判断是否为文本模版{{}}
	isInter(node) {
		// /\{\{(.*)\}\}/.test(node.textContent) 正则匹配节点文本是否为模板文本{{}}
		return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
	}
	// 编译文本
	compileText(node) {
		const key = RegExp.$1
		node.textContent = this.$vm[key]
	}
	isDir(attrName) {
		// 判断属性名是否为c-或者:开头
		return attrName.startsWith('c-')
	}
	// 处理node中的属性
	compileElement(node) {
		// 获取节点属性 node.attributes 得到一个类数组,使用es6 Array.from()转为真实数组
		Array.from(node.attributes).forEach(attr => {
			// attr有两个属性分别为name和value
			console.log(node, attr, attr.name, '属性')
			const attrName = attr.name;
			const exp = attr.value;
			// 判断attr是否为一个指令
			if(this.isDir(attrName)) {
				// 如果为指令执行指令的处理函数
				const dir = attrName.substring(2); // 获取指令名称
				this[dir] && this[dir](node, exp)
			}
		})
	}
	text(node, exp) {
		node.textContent = this.$vm[exp]
	}
	html(node, exp) {
		node.innerHTML = this.$vm[exp]
	}
}


// 做dom更新
let watchers = []
class Watcher {
	// vm+key获取最新值,updateFn得到最新值更新视图
	constructor(vm, key, updateFn) {
		this.vm = vm;
		this.key = key;
		this.updateFn = updateFn
		watchers.push(this)
	}
	update() {
		this.updateFn.call(this.vm, this.vm[this.key])
	}
}
class CVue {
	constructor(options) {
		// 保存选项
		this.$options = options
	    this.$data = options.data;
		// 响应式处理 data props watch computed 和视图有依赖的都是响应式
		observe(this.$data)
		
		// 把data代理到vue实例上
		proxy(this)
		
		/*编译 
		1.根据跟元素options.el遍历子元素
		2.判断子元素是文本还是节点如果是文本判断是一般文本还是模板文本{{}},如果是节点递归遍历子节点, 
		3.遍历属性比如v开头的v-html v-model, @开头的事件*/
		
		new Compile(options.el, this)
		
		
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值