手摸手从0实现简版Vue --- (模板编译)

接: 手摸手从0实现简版Vue --- (对象劫持) 手摸手从0实现简版Vue --- (数组劫持) 1. 如果有用户传入了el,去调用$mount方法 在前面对数据进行inintState之后,如果用户配置了el属性,会通过调用$mount方法,将数据渲染到页面上,此时: Vue.prototype._init = function(options) { // vue 中的初始化 this.$options 表示 Vue 中的参数 let vm = this; vm.$options = options; // MVVM 原理, 需要数据重新初始化 initState(vm); + if (vm.$options.el) { + vm.$mount(); + } } $mount 此时的$mount需要做两件事: 通过用户配置的el字段,获取DOM元素,并将该元素挂载到vm.$el字段上; 通过实例化一个渲染 Watcher,去进行页面渲染; function query(el) { if (typeof el === 'string') { return document.querySelector(el); }; return el; } // 渲染页面 将组件进行挂载 Vue.prototype.$mount = function () { let vm = this; let el = vm.$options.el; // 获取元素 el = vm.$el = query(el); // 获取当前挂载的节点 vm.$el 就是我要挂在的一个元素 // 渲染通过 watcher来渲染 let updateComponent = () => { // 更新、渲染的逻辑 vm._update(); // 更新组件 } new Watcher(vm, updateComponent); // 渲染Watcher, 默认第一次会调用updateComponent } 这里会生成一个渲染Watcher的实例。下面先简单实现一下这个Watcher类,在observe目录下新建watcher.js: let id = 0; // Watcher 唯一标识 class Watcher { // 每次产生一个watch 都会有一个唯一的标识 /** * * @param {*} vm 当前逐渐的实例 new Vue * @param {*} exprOrFn 用户可能传入的一个表达式 也可能传入一个函数 * @param {*} cb 用户传入的回调函数 vm.$watch('msg', cb) * @param {*} opts 一些其他参数 */ constructor(vm, exprOrFn, cb = () => {}, opts = {}) { this.vm = vm; this.exprOrFn = exprOrFn; if (typeof exprOrFn === 'function') { this.getter = exprOrFn; } this.cb = cb; this.opts = opts; this.id = id++; this.get(); } get() { this.getter(); // 让传入的函数执行 } } export default Watcher; 根据现在的Watcher实现,新生成这个渲染Watcher的实例,会默认去执行UpdateComponent方法,也就是去执行vm._update方法,下面我们去看一下_update方法的实现。 2. _update _update方法主要实现页面更新,将编译后的DOM插入到对应节点中,这里我们暂时先不引入虚拟DOM的方式,我们首先用一种较简单的方式去实现文本渲染。 首先使用createDocumentFragment把所有节点都剪贴到内存中,然后编译内存中的文档碎片。 Vue.prototype._update = function() { let vm = this; let el = vm.$el; /** TODO 虚拟DOM重写 */ // 匹配 {{}} 替换 let node = document.createDocumentFragment(); let firstChild; while(firstChild = el.firstChild) { node.appendChild(firstChild); } compiler(node, vm); // 编译节点内容 匹配 {{}} 文本,替换为变量的值 el.appendChild(node); } 下面我们去实现compiler方法: 3. Compiler 方法实现 const defaultRE = /\{\{((?:.|\r?\n)+?)\}\}/g; export const util = { getValue(vm, expr) { // school.name let keys = expr.split('.'); return keys.reduce((memo, current) => { memo = memo[current]; // 相当于 memo = vm.school.name return memo; }, vm); }, /** * 编译文本 替换{{}} */ compilerText(node, vm) { node.textContent = node.textContent.replace(defaultRE, function(...args) { return util.getValue(vm, args[1]); }); } } /** * 文本编译 */ export function compiler(node, vm) { let childNodes = node.childNodes; [...childNodes].forEach(child => { // 一种是元素一种是文本 if (child.nodeType == 1) { // 1表示元素 compiler(child, vm); // 如果子元素还是非文本, 递归编译当前元素的孩子节点 } else if (child.nodeType == 3) { // 3表示文本 util.compilerText(child, vm); } }) } 好了到现在我们的节点编译方法也实现了,我们去看下页面效果,将 index.html修改为Vue模板的形式:
{{msg}}

学校名字 {{school.name}}

学校年龄 {{school.age}}

{{arr}}
可以看到页面展示: image-20200307144823192 说明我们的变量被正常渲染到页面上了,但是我们去修改变量的值,发现页面不能正常更新,别急,下一部分我们去搞定依赖收集去更新视图。 代码部分可看本次提交commit 希望各位老板点个star,小弟跪谢~

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):