来自 Web前端 2020-04-29 17:43 的文章
当前位置: 网上澳门金莎娱乐 > Web前端 > 正文

仿照 Vue ,简单实现一个的 MVVM网上澳门金莎娱乐

compileElement 及 compileText 中最终调用了 CompileUtil 的方法更新 dom。

4. Watcher.js

DocumentFragment是一个可以被 js 操作但不会直接出发渲染的文档对象,Vue 中编译模板时是现将所有节点存到 DocumentFragment 中,操作完后再统一插入到 html 中,这样就避免了多次修改 Dom 出发渲染导致的性能问题。

2. Observer.js

nodeType 为 HTML 原生节点的一个属性,用于表示节点的类型。

function MVVM { this.$options = options || {}; var data = this._data = this.$options.data; var me = this; // 数据代理 // 实现 vm.xxx -> vm._data.xxx Object.keys.forEach { me._proxyData; // 代理计算属性 // 同样通过Object.defineProperty进行劫持 this._initComputed(); observe; this.$compile = new Compile(options.el || document.body, this)}MVVM.prototype = { $watch: function { new Watcher; }}
class Vue { constructor(options) { // 参数挂载到实例 this.$el = document.querySelector(options.el); this.$data = options.data; if (this.$el) { // 数据劫持 new Observer(this.$data); // 编译模板 new Compile(this.$el, this); } }}

双向绑定的原理很简单,通过数据劫持,当设置新属性值的时候通过订阅者更新视图;编译指令,替换变量,同时绑定更新函数到订阅者;对应事件绑定调用addEventListener进行监听。

Compile

要实现mvvm的双向绑定,需要实现如下几点:

实现的这个 类 Vue 包含了4个主要模块:

function Observer { Object.keys.forEach { defineReactive; });}function defineReactive  { var dep = new Dep(); var childObj = observe; Object.defineProperty(data, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get: function { dep.depend(); } return val; }, set: function { if  { return; } val = newVal; // 新的值是object的话,进行监听 childObj = observe; // 通知订阅者 dep.notify;}

Watcher

基本原理

因此需要创建一个 Watcher 类:

function Dep() { this.id = uid++; this.subs = [];}Dep.prototype = { addSub: function { this.subs.push; }, depend: function() { Dep.target.addDep; }, notify: function() { this.subs.forEach { sub.update; }};

总结

1. MVVM.js

DocumentFragment

Compile做了两件事情:

从零实现一个类 Vue以下代码的 git 地址:以下代码的 git 地址目录结构

1.实现一个数据监听器Observer,能够对对象的所有属性进行监听,发生变化时拿到最新值通知订阅者2.实现一个解析器Compile,对每个子元素节点的指令进行扫描和解析,根据模板指令替换数据,初始化视图以及绑定相应的回调函数;3.实现一个Watcher,作为Observer和Compile的桥梁,能够订阅属性变动的通知,执行指令绑定的回调函数,更新视图4.mvvm的入口,整合以上三者

网上澳门金莎娱乐,定义为响应式数据后再对其取值和修改是会触发对应的 get 和 set 方法。取值时将改值本身返回,并先判断是否有依赖目标 Dep.target,如果有则保存起来。修改值时先手动将原值修改并通知保存的所有依赖目标进行更新操作。

function compileElement  { var childNodes = el.childNodes, me = this; [].slice.call.forEach { var text = node.textContent; var reg = /{{}}/; // 解析元素节点 if (me.isElementNode { me.compile; // {{}}替换变量 } else if  && reg.test { me.compileText; } // 递归遍历子节点 if (node.childNodes && node.childNodes.length) { me.compileElement;}

compile: function { var nodeAttrs = node.attributes, me = this; [].slice.call.forEach { // 指令以v-xxx命名 //  var attrName = attr.name; // v-html if (me.isDirective { var exp = attr.value; // content var dir = attrName.substring; // 事件指令 if (me.isEventDirective { compileUtil.eventHandler(node, me.$vm, exp, dir); // 普通指令 } else { compileUtil[dir] && compileUtil[dir]; } node.removeAttribute;}

var compileUtil = { html: function { this.bind(node, vm, exp, 'html'); }, bind: function { var updaterFn = updater[dir + 'Updater']; // 第一次初始化视图 updaterFn && updaterFn(node, this._getVMVal; // 实例化Watcher,添加订阅者 new Watcher(vm, exp, function { // 属性变化的视图更新函数 updaterFn && updaterFn(node, value, oldValue); }); },}var Updater = { htmlUpdater: function { node.innerHTML = typeof value == 'undefined' ? '' : value; }}

前置知识nodeType

1.自身实例化时在属性订阅器集合dep里添加自己2.自身需有update方法3.调用dep.notice时,watcher调用自身的update ,触发Compile中定义的回调

Object.defineProperty接收三个参数Object.defineProperty(obj, prop, descriptor), 可以为一个对象的属性 obj.prop t通过 descriptor 定义 get 和 set 方法进行拦截,定义之后该属性的取值和修改时会自动触发其 get 和 set 方法。

function Watcher { this.cb = cb; this.vm = vm; this.expOrFn = expOrFn; this.value = this.get();}Watcher.prototype = { update: function; }, run: function() { var value = this.get(); var oldVal = this.value; if  { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; var value = this.getter.call; Dep.target = null; return value; }};
model(node, vm, expr) { // 添加数据监听,数据变化时调用回调函数 new Watcher(vm, expr, () = { this.updater.modelUpdater(node, this.getVal(vm, expr)); }) this.updater.modelUpdater(node, this.getVal(vm, expr));},

分布实现

index.js 中调用了new Observer()进行数据劫持,Vue 实例 data 属性的每项数据都通过 defineProperty 方法添加 getter setter 拦截数据操作将其定义为响应式数据,因此这里首先需要提供一个 Observer 类:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

class Compile { constructor(el, vm) { this.el = el; this.vm = vm; if (this.el) { // 将 dom 转入 fragment 内存中 const fragment = this.node2fragment(this.el); // 编译 提取需要的节点并替换为对应数据 this.compile(fragment); // 插回页面中去 this.el.appendChild(fragment); } } // 编译元素节点 获取 Vue 指令并执行对应的编译函数(取值并更新 dom) compileElement(node) { const attrs = node.attributes; Array.from(attrs).forEach(attr = { const attrName = attr.name; if (this.isDirective(attrName)) { const expr = attr.value; let [, ...type] = attrName.split('-'); type = type.join(''); // 调用指令对应的方法更新 dom CompileUtil[type](node, this.vm, expr); } }) } // 编译文本节点 判断文本内容包含 {{}} 则执行文本节点编译函数(取值并更新 dom) compileText(node) { const expr = node.textContent; const reg = /{{s*([^}s]+)s*}}/; if (reg.test(expr)) { // 调用文本节点对应的方法更新 dom CompileUtil['text'](node, this.vm, expr); } } // 递归遍历 fragment 中所有节点判断节点类型并编译 compile(fragment) { const childNodes = fragment.childNodes; Array.from(childNodes).forEach(node = { if (this.isElementNode(node)) { // 元素节点 编译并递归 this.compileElement(node); this.compile(node); } else { // 文本节点 this.compileText(node); } }) } // 循环将 el 中每个节点插入 fragment 中 node2fragment(el) { const fragment = document.createDocumentFragment(); let firstChild; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment; } isElementNode(node) { return node.nodeType === 1; } isDirective(name) { return name.startsWith('v-'); }}

Watcher作为Observer与Compile之间通信的桥梁,属性变化的订阅者,做了如下的事情:

getVal 方法用于处理嵌套对象的属性,如传入表达式 expr 为user.name的情况,利用 reduce 从 vm.$data 上拿到。

对需要监测的对象的每个属性进行递归遍历,通过Object.defineProperty设置setter和getter。当设置新的属性值时,触发相应的setter,通知订阅者。

这里对每项数据都通过创建一个 Dep 类实例进行保存依赖和通知更新的操作,因此需要写一个 Dep 类:

Vue.采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,数据变动时发布消息给订阅者,触发相应函数的回调。

依赖目标就是 Watcher 的实例,对外提供了 update 方法,调用 update 时会重新根据表达式 expr 取值与老值对比并调用回调函数。这里的回调函数就是对应的更新 dom 的方法,在 compile.js 中的 model 及 text 方法中有执行new Watcher(),在模板解析时就为每项数据添加了监听:

1.将DOM转成文档碎片fragment,提升查询效率2.遍历所有元素节点及其子节点,调用对应的指令渲染函数渲染,并调用对应的指令更新函数进行绑定3.将fragment添加回真实的DOM中

Vue 中通过每个节点的 nodeType 属性是1还是3判断是元素节点还是文本节点,针对不同类型节点做不同的处理。

订阅者模式,每个属性维护一个Dep,记录自己的订阅者,notify通知每个订阅者执行相应的update方法,更新视图。

本文由网上澳门金莎娱乐发布于Web前端,转载请注明出处:仿照 Vue ,简单实现一个的 MVVM网上澳门金莎娱乐

关键词: