代码

  1. class Watcher{
  2. // 通过回调函数实现更新的数据通知到视图
  3. constructor(expr, vm, cb){
  4. this.expr = expr;
  5. this.vm = vm;
  6. this.cb = cb;
  7. this.oldVal = this.getOldVal();
  8. }
  9. // 获取旧数据
  10. getOldVal(){
  11. // 在利用getValue获取数据调用getter()方法时先把当前观察者挂载
  12. Dep.target = this;
  13. const oldVal = compileUtil.getValue(this.expr, this.vm);
  14. // 挂载完毕需要注销,防止重复挂载 (数据一更新就会挂载)
  15. Dep.target = null;
  16. return oldVal;
  17. }
  18. // 通过回调函数更新数据
  19. update(){
  20. const newVal = compileUtil.getValue(this.expr, this.vm);
  21. if(newVal !== this.oldVal){
  22. this.cb(newVal);
  23. }
  24. }
  25. }
  26. // Dep类存储watcher对象,并在数据变化时通知watcher
  27. class Dep{
  28. constructor(){
  29. this.watcherCollector = [];
  30. }
  31. // 添加watcher
  32. addWatcher(watcher){
  33. this.watcherCollector.push(watcher);
  34. }
  35. // 数据变化时通知watcher更新
  36. notify(){
  37. this.watcherCollector.forEach(w=>w.update());
  38. }
  39. }
  40. // 定义观察者
  41. class Observer{
  42. constructor(data){
  43. this.observe(data);
  44. }
  45. // data是一个对象,可能嵌套其它对象,需要采用递归遍历的方式进行观察者绑定
  46. observe(data){
  47. if(data && typeof data === 'object'){
  48. Object.keys(data).forEach(key =>{
  49. this.defineReactive(data, key, data[key]);
  50. })
  51. }
  52. }
  53. // 通过 object.defineProperty方法对对象属性进行劫持
  54. defineReactive(obj, key, value){
  55. // 递归观察
  56. this.observe(value);
  57. const dep = new Dep();
  58. Object.defineProperty(obj, key, {
  59. enumerable: true,
  60. configurable: false,
  61. get(){
  62. // 订阅数据变化时,往Dev中添加观察者
  63. Dep.target && dep.addWatcher(Dep.target);
  64. return value;
  65. },
  66. // 采用箭头函数在定义时绑定this的定义域
  67. set: (newVal)=>{
  68. if(value === newVal) return;
  69. this.observe(newVal);
  70. value = newVal;
  71. // 通知watcher数据发生改变
  72. dep.notify();
  73. }
  74. })
  75. }
  76. }
  77. // 编译模版具体执行
  78. const compileUtil = {
  79. getValue(expr, vm){
  80. // 处理 person.name 这种对象类型,取出真正的value
  81. return expr.split('.').reduce((data,currentVal)=>{
  82. return data[currentVal];
  83. }, vm.$data)
  84. },
  85. setVal(expr, vm, inputValue){
  86. expr.split('.').reduce((data,currentVal)=>{
  87. data[currentVal] = inputValue;
  88. }, vm.$data)
  89. },
  90. getContent(expr, vm){
  91. // {{person.name}}--{{person.age}}
  92. // 防止修改person.name使得所有值全部被替换
  93. return expr.replace(/\{\{(.+?)\}\}/g, (...args)=>{
  94. return this.getValue(args[1], vm);
  95. });
  96. },
  97. text(node, expr, vm) {
  98. let value;
  99. if(expr.indexOf('{{')!==-1){
  100. value = expr.replace(/\{\{(.+?)\}\}/g, (...args)=>{
  101. // text的 Watcher应在此绑定,因为是对插值{{}}进行双向绑定
  102. // Watcher的构造函数的 getOldVal()方法需要接受数据或者对象,而{{person.name}}不能接收
  103. new Watcher(args[1], vm, ()=>{
  104. this.updater.textUpdater(node, this.getContent(expr, vm));
  105. });
  106. return this.getValue(args[1], vm);
  107. });
  108. }else{
  109. value = this.getValue(expr, vm);
  110. }
  111. this.updater.textUpdater(node, value);
  112. },
  113. html(node, expr, vm) {
  114. let value = this.getValue(expr, vm);
  115. // html对应的 Watcher
  116. new Watcher(expr, vm, (newVal)=>{
  117. this.updater.htmlUpdater(node, newVal);
  118. })
  119. this.updater.htmlUpdater(node, value);
  120. },
  121. model(node, expr, vm) {
  122. const value = this.getValue(expr, vm);
  123. // v-model绑定对应的 Watcher, 数据驱动视图
  124. new Watcher(expr, vm, (newVal)=>{
  125. this.updater.modelUpdater(node, newVal);
  126. });
  127. // 视图 => 数据 => 视图
  128. node.addEventListener('input', (e)=>{
  129. this.setVal(expr, vm, e.target.value);
  130. })
  131. this.updater.modelUpdater(node, value);
  132. },
  133. on(node, expr, vm, detailStr) {
  134. let fn = vm.$options.methods && vm.$options.methods[expr];
  135. node.addEventListener(detailStr,fn.bind(vm), false);
  136. },
  137. bind(node, expr, vm, detailStr){
  138. // v-on:href='...' => href='...'
  139. node.setAttribute(detailStr, expr);
  140. },
  141. // 视图更新函数
  142. updater: {
  143. textUpdater(node, value) {
  144. node.textContent = value;
  145. },
  146. htmlUpdater(node, value){
  147. node.innerHTML = value;
  148. },
  149. modelUpdater(node, value){
  150. node.value = value;
  151. }
  152. }
  153. }
  154. // 编译HTML模版对象
  155. class Compiler {
  156. constructor(el, vm) {
  157. this.el = this.isElementNode(el) ? el : document.querySelector(el);
  158. this.vm = vm;
  159. // 1. 将预编译的元素节点放入文档碎片对象中,避免DOM频繁的回流与重绘,提高渲染性能
  160. const fragments = this.node2fragments(this.el);
  161. // 2. 编译模版
  162. this.compile(fragments);
  163. // 3. 追加子元素到根元素
  164. this.el.appendChild(fragments);
  165. }
  166. compile(fragments) {
  167. // 1.获取子节点
  168. const childNodes = fragments.childNodes;
  169. // 2.递归循环编译
  170. [...childNodes].forEach(child => {
  171. // 如果是元素节点
  172. if (this.isElementNode(child)) {
  173. this.compileElement(child);
  174. } else {
  175. // 文本节点
  176. this.compileText(child);
  177. }
  178. //递归遍历
  179. if(child.childNodes && child.childNodes.length){
  180. this.compile(child);
  181. }
  182. })
  183. }
  184. compileElement(node) {
  185. let attributes = node.attributes;
  186. // 对于每个属性进行遍历编译
  187. // attributes是类数组,因此需要先转数组
  188. [...attributes].forEach(attr => {
  189. let {name,value} = attr; // v-text="msg" v-html=htmlStr type="text" v-model="msg"
  190. if (this.isDirector(name)) { // v-text v-html v-mode v-bind v-on:click v-bind:href=''
  191. let [, directive] = name.split('-');
  192. let [compileKey, detailStr] = directive.split(':');
  193. // 更新数据,数据驱动视图
  194. compileUtil[compileKey](node, value, this.vm, detailStr);
  195. // 删除有指令的标签属性 v-text v-html等,普通的value等原生html标签不必删除
  196. node.removeAttribute('v-' + directive);
  197. }else if(this.isEventName(name)){
  198. // 如果是事件处理 @click='handleClick'
  199. let [, detailStr] = name.split('@');
  200. compileUtil['on'](node, value, this.vm, detailStr);
  201. node.removeAttribute('@' + detailStr);
  202. }
  203. })
  204. }
  205. compileText(node) {
  206. // 编译文本中的{{person.name}}--{{person.age}}
  207. const content = node.textContent;
  208. if(/\{\{(.+?)\}\}/.test(content)){
  209. compileUtil['text'](node, content, this.vm);
  210. }
  211. }
  212. isEventName(attrName){
  213. // 判断是否@开头
  214. return attrName.startsWith('@');
  215. }
  216. isDirector(attrName) {
  217. // 判断是否为Vue特性标签
  218. return attrName.startsWith('v-');
  219. }
  220. node2fragments(el) {
  221. // 创建文档碎片对象
  222. const f = document.createDocumentFragment();
  223. let firstChild;
  224. while (firstChild = el.firstChild) {
  225. f.appendChild(firstChild);
  226. }
  227. return f;
  228. }
  229. isElementNode(node) {
  230. // 元素节点的nodeType属性为 1
  231. return node.nodeType === 1;
  232. }
  233. }
  234. class MVue {
  235. constructor(options) {
  236. // 初始元素与数据通过options对象绑定
  237. this.$el = options.el;
  238. this.$data = options.data;
  239. this.$options = options;
  240. // 通过Compiler对象对模版进行编译,例如{{}}插值、v-text、v-html、v-model等Vue语法
  241. if (this.$el) {
  242. // 1. 创建观察者
  243. new Observer(this.$data);
  244. // 2. 编译模版
  245. new Compiler(this.$el, this);
  246. // 3. 通过数据代理实现 this.person.name = '海贼王——路飞'功能,而不是this.$data.person.name = '海贼王——路飞'
  247. this.proxyData(this.$data);
  248. }
  249. }
  250. //用vm代理vm.$data
  251. proxyData(data){
  252. for(let key in data){
  253. Object.defineProperty(this,key,{
  254. get(){
  255. return data[key];
  256. },
  257. set(newVal){
  258. data[key] = newVal;
  259. }
  260. })
  261. }
  262. }
  263. }

参考

【1】mvvm
【2】ImitateVue