当前位置: 首页 > news >正文

前端面试宝典---vue实现简化版

实现思路说明:

Vue 构造函数:

保存传入的配置选项和 data。
对 data 进行响应式处理,使用 Object.defineProperty 将 data 中的属性转换为 getter/setter。
创建 Compile 实例进行模板编译。
执行挂载钩子函数。

Dep 类:

用于收集依赖(Watcher 实例),并在数据变化时通知所有依赖更新。

Watcher 类:

订阅数据变化,当数据变化时调用回调函数。
在创建时触发一次 getter,将自己添加到 Dep 中。

Compile 类:

将真实 DOM 转换为文档片段,便于操作。
编译文档片段,处理指令和事件。
为每个绑定的数据创建 Watcher 实例,实现数据的响应式更新。

源码

// Vue 构造函数
class Vue {constructor(options) {// 保存传入的配置选项this.$options = options;// 保存 data 选项this.$data = options.data;// 对 data 进行响应式处理this.observe(this.$data);// 创建一个虚拟 DOM 实例new Compile(options.el, this);// 执行挂载钩子函数if (options.mounted) {options.mounted.call(this);}}// 对对象进行响应式处理observe(obj) {if (!obj || typeof obj !== 'object') {return;}// 遍历对象的每个属性Object.keys(obj).forEach(key => {// 对每个属性进行响应式转换this.defineReactive(obj, key, obj[key]);// 将属性代理到 Vue 实例上this.proxyData(key);});}// 定义响应式属性defineReactive(obj, key, val) {// 创建一个 Dep 实例,用于收集依赖const dep = new Dep();// 递归处理子对象this.observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {// 如果当前有正在收集依赖的 Watcher,则将其添加到 Dep 中if (Dep.target) {dep.addSub(Dep.target);}return val;},set(newVal) {if (newVal === val) {return;}val = newVal;// 通知所有依赖更新dep.notify();}});}// 将 data 中的属性代理到 Vue 实例上proxyData(key) {Object.defineProperty(this, key, {get() {return this.$data[key];},set(newVal) {this.$data[key] = newVal;}});}
}// Dep 类,用于收集依赖和通知更新
class Dep {constructor() {// 存储所有依赖(Watcher 实例)this.subs = [];}// 添加依赖addSub(sub) {this.subs.push(sub);}// 通知所有依赖更新notify() {this.subs.forEach(sub => sub.update());}
}// Watcher 类,用于订阅数据变化
class Watcher {constructor(vm, expOrFn, cb) {this.vm = vm;this.cb = cb;this.expOrFn = expOrFn;// 保存当前 Watcher 实例到 Dep.targetDep.target = this;// 获取初始值,触发依赖收集this.value = this.get();Dep.target = null;}// 获取值get() {return this.vm[this.expOrFn];}// 更新操作update() {const oldValue = this.value;this.value = this.get();// 调用回调函数,传入新值和旧值this.cb.call(this.vm, this.value, oldValue);}
}// 模板编译类
class Compile {constructor(el, vm) {// 获取根元素this.$el = document.querySelector(el);this.$vm = vm;if (this.$el) {// 将真实 DOM 转换为文档片段this.$fragment = this.node2Fragment(this.$el);// 编译文档片段this.compile(this.$fragment);// 将编译后的文档片段插入到根元素中this.$el.appendChild(this.$fragment);}}// 将真实 DOM 转换为文档片段node2Fragment(el) {const frag = document.createDocumentFragment();let child;while (child = el.firstChild) {frag.appendChild(child);}return frag;}// 编译文档片段compile(el) {const childNodes = el.childNodes;Array.from(childNodes).forEach(node => {if (this.isElementNode(node)) {// 编译元素节点this.compileElement(node);} else if (this.isTextNode(node)) {// 编译文本节点this.compileText(node);}if (node.childNodes && node.childNodes.length > 0) {// 递归编译子节点this.compile(node);}});}// 编译元素节点compileElement(node) {const nodeAttrs = node.attributes;Array.from(nodeAttrs).forEach(attr => {const attrName = attr.name;const exp = attr.value;if (this.isDirective(attrName)) {const dir = attrName.substring(2);// 调用对应的指令处理函数this[dir] && this[dir](node, this.$vm, exp);}if (this.isEventDirective(attrName)) {const dir = attrName.substring(1);// 处理事件指令this.eventHandler(node, this.$vm, exp, dir);}});}// 编译文本节点compileText(node) {const reg = /\{\{(.*)\}\}/;const text = node.textContent;if (reg.test(text)) {const exp = RegExp.$1;// 更新文本节点内容this.update(node, this.$vm, exp, 'text');}}// 更新节点内容update(node, vm, exp, dir) {const updaterFn = this[dir + 'Updater'];updaterFn && updaterFn(node, vm[exp]);// 创建 Watcher 实例,订阅数据变化new Watcher(vm, exp, function (value) {updaterFn && updaterFn(node, value);});}// 文本更新函数textUpdater(node, value) {node.textContent = value;}// 输入框值更新函数modelUpdater(node, value) {node.value = value;}// v-text 指令处理函数text(node, vm, exp) {this.update(node, vm, exp, 'text');}// v-model 指令处理函数model(node, vm, exp) {this.update(node, vm, exp, 'model');node.addEventListener('input', e => {vm[exp] = e.target.value;});}// 事件处理函数eventHandler(node, vm, exp, dir) {const fn = vm.$options.methods && vm.$options.methods[exp];if (dir && fn) {node.addEventListener(dir, fn.bind(vm));}}// 判断是否为元素节点isElementNode(node) {return node.nodeType === 1;}// 判断是否为文本节点isTextNode(node) {return node.nodeType === 3;}// 判断是否为指令isDirective(attr) {return attr.indexOf('v-') === 0;}// 判断是否为事件指令isEventDirective(attr) {return attr.indexOf('@') === 0;}
}

相关文章:

  • 用Xshell8配置密钥登陆
  • olama部署deepseek模型
  • 【AI论文】Tina:通过LoRA的微小推理模型
  • 住宅代理IP助力大规模数据采集实战
  • 数组的多种声明方式:类型标注与泛型数组
  • Git分支重命名与推送参数解析
  • 系列位置效应——AI与思维模型【80】
  • 《Keras 3部署全攻略:从新手到实战高手》
  • ShenNiusModularity项目源码学习(22:ShenNius.Admin.Mvc项目分析-7)
  • Axure疑难杂症:全局变量典型应用及思考逻辑(玩转全局变量)
  • 立创EDA
  • 哈希表的模拟实现---C++
  • WSL 中 nvidia-smi: command not found的解决办法
  • 【MCP 应用】CherryStudio 配置和应用 MCP
  • 当高级辅助驾驶遇上“安全驾校”:NVIDIA如何用技术给无人驾驶赋能?
  • 力扣2444. 统计定界子数组的数目:Java三种解法详解
  • 121. 买卖股票的最佳时机
  • 第八章 IO流
  • 深圳举办2025年全国儿童预防接种日主题宣传活动 全生命周期健康守护再升级
  • Compose笔记(十九)--NestedScroll
  • 王庆成:儒家、墨家和洪秀全的“上帝”
  • 建投读书会·东西汇流|全球物品:跨文化交流视域下的明清外销瓷
  • 广州多条BRT相关线路将停运,全市BRT客运量较高峰时大幅下降
  • 调查丨当节气出现“时差”,陕西的果农们经历着什么?
  • 神二十成功对接空间站
  • 上海汽车贸易有限公司原总经理王璟接受监察调查