接:javascript
在前面对数据进行inintState
以后,若是用户配置了el
属性,会经过调用$mount
方法,将数据渲染到页面上,此时:java
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
须要作两件事:node
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
:git
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
方法的实现。github
_update
方法主要实现页面更新,将编译后的DOM插入到对应节点中,这里咱们暂时先不引入虚拟DOM的方式,咱们首先用一种较简单的方式去实现文本渲染。segmentfault
首先使用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
方法:app
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
模板的形式:
<div id="app"> {{msg}} <div> <p>学校名字 {{school.name}}</p> <p>学校年龄 {{school.age}}</p> </div> <div>{{arr}}</div> </div>
能够看到页面展现:
说明咱们的变量被正常渲染到页面上了,可是咱们去修改变量的值,发现页面不能正常更新,别急,下一部分咱们去搞定依赖收集去更新视图。
代码部分可看本次提交commit
但愿各位老板点个star,小弟跪谢~