阅读 118

vue源码探秘之指令和生命周期

今天我们来探讨一下vue源码里的指令和生命周期,上篇我们主要过了下响应式原理,这里我要多说一句,学习源码主要是学习它里面的思想,思想就是所实现的算法,逻辑。所以读源码对提高我们编程能力有很大的帮助,学什么语言是次要的,好了,现在来看看前面需要哪些准备。

1. 将Vue2.x的指令和生命周期部分手写演示,精简其余部分

2. 之前已经实现过的内容,将采用之前的内容

  • 使用 webpack 和 webpack-dev-server 构建项目

  • 新建目录 study-directive

  • cd study-directive

  • npm init -yes

  • npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3

  • 新建 webpack.config.js 文件

  • 将如下配置拷贝到 webpack.config.js 中

const path = require('path') module.exports = {   // 入口   entry: './src/index.js',   // 出口   output: {     // 虚拟打包路径,就是说文件夹不会真正生成,而是在 8080 端口虚拟生成,不会真正的物理生成     publicPath: 'xuni',     // 打包出来的文件名     filename: 'bundle.js'   },   devServer: {     // 端口号     port: 8080,     // 静态资源文件夹     contentBase: 'www'   } } 复制代码

新建 src/index.js 文件

alert(123) 复制代码

新建 www/index.html 文件

<!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <meta name="viewport" content="width=device-width, initial-scale=1.0">   <title>Document</title> </head> <body>   <h1>你好!!!</h1>   <script src="xuni/bundle.js"></script> </body> </html> 复制代码

package.json 文件中新增命令:

{ "scripts": { "dev": "webpack-dev-server", } } 复制代码

  • 终端运行 npm run dev

  • 访问:http://localhost:8080/ 和 http://127.0.0.1:8080/xuni/bundle.js, 可以看到 www/index.html 和 xuni/bundle.js 文件的内容

前置条件

  • Vue2.x 数据响应式原理中实现的数据响应式相关的的模块复制到src目录下

  • 文章地址:juejin.cn/post/702634…

实现

  • www/index.html - 测试数据

<!DOCTYPE html> <html> <head>   <meta charset="UTF-8">   <meta name="viewport" content="width=device-width, initial-scale=1.0">   <title>Document</title> </head> <body>   <div id="app">     你好{{b.m.n}}     <ul>       <li>A</li>       <li>B</li>       <li>C</li>     </ul>     <input type="text" v-model="b.m.n">     <br>   </div>   <button onclick="add()">按我加1</button>   <script src="xuni/bundle.js"></script>   <script>     var vm = new Vue({       el: '#app',       data: {         a: 10,         b: {           m: {             n: 7           }         }       },       watch: {         a() {           console.log('a改变了')         }       }     })     function add() {       vm.b.m.n++     }   </script> </body> </html> 复制代码

  • src/index.js - 将 Vue 挂在到全局对象 Window 上

import Vue from './Vue'  window.Vue = Vue 复制代码

  • src/Vue.js - Vue 类的实现(为实现数据的响应式绑定,需使用 observe 和 Watcher 模块)

import Compiler from './Compiler' import observe from './observe' import Watcher from './Watcher' export default class Vue {   constructor(options) {     // 把参数 options 对象存为 $options     this.$options = options || {}     // 数据     this._data = options.data || undefined     // 数据要变成响应式的     observe(this._data)     // 默认数据变为响应式的,这里就是生命周期     this._initData()     // 调用默认的 watch     this._initWatch()     // 模板编译     new Compiler(options.el, this)   }   _initData() {     var self = this     Object.keys(this._data).forEach((key) => {       Object.defineProperty(self, key, {         get() {           return self._data[key]         },         set(newValue) {           self._data[key] = newValue         }       })     })   }   _initWatch() {     var self = this     var watch = this.$options.watch     Object.keys(watch).forEach((key) => {       new Watcher(self, key, watch[key])     })   } } 复制代码

  • src/Compiler.js - 模板编译

import Watcher from './Watcher' export default class Compiler {   constructor(el, vue) {     // vue 实例     this.$vue = vue     // 挂载点     this.$el = document.querySelector(el)     // 如果用户传入了挂载点     if (this.$el) {       // 调用函数,让节点变为 fragment,类似于 mustache 中的 tokens       // 实际上使用的是 AST,这里是轻量级的,fragment       let $fragment = this.node2Fragment(this.$el)       // 编译模板       this.compile($fragment)       // 替换好的内容要上树       this.$el.appendChild($fragment)     }   }   node2Fragment(el) {     var fragment = document.createDocumentFragment()     var child     // 让 el 中所有 dom 节点都进入 fragment     while ((child = el.firstChild)) {       fragment.appendChild(child)     }     return fragment   }   compile(el) {     // 得到子元素     var childNodes = el.childNodes     var self = this     var reg = /\{\{(.*)\}\}/     childNodes.forEach((node) => {       var text = node.textContent       if (node.nodeType === 1) {         self.compileElement(node)       } else if (node.nodeType === 3 && reg.test(text)) {         let name = text.match(reg)[1]         self.compileText(node, name)       }     })   }   compileElement(node) {     // 这里的方便之处在于不是讲HTML结构看作是字符串,而是真正的属性列表     var nodeAttrs = node.attributes     // 类数组对象变为数组     ;[].slice.call(nodeAttrs).forEach((attr) => {       // 这里分析指令       var attrName = attr.name       var attrValue = attr.value       // 指令都是 v- 开头的       var dir = attrName.substring(2)       // 看看是不是指令 也可用 attrName.startsWith('v-')       if (attrName.indexOf('v-') === 0) {         // v- 开头的指令         if (dir === 'model') {           new Watcher(this.$vue, attrValue, (value) => {             node.value = value           })           var v = this.getVueVal(this.$vue, attrValue)           node.value = v           node.addEventListener('input', (e) => {             var newVal = e.target.value             this.setVueVal(this.$vue, attrValue, newVal)           })         } else if (dir === 'if') {         }       }     })   }   compileText(node, name) {     node.textContent = this.getVueVal(this.$vue, name)     new Watcher(this.$vue, name, (value) => {       node.textContent = value // 触发了视图更新     })   }   getVueVal(vue, exp) {     var val = vue     exp = exp.split('.')     exp.forEach((k) => {       val = val[k]     })     return val   }   setVueVal(vue, exp, value) {     var val = vue     exp = exp.split('.')     exp.forEach((k, i) => {       if (i < exp.length - 1) {         val = val[k]       } else {         val[k] = value       }     })   } }


作者:殢无伤
链接:https://juejin.cn/post/7026535775617433614

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐