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