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

个人做网站 私活游戏平台十大排名

个人做网站 私活,游戏平台十大排名,广州市品牌网站建设企业,公司网站怎么设计面试题 1 . 简述Vue的MVVM 模式? 参考回答#xff1a; MVVM 是 Model-View-ViewModel的缩写#xff0c;即将数据模型与数据表现层通过数据驱动进行分离#xff0c;从而只需要关系数据模型的开发#xff0c;而不需要考虑页面的表现#xff0c;具体说来如下#xff1a;M…面试题 1 . 简述Vue的MVVM 模式? 参考回答 MVVM 是 Model-View-ViewModel的缩写即将数据模型与数据表现层通过数据驱动进行分离从而只需要关系数据模型的开发而不需要考虑页面的表现具体说来如下Model代表数据模型主要用于定义数据和操作的业务逻辑。View代表页面展示组件即dom展现形式负责将数据模型转化成UI 展现出来。ViewModel为model和view之间的桥梁监听模型数据的改变和控制视图行为、处理用户交互。通过双向数据绑定把 View 层和 Model 层连接了起来而View 和 Model 之间的同步工作完全是自动的无需人为干涉在MVVM架构下View 和 Model 之间并没有直接的联系而是通过ViewModel进行交互Model 和 ViewModel 之间的交互是双向的 因此View 数据的变化会同步到Model中而Model 数据的变化也会立即反应到View 上。1Model模型 模型是指代表真实状态内容的领域模型面向对象或指代表内容的数据访问层以数据为中心。 2View视图 就像在MVC和MVP模式中一样视图是用户在屏幕上看到的结构、布局和外观UI。 3ViewModel视图模型 视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器也没有MVP模式的 presenter有的是一个绑定器。在视图模型中绑定器在视图和数据绑定器之间进行通信。MVVM 优点: 低耦合 :View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化 的时候Model可以不变,当Model变化的时候View也可以不变。 可重用性 : 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。 独立开发 : 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计面试题 2 . 请简述Vue插件和组件的区别 参考回答 一、组件是什么 1组件的定义 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念组件来实现开发的模式在Vue中每一个.vue文件都可以视为一个组件 2组件的优势 降低整个系统的耦合度在保持接口不变的情况下我们可以替换不同的组件快速完成需求例如输入框可以替换为日历、时间、范围等组件作具体的实现调试方便由于整个系统是通过组件组合起来的在出现问题的时候可以用排除法直接移除组件或者根据报错的组件快速定位问题之所以能够快速定位是因为每个组件之间低耦合职责单一所以逻辑会比分析整个系统要简单提高可维护性由于每个组件的职责单一并且组件在系统中是被复用的所以对代码进行优化可获得系统的整体升级二、插件是什么 插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种添加全局方法或者属性。如: vue-custom-element 添加全局资源指令/过滤器/过渡等。如 vue-touch 通过全局混入来添加一些组件选项。如vue-router 添加 Vue 实例方法通过把它们添加到 Vue.prototype 上实现。 一个库提供自己的 API同时提供上面提到的一个或多个功能。如vue-router三、两者的区别 两者的区别主要表现在以下几个方面 1编写形式 2注册形式 3使用场景 4编写形式 5编写组件 编写一个组件可以有很多方式我们最常见的就是vue单文件的这种格式每一个.vue文件我们都可以看成是一个组件 vue文件标准格式 br / templatebr / /templatebr / scriptbr / export default{ br /...br / }br / /scriptbr /我们还可以通过template属性来编写一个组件如果组件内容多我们可以在外部定义template组件内容如果组件内容并不多我们可直接写在template属性上 br / template idtestComponent // 组件显示的内容br /divcomponent!/div br / /templatebr /Vue.component(componentA,{ template: #testComponent template: component// 组件内容少可以通过这种形式 })四编写插件 vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器第二个参数是一个可选的选项对象 MyPlugin.install function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod function () { // 逻辑... } // 2. 添加全局资源 Vue.directive(my-directive, { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod function (methodOptions) { // 逻辑... } } 注册形式 组件注册 vue组件注册主要分为全局注册与局部注册 全局注册通过Vue.component方法第一个参数为组件的名称第二个参数为传入的配置项 Vue.component(my-component-name, { /* ... */ }) 局部注册只需在用到的地方通过components属性注册一个组件 const component1 {...} // 定义一个组件 export default { components:{ component1 // 局部注册 } } 插件注册 插件的注册通过Vue.use()的方式进行注册安装第一个参数为插件的名字第二个参数是可选择的配置项 Vue.use(插件名字,{ /* ... */} ) 注意的是 注册插件的时候需要在调用 new Vue() 启动应用之前完成 Vue.use会自动阻止多次注册相同插件只会注册一次五使用场景 具体的其实在插件是什么章节已经表述了这里在总结一下 组件 (Component) 是用来构成你的 App 的业务模块它的目标是 App.vue 插件 (Plugin) 是用来增强你的技术栈的功能模块它的目标是 Vue 本身 简单来说插件就是指对Vue的功能的增强或补充面试题 3 . 简述MVC与MVVM的区别 参考回答 MVC和MVVM的区别并不是VM完全取代了CViewModel存在目的在于抽离Controller中展示的业务逻辑而不是替代Controller其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。1MVC中Controller演变成MVVM中的ViewModel 2MVVM通过数据来显示视图层而不是节点操作 3MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验面试题 4 . 简述Vue组件通讯有哪些方式 参考回答 Vue组件通讯有方式 1、props 和 $emit 父组件向子组件传递数据是通过props传递的子组件传递给父组件是通过$emit触发事件来做到的。 2、$parent 和 $children 获取单签组件的父组件和当前组件的子组件。 3、$attrs 和 $listeners A - B - C。Vue2.4开始提供了$attrs和$listeners来解决这个问题。 4、父组件中通过 provide 来提供变量然后在子组件中通过 inject 来注入变量。官方不推荐在实际业务中适用但是写组件库时很常用。 5、$refs 获取组件实例。 6、envetBus 兄弟组件数据传递这种情况下可以使用事件总线的方式。 7、vuex 状态管理面试题 5 . 简述Vue的生命周期方法有哪些 参考回答 Vue的生命周期方法1beforeCreate 在实例初始化之后数据观测data observe和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。 2created 实例已经创建完成之后被调用。在这一步实例已经完成以下的配置数据观测data observe 属性和方法的运算watch/event 事件回调。这里没有 $el如果非要想与 DOM 进行交互可以通过vm.$nextTick 来访问 DOM。 3beforeMount 在挂载开始之前被调用相关的 render 函数首次被调用。 4mounted 在挂载完成后发生在当前阶段真实的 Dom 挂载完毕数据完成双向绑定可以访问到 Dom节点。 5beforeUpdate 数据更新时调用发生在虚拟 DOM 重新渲染和打补丁 patch之前。可以在这个钩子中进一步地更改状态这不会触发附加的重渲染过程。数据修改页面未修改 6updated 发生在更新完成之后当前阶段组件 Dom 已经完成更新。要注意的是避免在此期间更新数据因为这个可能导致无限循环的更新该钩子在服务器渲染期间不被调用。 7beforeDestroy 实例销毁之前调用。在这一步实例仍然完全可用。我们可以在这时进行 善后收尾工作比如清除定时器。 8destroyed Vue实例销毁后调用。调用后Vue实例指示的东西都会解绑定所有的事件监听器会被移除左右的子实例也会被销毁该钩子在服务器端渲染不被调用。 9activated keep-alive 专属组件被激活时调用 10deactivated keep-alive 专属组件被销毁时调用面试题 6 . 简述 Vue 有哪些内置指令 参考回答 vue 内置指令 v-once - 定义它的元素或组件只渲染一次包括元素或组件的所有节点首次渲染后不再随数据的变化重新渲染将被视为静态内容。 v-cloak - 这个指令保持在元素上直到关联实例结束编译 -- 解决初始化慢到页面闪动的最佳实践。 v-bind - 绑定属性动态更新HTML元素上的属性。例如 v-bind:class。 v-on - 用于监听DOM事件。例如 v-on:click v-on:keyup v-html - 赋值就是变量的innerHTML -- 注意防止xss攻击 v-text - 更新元素的textContent v-model - 1、在普通标签。变成value和input的语法糖并且会处理拼音输入法的问题。2、再组件上。也是处理value和input语法糖。 v-if / v-else / v-else-if。可以配合template使用在render函数里面就是三元表达式。 v-show - 使用指令来实现 -- 最终会通过display来进行显示隐藏 v-for - 循环指令编译出来的结果是 -L 代表渲染列表。优先级比v-if高最好不要一起使用尽量使用计算属性去解决。注意增加唯一key值不要使用index作为key。 v-pre - 跳过这个元素以及子元素的编译过程以此来加快整个项目的编译速度。面试题 7 . 简述怎样理解 Vue 的单项数据流 参考回答 Vue的单向数据流是指数据在Vue应用中的流动方向是单向的数据总是从父组件传到子组件子组件没有权利修改父组件传过来的数据只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父组件的状态从而导致你的应用的数据流向难以理解。 注意在子组件直接用 v-model 绑定父组件传过来的 props 这样是不规范的写法开发环境会报警告。 如果实在要改变父组件的 props 值可以再data里面定义一个变量并用 prop 的值初始化它之后用$emit 通知父组件去修改。 多种方法实现在子组件直接用 v-model 绑定父组件传过来的 props这种单向数据流的设计有以下几个优点1. 易于追踪数据流由于数据的流动方向是单向的我们可以很容易地追踪数据的来源和去向减少了数据流动的复杂性提高了代码的可读性和可维护性。 2. 提高组件的可复用性通过props将数据传递给子组件使得子组件可以独立于父组件进行开发和测试。这样一来我们可以更方便地复用子组件提高了组件的可复用性。 3. 避免数据的意外修改由于子组件不能直接修改父组件的数据可以避免数据被意外修改的情况发生。这样可以提高应用的稳定性和可靠性。单向数据流也有一些限制和不足之处。例如当数据需要在多个组件之间进行共享时通过props传递数据会变得繁琐这时可以考虑使用Vuex等状态管理工具来管理共享数据。单向数据流也可能导致组件之间的通信变得复杂需要通过事件的方式进行数据传递和更新。 Vue的单向数据流是一种有助于提高应用可维护性和可预测性的设计模式通过明确数据的流动方向使得代码更易于理解和维护。面试题 8 . 简述 v-model 双向绑定的原理是什么 参考回答 v-model 本质 就是 : value input 方法的语法糖 。可以通过 model 属性的 prop 和 event 属性来进行自定义。原生的 v-model会根据标签的不同生成不同的事件和属性。 例如 1. text 和 textarea 元素使用 value 属性和 input 事件 2. checkbox 和 radio 使用 checked 属性和 change 事件 3. select 字段将 value 作为 prop 并将 change 作为事件 以输入框为例当用户在输入框输入内容时会触发 input 事件从而更新 value。而 value 的改变同样会更新视图这就是 vue 中的双向绑定。双向绑定的原理其实现思路 如下 首先要对 数据进行劫持监听 所以我们需要设置 一个监听器 Observe r用来 监听 所有属 性。如果属性发上变化了就需要告 诉订阅者 Watcher 看是否需要更新 。 因为订阅者是有很多个所以我们需要有一个 消息订阅器 Dep 来专门收集这些订阅者 然后在监听器 Observer 和订阅者 Watcher 之间 进行统一管理的。 接着我们还需要有一个 指令解析器 Compile 对每个节点元素进 行扫描和解析 将相关指令对应初始化成一个订阅者 Watcher并替换模板数据或者绑定相应的函数此时当订阅者 Watcher 接收到相应属性的变化就会执行对应的更新函数从而更新视图。 因此接下去我们执行以 下 3 个步骤实现数据的双向绑定 1. 实现一个 监听器 Observer 用来 劫持并监听所有属 性如果有变动的就通知订阅者。 2. 实现一个 订阅者 Watcher 可以 收到属性的变化通知并执 行相应的函数从而更新视图。 3. 实现一个 解析器 Compile 可以 扫描和解析每个 节点的相关指令并根据 初始化模板数 据以及初始化相应的订阅器面试题 9 . 简述 Vue 2.0 响应式数据的原理 重点 参考回答 整体思路是 数据劫持 观察者模式 Vue 在初始化数据时 会使用 Object.defineProperty 重新定义 data 中的所有属性 当页面 使用对 应 属性时首先会进行 依赖收集 (收集当前组件的 watcher )如果属性 发生变化 会通知相关 依赖进行 更新操作( 发布订阅 ) Vue2.x 采用 数据劫持结合发布订阅模式 PubSub 模式的方式通过 Object.defineProperty 来劫 持 各个属性 的 setter、getter 在 数据变动时 发 布消息给订阅者 触发相应的监听回 调。 当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时Vue 将遍历它的属性用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter但是在内部它们让 Vue 追踪依赖在属性被访问和修改时 通知变化 。 Vue 的数据 双向绑定 整合了 ObserverCompile 和 Watcher 三者通过 Observer 来监听 自己的 model 的数据变化通过 Compile 来解析编 译模板指令最终 利用 Watcher 搭 起 Observer 和 Compile 之间的 通信桥梁 达到数据变化-视图更新视图交互变化例如 input 操作-数据 model 变更的双向绑定效果。 Vue3.x 放弃了 Object.defineProperty 使用 ES6 原生的 Proxy来解决以前使用 Object.defineProperty 所存在的一些问题。 1、Object.defineProperty 数据劫持 2、使用 getter 收集依赖 setter 通知 watcher派发更新。 3、watcher 发布订阅模式。面试题 10 . 请简述vue-router 动态路由是什么 参考回答 我们经常需要把某种模式匹配到的所有路由全都映射到同个组件。例如我们有一个 User 组件对于所有 ID 各不相同的用户都要使用这个组件来渲染。那么我们可以在 vue-router 的路由路径中使用 “ 动态路径参数 ” dynamic segment 来达到这个效果const User { template: User, }; const router new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: /user/:id, component: User }, ], });面试题 11 . 请简述Vue3.x 响应式数据原理是什么 重点 参考回答 Vuejs 作为在众多 MVVM(Model-View-ViewModel) 框架中脱颖而出的佼佼者无疑是值得任何一个前端开发者去深度学习的。 不可置否尤大佬的 VueJs 中有许多值得我们深入研究的内容但是作为最核心的数据响应式 Reactivity 模块正是我们日常工作中高端相关的内容同时也是 VueJs 中最核心的内容之一。 至于 Vuejs 中的响应式原理究竟有多重要这里我就不必累赘了。相信大家都能理解它的重要性。 不过这里我想强调的是所谓响应式原理本质上也是基于 Js 代码的升华实现而已。你也许会觉得它很难但是这一切只是源于你对他的未知。毕竟只要是你熟悉的 JavaScript 那么问题就不会很大对吧。 今天我们就让我们基于最新版 Vuejs 3.2 来稍微聊聊 VueJs 中核心模块 Reactive 是如何实现数据响应式的。 前置知识 ES6 Proxy Reflect Proxy 是 ES6 提供给我们对于原始对象进行劫持的 Api 同样 Reflect 内置 Api 为我们提供了对于原始对象的拦截操作。 这里我们主要是用到他们的 get 、 set 陷阱。 Typescript TypeScript 的作用不言而喻了文中代码我会使用 TypeScript 来书写。 Esbuild EsBuild 是一款新型 bundle build tools 它内部使用 Go 对于我们的代码进行打包整合。 Pnpm pnpm 是一款优秀的包管理工具这里我们主要用它来实现 monorepo 。 如果你还没在你的电脑上安装过 pnpm 那么请你跟随官网安装它很简单只需要一行 npm install -g pnpm即可。 搭建环境 工欲善其事必先利其器。在开始之前我们首先会构建一个简陋的开发环境便于将我们的 TypeScript 构建成为 Iife 形式提供给浏览器中直接使用。 因为文章主要针对于响应式部分内容进行梳理构建环境并不是我们的重点。所以我并不会深入构建环境的搭建中为大家讲解这些细节。 如果你有兴趣可以跟着我一起来搭建这个简单的组织结构。如果你并不想动手没关系。我们的重点会放在在之后的代码。 初始化项目目录 首先我们创建一个简单的文件夹命名为 vue 执行 pnpm init -y 初始化 package.json 。 接下来我们依次创建 pnpm-workspace.yaml文件 这是一个有关 pnpm 实现 monorepo 的 yaml 配置文件我们会在稍微填充它。 .npmrc文件 这是有关 npm 的配置信息存放文件。 packages/reactivity目录 我们会在这个目录下实现核心的响应式原理代码上边我们提过 vue3 目录架构基于 monorepo 的结构所以这是一个独立用于维护响应式相关的模块目录。 当然每个 packages 下的内容可以看作一个独立的项目所以它们我们在 reactivity 目录中执行 pnpm init -y 初始化自己的 package.json。 同样新建 packages/reactivity/src 作为 reactivity 模块下的文件源代码。 packages/share目录 同样正如它的文件夹名称这个目录下存放所有 vuejs 下的工具方法分享给别的模块进行引入使用。 它需要和 reactivity 维护相同的目录结构。 scripts/build.js文件 我们需要额外新建一个 scripts 文件夹同时新建 scripts/build.js 用于存放构建时候的脚本文件。 image.png 此时目录如图所示。 安装依赖 接下来我们来依次安装需要使用到的依赖环境在开始安装依赖之前。我们先来填充对应的 .npmrc 文件 shamefully-hoist true 默认情况下 pnpm 安装的依赖是会解决幽灵依赖的问题所谓什么是幽灵依赖你可以查看这篇文章。 这里我们配置 shamefully-hoist true 意为我们需要第三方包中的依赖提升也就是需要所谓的幽灵依赖。 这是因为我们会在之后引入源生 Vue 对比实现效果与它是否一致。 你可以在这里详细看到它的含义。 同时接下里让我们在 pnpm-workspace.yaml 来填入以下代码 packages: # 所有在 packages/ 和 components/ 子目录下的 package - packages/** # - components/** # 不包括在 test 文件夹下的 package # - !**/test/** 因为基于 monorepo 的方式来组织包代码所以我们需要告诉 pnpm 我们的 repo 工作目录。 这里我们指定了 packages/ 为 monorepo 工作目录此时我们的 packages 下的每一个文件夹都会被 pnpm 认为是一个独立的项目。 接下来我们去安装所需要的依赖 pnpm install -D typescript vue esbuild minimist -w 注意这里 -w 意为 --workspace-root 表示我们将依赖安装在顶层目录所以包可以共享到这些依赖。 同时 minimist 是 node-optimist 的核心解析模块它的主要作为即为解析执行 Node 脚本时的环境变量。 填充构建 接下来我们就来填充构建部分逻辑。 更改 package.json 首先让我们切换到项目跟目录下对于整个 repo 的 pacakge.json 进行改造。 { name: vue, version: 1.0.0, description: , main: index.js, scripts: { dev: node ./scripts/dev.js reactivity -f global } , keywords: [], author: , license: ISC, devDependencies: { esbuild: ^0.14.27, typescript: ^4.6.2, vue: ^3.2.31 } } 首先我们将包名称修改为作用域vue 表示该包是一个组织包。 其次我们修改 scripts 脚本。表示当运行 pnpm run dev 时会执行 ./scripts/dev.js 同时传入一个 reactivity 参数以及 -f 的 global 环境变量。 更改项目内 package.json 接下来我们需要更改每个 repop 内的 package.json以下简称 pck 。这里我们以 reactivity 模块为例share 我就不重复讲解了。 { name: vue/reactive, version: 1.0.0, description: , main: index.js, buildOptions: { name: VueReactivity, formats: [ esm-bundler, esm-browser, cjs, global ] } , keywords: [], author: , license: ISC } 首先我们将 reactivity 包中的名称改为作用域名 vue/reactive 。 其次我们为 pck 中添加了一些自定义配置分别为 buildOptions.name 该选项表示打包生成 IIFE 时该模块挂载在全局下的变量名。 buildOptions.formats 该选项表示该模块打包时候需要输出的模块规范。 填充scripts/dev.js 之后让我们切换到 scripts/dev.js 来实现打包逻辑 // scripts/dev.js const { build }require(esbuild); const { resolve }require(path); const argv require(minimist)(process.argv.slice(2)); // 获取参数 minimist const target argv[_]; const format argv[f]; const pkg require(resolve(__dirname, ../packages/reactivity/package.json)); const outputFormat format.startsWith(global) ? iife : format.startsWith(cjs) ? cjs : esm; // 打包输出文件 const outfile resolve( __dirname, ../packages/$ { target } /dist/$ { target } .$ { outputFormat } .js ); // 调用ESbuild的NodeApi执行打包 build( { entryPoints: [resolve(__dirname, ../packages/$ { target } /src/index.ts)], outfile, bundle: true, // 将所有依赖打包进入 sourcemap: true, // 是否需要sourceMap format: outputFormat, // 输出文件格式 IIFE、CJS、ESM globalName: pkg.buildOptions?.name, // 打包后全局注册的变量名 IIFE下生效 platform: outputFormat cjs ? node : browser, // 平台 watch: true, // 表示检测文件变动重新打包 } ); 脚本中的已经进行了详细的注释这里我稍微在啰嗦一些。 其次整个流程看来像是这样首先当我们运行 npm run dev 时相当于执行了 node ./scripts/dev.js reactivity -f global。 所以在执行对应 dev.js 时我们通过 minimist 获得对应的环境变量 target 和 format 表示我们本次打包分别需要打包的 package 和模式当然你也可以通过 process.argv 自己截取。 之后我们通过判断如果传入的 -f 为 global 时将它变成 iife 模式执行 esbuild 的 Node Api 进行打包对应的模块。 需要注意的是ESbuild 默认支持 typescript 所以不需要任何额外处理。 当然我们此时并没有在每个包中创建对应的入口文件。让我们分别创建两个 packages/reactivity/src/index.ts以及packages/share/src/index.ts作为入口文件。 此时当你运行 npm run dev 时会发现会生成打包后的js文件 image.png 写在环境结尾的话 至此针对于一个简易版 Vuejs 的项目构建流程我们已经初步实现了。如果有兴趣深入了解这个完整流程的同学可以自行查看对应 源码。 当然这种根据环境变量进行动态打包的思想我在之前的React-Webpack5-TypeScript打造工程化多页面应用中详细讲解过这一思路有兴趣的同学可以自行查阅。 其实关于构建思路我大可不必在这里展开直接讲述响应式部分代码即可。但是这一流程在我的日常工作中的确帮助过我在多页面应用业务上进行了项目构建优化。 所以我觉得还是有必要拿出来和大家稍微聊一聊这一过程希望大家在以后业务中遇到该类场景下可以结合 Vuejs 的构建思路来设计你的项目构建流程。 响应式原理 上边我们对于构建稍稍花费了一些篇幅接下来终于我们要步入正题进行响应式原理部分了。 首先在开始之前我会稍微强调一些。文章中的代码并不是一比一对照源码来实现响应式原理但是实现思想以及实现过程是和源码没有出入的。 这是因为源码中拥有非常多的条件分支判断和错误处理同时源码中也考虑了数组、Set、Map 之类的数据结构。 这里我们仅仅先考虑基础的对象至于其他数据类型我会在之后的文章中详细和大家一一道来。 同时我也会在每个步骤的结尾贴出对应的源代码地址提供给大家参照源码进行对比阅读。 开始之前 在我们开始响应式原理之前我想和大家稍微阐述下对应背景。因为可能有部分同学对应 Vue3 中的源码并不是很了解。 在 VueJs 中的存在一个核心的 Api Effect 这个 Api 在 Vue 3.2 版本之后暴露给了开发者去调用在3.2之前都是 Vuejs 内部方法并不提供给开发者使用。 简单来说我们所有模版组件最终都会被 effect 包裹 当数据发生变化时 Effect 会重新执行所以 vuejs 中的响应式原理可以说是基于 effect 来实现的 。 当然这里你仅仅需要了解最终组件是会编译成为一个个 effect 当响应式数据改变时会触发 effect 函数重新执行从而更新渲染页面即可。 之后我们也会详细介绍 effect 和 响应式是如何关联到一起的。 基础目录结构 首先我们来创建一些基础的目录结构 reactivity/src/index.ts 用于统一引入导出各个模块 reactivity/src/reactivity.ts 用于维护 reactive 相关 Api。 reactivity/src/effect.ts 用户维护 effect 相关 Api。 这一步我们首先在 reactivity 中新建对应的文件 image.png reactive 基础逻辑处理 接下来我们首先进入相关的 reactive.ts 中去。 思路梳理 关于 Vuejs 是如何实现数据响应式简单来说它内部利用了 Proxy Api 进行了访问/设置数据时进行了劫持。 对于数据访问时需要进行依赖收集。记录当前数据中依赖了哪些 Effect 当进行数据修改时候同样会进行触发更新重新执行当前数据依赖的 Effect。简单来说这就是所谓的响应式原理。 关于 Effect 你可以暂时的将它理解成为一个函数当数据改变函数Effect重新执行从而函数执行导致页面重新渲染。 Target 实现目标 在开始书写代码之前我们先来看看它的用法。我们先来看看 reactive 方法究竟是如何搭配 effect 进行页面的更新不太了解 Effect 和响应式数据的同学可以将这段代码放在浏览器下执行试试看。 首先我们使用 reactive Api 创建了一个响应式数据 reactiveData 。 之后我们创建了一个 effect它会接受一个 fn 作为参数 。这个 effect 内部的逻辑非常简单它将 id 为 app 元素的内容置为 reactiveData.name 的值。 注意这个 effect 传入的 fn 中依赖了响应式数据 reactiveData 的 name 属性这一步通常成为依赖收集。 当 effect 被创建时fn 会被立即执行所以 app 元素会渲染对应的 19Qingfeng 。 当 0.5s 后 timer 达到时间我们修改了 reactiveData 响应式数据的 name 属性此时会触发改属性依赖的 effct 重新执行这一步同样通常被称为触发更新。 所以页面上看起来的结果就是首先渲染出 19Qingfeng 在 0.5s 后由于响应式数据的改变导致 effect 重新执行所以修改了 app 的 innerHTML 导致页面重新渲染。 这就是一个非常简单且典型的响应式数据 Demo 之后我们会一步一步基于结果来逆推实现这个逻辑。 基础 Reactive 方法实现 接下来我们先来实现一个基础版的 Reactive 方法具体使用 API 你可以参照 这里。 上边我们提到过 VueJs 中针对于响应式数据本质上就是基于 Proxy Reflect 对于数据的劫持那么自然我们会想到这样的代码 // reactivity/src/reactivity.ts export function isPlainObj(value: any): value is object { return typeof value object value ! null; } const reactive (obj) { // 传入非对象 if (!isPlainObj(obj)) { return obj; } // 声明响应式数据 const proxy new Proxy(obj, { get() { // dosomething } , set() { // dosomething } } ); return proxy; } ; 上边的代码非常简单我们创建了一个 reactive 对象它接受传入一个 Object 类型的对象。 我们会对于函数传入的 obj 进行校验如果传入的是 object 类型那么会直接返回。 接下来我们会根据传入的对象 obj 创建一个 proxy 代理对象。并且会在该代理对象上针对于 get 陷阱访问对象属性时以及 set 修改代理对象的值时进行劫持从而实现一系列逻辑。 依赖收集 之前我们提到过针对于 reactive 的响应式数据会在触发 get 陷阱时会进行依赖收集。 这里你可以简单将依赖收集理解为记录当前数据被哪些Effect使用到之后我们会一步一步来实现它。 // reactivity/src/reactivity.ts export function isPlainObj(value: any): value is object { return typeof value object value ! null; } const reactive (obj) { // 传入非对象 if (!isPlainObj(obj)) { return obj; } // 声明响应式数据 const proxy new Proxy(obj, { get(target, key, receiver) { // 依赖收集方法 track track(target, get, key); // 调用 Reflect Api 获得原始的数据 你可以将它简单理解成为 target[key] let result Reflect.get(target, key, receiver); // 依赖为对象 递归进行reactive处理 if (isPlainObj(result)) { return reactive(result); } // 配合Reflect解决当访问get属性递归依赖this的问题 return result; } , set() { // dosomething } } ); return proxy; } ; 上边我们填充了在 Proxy 中的 get 陷阱的逻辑 当访问响应式对象 proxy 中的属性时首先会针对于对应的属性进行依赖收集。主要依靠的是 track 方法。 之后如果访问该响应式对象 key 对应的 value 仍为对象时会再次递归调用 reactive 方法进行处理。 需要注意的是递归进行 reactive 时是一层懒处理换句话说只有访问时才会递归处理并不是在初始化时就会针对于传入的 obj 进行递归处理。 当然这里的依赖收集主要依靠的就是 track 方法我们会在稍后详解实现这个方法。 依赖收集 接下来我们来看看 set 陷阱中的逻辑当触发对于 proxy 对象的属性修改时会触发 set 陷阱从而进行触发对应 Effect 的执行。 我们来一起看看对应的 set 陷阱中的逻辑 // reactivity/src/reactivity.ts export function isPlainObj(value: any): value is object { return typeof value object value ! null; } const reactive (obj) { // 传入非对象 if (!isPlainObj(obj)) { return obj; } // 声明响应式数据 const proxy new Proxy(obj, { get(target, key, receiver) { // 依赖收集方法 track track(target, get, key); // 调用 Reflect Api 获得原始的数据 你可以将它简单理解成为 target[key] let result Reflect.get(target, key, receiver); // 依赖为对象 递归进行reactive处理 if (isPlainObj(result)) { return reactive(result); } // 配合Reflect解决当访问get属性递归依赖this的问题 return result; } , // 当进行设置时进行触发更新 set(target, key, value, receiver) { const oldValue target[key]; // 配合Reflect解决当访问get属性递归依赖this的问题 const result Reflect.set(target, key, value, receiver); // 如果两次变化的值相同 那么不会触发更新 if (value ! oldValue) { // 触发更新 trigger(target, set, key, value, oldValue); } return result; } , } ); return proxy; } ; 同样我们在上边填充了对应 set 陷阱之中的逻辑当设置响应式对象时会触发对应的 set 陷阱。我们会在 set 陷阱中触发对应的 trigger 逻辑进行触发更新将依赖的 effect 重新执行。 关于为什么我们在使用 Proxy 时需要配合 Refelct 我在这篇文章有详细讲解。感兴趣的朋友可以查看这里 [为什么Proxy一定要配合Reflect使用]。 上边我们完成了 reactive.ts 文件的基础逻辑遗留了两个核心方法 track trigger 方法。 在实现着两个方法之前我们先来一起看看 effect 是如何被实现的。 effect 文件 effect 基础使用 让我们把视野切到 effcet.ts 中我们稍微来回忆一下 effect Api 的用法 const { reactive, effect }Vue const obj { name: 19Qingfeng } // 创建响应式数据 const reactiveData reactive(obj) // 创建effect依赖响应式数据 effect(() { app.innerHTML reactiveData.name } ) effect 基础原理 上边我们看到effect Api 有以下特点 effect 接受一个函数作为入参。 当调用effect(fn) 时内部的函数会直接被调用一次。 其次当 effect 中的依赖的响应式数据发生改变时。我们期望 effect 会重新执行比如这里的 effect 依赖了 reactiveData.name 上的值。 接下来我们先来一起实现一个简单的 Effect Api function effect(fn) { // 调用Effect创建一个的Effect实例 const _effect new ReactiveEffect(fn); // 调用Effect时Effect内部的函数会默认先执行一次 _effect.run(); // 创建effect函数的返回值_effect.run() 方法同时绑定方法中的this为_effect实例对象 const runner _effect.run.bind(_effect); // 返回的runner函数挂载对应的_effect对象 runner.effect _effect; return runner; } 这里我们创建了一个基础的 effect Api可以看到它接受一个函数 fn 作为参数。 当我们运行 effect 时会创建一个 const _effect new ReactiveEffect(fn); 对象。 同时我们会调用 _effect.run() 这个实例方法立即执行传入的 fn 之所以需要立即执行传入的 fn 我们在上边提到过当代码执行到 effect(fn) 时实际上会立即执行 fn 函数。 我们调用的 _effect.run() 实际内部也会执行 fn 我们稍微回忆下上边的 Demo 当代码执行 effect(fn) 时候相当于执行了: // ... effect(() { app.innerHTML reactiveData.name } ) 会立即执行传入的 fn 也就是 () { app.innerHTML reactiveData.name } 会修改 app 节点中的内容。 同时我们之前提到过因为 reactiveData 是一个 proxy 代理对象当我们访问它的属性时实际上会触发它的 get 陷阱。 // effect.ts export let activeEffect; export function effect(fn) { // 调用Effect创建一个的Effect实例 const _effect new ReactiveEffect(fn); // 调用Effect时Effect内部的函数会默认先执行一次 _effect.run(); // 创建effect函数的返回值_effect.run() 方法同时绑定方法中的this为_effect实例对象 const runner _effect.run.bind(_effect); // 返回的runner函数挂载对应的_effect对象 runner.effect _effect; return runner; } /** * Reactive Effect */ export class ReactiveEffect { private fn: Function; constructor(fn) { this.fn fn; } run() { try { activeEffect this; // run 方法很简单 就是执行传入的fn return this.fn(); } finally { activeEffect undefined } } } 这是一个非常简单的 ReactiveEffect 实现它的内部非常简单就是简单的记录了传入的 fn 同时拥有一个 run 实例方法当调用 run 方法时会执行记录的 fn 函数。 同时我们在模块内部声明了一个 activeEffect 的变量。当 我们调用运行 effect(fn) 时实际上它会经历以下步骤 首先用户代码中调用 effect(fn) VueJs 内部会执行 effect 函数同时创建一个 _effect 实例对象。立即调用 _effect.run() 实例方法。 重点就在所谓的 _effect.run() 方法中。 首先当调用 _effect.run() 方法时我们会执行 activeEffect this 将声明的 activeEffect 变成当前对应的 _effect 实例对象。 同时run() 方法接下来会调用传入的 fn() 函数。 当 fn() 执行时如果传入的 fn() 函数存在 reactive() 包裹的响应式数据那么实际上是会进入对应的 get 陷阱中。 当进入响应式数据的 get 陷阱中时不要忘记我们声明全局的 activeEffect 变量我们可以在对应响应式数据的 get 陷阱中拿到对应 activeEffect (也就是创建的 _effect) 变量。 接下来我们需要做的很简单: 在响应式数据的 get 陷阱中记录该数据依赖到的全局 activeEffect 对象(_effect)依赖收集也就是我们之前遗留的 track 方法。 同时: 当改变响应式数据时我们仅仅需要找出当前对应的数据依赖的 _effect 修改数据同时重新调用 _effect.run() 相当于重新执行了 effectfn中的 fn。那么此时不就是相当于修改数据页面自动更新吗这一步就被称为依赖收集也就是我们之前遗留的 trigger 方法。 track trigger 方法 让我们会回到之前遗留的 track 和 trigger 逻辑中接下来我们就尝试去实现它。 这里我们将在 effect.ts 中来实现这两个方法将它导出提供给 reactive.ts 中使用。 思路梳理 上边我们提到过核心思路是当代码执行到 effect(fn) 时内部会调用对应的 fn 函数执行。当 fn 执行时会触发 fn 中依赖的响应式数据的 get 当 get 触发时我们记录到对应 声明的(activeEffect) _effect 对象和对应的响应式数据的关联即可。 当响应式数据改变时我们取出关联的 _effect 对象重新调用 _effect.run() 重新执行 effect(fn) 传入的 fn 函数即可。 看到这里一些同学已经反应过来了。我们有一份记录对应 activeEffect(_effect) 和 对应的响应式数据的表于是我们自然而然的想到使用一个 WeakMap 来存储这份关系。 之所以使用 WeakMap 来存储第一个原因自然是我们需要存储的 key 值是非字符串类型这显然只有 map 可以。其次就是 WeakMap 的 key 并不会影响垃圾回收机制。 创建映射表 上边我们分析过我们需要一份全局的映射表来维护 _effect 实例和依赖的响应式数据的关联 于是我们自然想到通过一个 WeakMap 对象来维护映射关系那么如何设计这个 WeakMap 对象呢这里我就不卖关子了。 我们再来回忆下上述的 Demo // ... const { reactive, effect }Vue const obj { name: 19Qingfeng } // 创建响应式数据 const reactiveData reactive(obj) // 创建effect依赖响应式数据 effect(() { app.innerHTML reactiveData.name } ) // 上述Demo的基础上增加了一个effect依赖逻辑 effect(() { app2.innerHTML reactiveData.name } ) 首先针对于响应式数据 reactiveData 它是一个对象上述代码中的 effect 中依赖了 reactiveData 对象上的 name 属性。 所以我们仅仅需要关联当前响应式对象中的 name 属性和对应 effect 即可。 同时针对于同一个响应式对象的属性比如这里的 name 属性被多个 effect 依赖。自然我们可以想到一份响应式数据的属性可以和多个 effect 依赖。 根据上述的分析最终 Vuejs 中针对于这份映射表设计出来了这样的结构 当一个 effect 中依赖对应的响应式数据时比如上述 Demo 我们创建的全局的 WeakMap 首先会将响应式对象的原始对象未代理前的对象作为 key value 为一个 Map 对象。 同时 effect 内部使用了上述对象的某个属性那么此时 WeakMap 对象的该对象的值我们刚才创建的 Map 。我们会在这个 Map 对象中设置 key 为使用到的属性value 为一个 Set 对象。 为什么对应属性的值为一个 Set 这非常简单。因为该属性可能会被多个 effect 依赖到。所以它的值为一个 Set 对象当该属性被某个 effect 依赖到时会将对应 _effect 实例对象添加进入 Set 中。 也许有部分同学乍一看对于这份映射表仍然比较模糊没关系接下来我会用代码来描述这一过程。你可以结合代码和这段文字进行一起理解。 track 实现 接下来我们来看看 track 方法的实现 // *用于存储响应式数据和Effect的关系Hash表 const targetMap new WeakMap(); /** * 依赖收集函数 当触发响应式数据的Getter时会进入track函数 * param target 访问的原对象 * param type 表示本次track从哪里来 * param key 访问响应式对象的key */ export function track(target, type, key) { // 当前没有激活的全局Effect 响应式数据没有关联的effect 不用收集依赖 if (!activeEffect) { return; } // 查找是否存在对象 let depsMap targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap new Map())); } // 查找是否存在对应key对应的 Set effect let deps depsMap.get(key); if (!deps) { depsMap.set(key, (deps new Set())); } // 其实Set本身可以去重 这里判断下会性能优化点 const shouldTrack !deps.has(activeEffect) activeEffect; if (shouldTrack) { // *收集依赖将 effect 进入对应WeakMap中对应的target中对应的keys deps.add(activeEffect); } } 我们一行一行分析上边的 track 方法这个方法我们之前提到过。它是在 reactive.ts中对于某个响应式属性进行依赖收集触发proxy的 get 陷阱时触发的忘记了的小伙伴可以翻回去重新看下。 首先它会判断当前 activeEffect 是否存在所谓 actvieEffect 也就是当前是否存在 effect 。换句话说比如这样 // ... app.innerHTML reactiveData.name 那么我们有必要进行依赖收集吗虽然 reactiveData 是一个响应式数据这不假但是我们并没有在模板上使用它。它并不存在任何关联的 effect 所以完全没有必要进行依赖收集。 而在这种情况下: effect(() { app.innerHTML reactiveData.name } ) 只有我们在 effect(fn) 中当 fn 中使用到了对应的响应式数据。简单来说也就是 activeEffect 存在值得时候对于响应式数据的依赖收集才有意义。 其次接下来会去全局的 targetMap 中寻找是否已经存在对应响应式数据的原始对象 - depsMap 。如果该对象首次被收集那么我们需要在 targetMap 中设置 key 为 target value 为一个新的Map。 // 查找是否存在对象 let depsMap targetMap.get(target); if (!depsMap) { // 不存在则创建一个Map作为value将target作为key放入depsMap中 targetMap.set(target, (depsMap new Map())); } 同时我们会继续去上一步返回的 deps 此时的 deps 是一个 Map 。它的内部会记录改对象中被进行依赖收集的属性。 我们回去寻找 name 属性是否存在显然它是第一次进行依赖收集。所以会进行 // 查找是否存在对应key对应的 Set effect let deps depsMap.get(key); if (!deps) { // 同样不存在则创建set放入 depsMap.set(key, (deps new Set())); } 此时比如上方的 Demo 中当代码执行到 effect 中的 fn 碰到响应式数据的 get 陷阱时触发 track 函数。 我们会为全局的 targetMap 对象中首先设置 key 为 obj reactiveData的原始对象value 为一个 Map 。 其次我们会为该创建的 Map 中再次进行设置 key 为该响应式对象需要被收集的属性也就是我们在 effect 中访问到该对象的 name value 为一个 Set 集合。 接下里 Set 中存放什么其实很简单我们仅需在对应 Set 中记录当前正在运行的 effct 实例对象也就是 activeEffct 就可以达到对应的依赖收集效果。 此时targetMap 中就会存放对应的对象和关联的 effect 了。 trigger 实现 当然上述我们已经通过对应的 track 方法收集了相关响应式数据和对应它依赖的 effect 。 那么接下来如果当改变响应式数据时触发 set 陷阱时自然我们仅仅需要找到对应记录的 effect 对象调用它的 effect.run() 重新执行不就可以让页面跟随数据改变而改变了吗。 我们来一起看看 trigger 方法 // ... effect.ts /** * 触发更新函数 * param target 触发更新的源对象 * param type 类型 * param key 触发更新的源对象key * param value 触发更新的源对象key改变的value * param oldValue 触发更新的源对象原始的value */ export function trigger(target, type, key, value, oldValue) { // 简单来说 每次触发的时 我拿出对应的Effect去执行 就会触发页面更新 const depsMap targetMap.get(target); if (!depsMap) { return; } let effects depsMap.get(key); if (!effects) { return; } effects new Set(effects); effects.forEach((effect) { // 当前zheng if (activeEffect ! effect) { // 默认数据变化重新调用effect.run()重新执行清空当前Effect依赖重新执行Effect中fn进行重新收集依赖以及视图更新 effect.run(); } } ); } 接下来我们在 effect.ts 中来补充对应的 trigger 逻辑其实 trigger 的逻辑非常简单。每当响应式数据触发 set 陷阱进行修改时会触发对应的 trigger 函数。 他会接受对应的 5 个 参数我们在函数的注释中已经标明了对应的参数。 当触发响应式数据的修改时首先我们回去 targetMap 中寻找 key 为对应原对象的值自然因为在 track 中我们已经保存了对应的值所以当然可以拿到一个 Map 对象。 因为该 Map 对象中存在对应 key 为 name 值为该属性依赖的 effect 的 Set 集合所以我们仅需要依次拿出对应修改的属性比如我们调用 // ... const { reactive, effect }Vue const obj { name: 19Qingfeng } // 创建响应式数据 const reactiveData reactive(obj) // 创建effect依赖响应式数据 effect(() { app.innerHTML reactiveData.name } ) // 修改响应式数据 触发set陷阱 reactiveData.name wang.haoyu 当我们调用 reactiveData.name wang.haoyu 时我们会一层一层取到 targetMap 中 key 为 obj 的 depsMapMap 对象。 再从 depsMap 中拿到 key 为 name 属性的 Set 集合Set 中保存该响应式对象属性依赖的 effect。 迭代当前 Set 中的所有 effect 进行 effect.run() 重新执行 effect 对象中记录的 fn 函数。 因为我们在 reactive.ts 中的 set 陷阱中对于数据已经修改之后调用了 trigger 方法trigger 导致重新执行 effect(fn) 中的 fn所以自然而然 fn() 重新执行 app.innerHTML 就会变成最新的 wang.haoyu 。 整个响应式核心原理其实一点都不难对吧核心思想还是文章开头的那句话对于数据访问时需要进行依赖收集。记录当前数据中依赖了哪些 Effect 当进行数据修改时候同样会进行触发更新重新执行当前数据依赖的 Effect。 阶段总结 其实写到这里已经 8K 多字了原本打算是和大家过一遍整个 Vue 3.2 中关于 reactivity 的逻辑包括各种边界情况。 比如文章中的代码其实仅仅只能说是实现了一个乞丐版的响应式原理其他一些边界情况比如 多个 effect 嵌套时的处理。 多次 reactive 调用同一对象或者对于已经 reactive 包裹的响应式对象。 每次触发更新时对于前一次依赖收集的清理。 shallow、readonly 情况等等... 这些边界情况其实文章中的代码我都没有考虑如果后续有小伙伴对这方面感兴趣我会再次开一篇文章去继续这次的代码去实现一个完整的 reactive 方法。 不过透过现象看本质。VueJs 中所谓主打的数据响应式核心原理即是文章中代码所表现的思想。 我在这个代码地址也自己实现了一版比较完整的精简版 reactivity 模块有兴趣的同学可以自行查阅。 当然你也可以直接参照源代码进行阅读。毕竟 Vue 3 的代码相较于 2 来说其实已经很人性化了。面试题 12 . 请描述Vue的实现原理 参考回答 vue.js 是采用数据劫持结合发布者-订阅者模式的方式通过Object.defineProperty()来劫持各个属性的settergetter在数据变动时发布消息给订阅者触发相应的监听回调。具体步骤第一步需要observe的数据对象进行递归遍历包括子属性对象的属性都加上setter和getter这样的话给这个对象的某个值赋值就会触发setter那么就能监听到了数据变化第二步compile解析模板指令将模板中的变量替换成数据然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数添加监听数据的订阅者一旦数据有变动收到通知更新视图第三步Watcher订阅者是Observer和Compile之间通信的桥梁主要做的事情是:1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时能调用自身的update()方法并触发Compile中绑定的回调则功成身退。 第四步MVVM作为数据绑定的入口整合Observer、Compile和Watcher三者通过Observer来监听自己的model数据变化通过Compile来解析编译模板指令最终利用Watcher搭起Observer和Compile之间的通信桥梁达到数据变化 - 视图更新视图交互变化(input) - 数据model变更的双向绑定效果。面试题 13 . 请简述什么是Vue的自定义指令 参考回答 自定义指令分为全局指令和组件指令其中全局指令需要使用directive来进行定义组件指令需要使用directives来进行定义具体定义方法同过滤器filter或者其他生命周期具体使用方法如下全局自定义指令 directive(name,{})其中name表示定义的指令名称定义指令的时候不需要带v-但是在调用的时候需要哦带v-第二个参数是一个对象对象中包括五个自定义组件的钩子函数具体包括bind函数只调用一次指令第一次绑定在元素上调用即初始化调用一次 inserted函数并绑定元素插入父级元素即new vue中el绑定的元素时调用此时父级元素不一定转化为了dom update函数在元素发生更新时就会调用可以通过比较新旧的值来进行逻辑处理 componentUpdated函数元素更新完成后触发一次 unbind函数在元素所在的模板删除的时候就触发一次 钩子函数对应的参数el,binding,vnode,oldnode,具体参数讲解如下a、el指令所绑定的元素 可以直接操组dom元素b、binding一个对象具体包括以下属性1name定义的指令名称 不包括v- 2value指令的绑定值如果绑定的是一个计算式value为对应计算结果 3oldvalue指令绑定元素的前一个值只对update和componentUpdated钩子函数有值 4expression指令绑定的原始值 不对值进行任何加工 5arg传递给指令的参数 6modifiers指令修饰符如v-focus.show.async 则接收的modifiers为showtrueasynctrue c、vnodevue编译生成的虚拟domd、oldVnode上一个vnode只在update和componentUpdated钩子函数中有效⚠️如果不需要其他钩子函数可以直接简写为directive(“focus”,function(el,binding){})面试题 14 . 简述对于Vue的diff算法理解 参考回答 1diff算法的作用用来修改dom的一小段不会引起dom树的重绘2diff算法的实现原理diff算法将virtual dom的某个节点数据改变后生成的新的vnode与旧节点进行比较并替换为新的节点具体过程就是调用patch方法比较新旧节点一边比较一边给真实的dom打补丁进行替换3具体过程详解a、在采用diff算法进行新旧节点进行比较的时候比较是按照在同级进行比较的不会进行跨级比较b、当数据发生改变的时候set方法会调用dep.notify通知所有的订阅者watcher订阅者会调用patch函数给响应的dom进行打补丁从而更新真实的视图c、patch函数接受两个参数第一个是旧节点第二个是新节点首先判断两个节点是否值得比较值得比较则执行patchVnode函数不值得比较则直接将旧节点替换为新节点。如果两个节点一样就直接检查对应的子节点如果子节点不一样就说明整个子节点全部改变不再往下对比直接进行新旧节点的整体替换d、patchVnode函数找到真实的dom元素判断新旧节点是否指向同一个对象如果是就直接返回如果新旧节点都有文本节点那么直接将新的文本节点赋值给dom元素并且更新旧的节点为新的节点如果旧节点有子节点而新节点没有则直接删除dom元素中的子节点如果旧节点没有子节点新节点有子节点那么直接将新节点中的子节点更新到dom中如果两者都有子节点那么继续调用函数updateChildrene、updateChildren函数抽离出新旧节点的所有子节点并且设置新旧节点的开始指针和结束指针然后进行两辆比较从而更新dom调整顺序或者插入新的内容 结束后删掉多余的内容面试题 15 . 请叙述Vue与React、Angular的比较 参考回答 Vue 轻量级框架只关注视图层是一个构建数据的视图集合大小只有几十kb 简单易学国人开发中文文档不存在语言障碍 易于理解和学习 双向数据绑定保留了angular的特点在数据操作方面更为简单 组件化保留了react的优点实现了html的封装和重用在构建单页面应用方面有着独特的优势视图数据结构分离使数据的更改更为简单不需要进行逻辑代码的修改只需要操作数据就能完成相关操作 虚拟DOMdom操作是非常耗费性能的 不再使用原生的dom操作节点极大解放dom操作但具体操作的还是dom不过是换了另一种方式 运行速度更快:相比较与react而言同样是操作虚拟dom就性能而言vue存在很大的优势。React 相同点 React采用特殊的JSX语法Vue.js在组件开发中也推崇编写.vue特殊文件格式对文件内容都有一些约定两者都需要编译后使用中心思想相同一切都是组件组件实例之间可以嵌套都提供合理的钩子函数可以让开发者定制化地去处理需求都不内置列数AJAXRoute等功能到核心包而是以插件的方式加载在组件开发中都支持mixins的特性。不同点 React采用的Virtual DOM会对渲染出来的结果做脏检查Vue.js在模板中提供了指令过滤器等可以非常方便快捷地操作Virtual DOM。Angular 相同点 都支持指令内置指令和自定义指令都支持过滤器内置过滤器和自定义过滤器都支持双向数据绑定都不支持低端浏览器。不同点 AngularJS的学习成本高比如增加了Dependency Injection特性而Vue.js本身提供的API都比较简单、直观在性能上AngularJS依赖对数据做脏检查所以Watcher越多越慢Vue.js使用基于依赖追踪的观察并且使用异步队列更新所有的数据都是独立触发的面试题 16 . 请简述Vuex的使用 参考回答 vuex 是什么 ? vuex 是一个专为 Vue 应用程序开发 的状态管理器 采用集中式 存储管理 应用的所有组件的状态。每 一个 vuex 应用的核心就是 store仓库。“store” 基本上就是一个容器它包含着应用中大部分 的状态 (state)。 为什么需要 vuex 由于组件只维护自身的状态(data)组件创建时或者路由切换时组件会被初始化从而导致 data 也 随之销毁。 使用方法 在 main.js 引入 store注入。只用来读取的状态集中放在 store 中 改变状态的方式是提交 mutations这是个同步的事物异步逻辑应该封装在 action 中。 什么场景下会使用到 vuex 如果是 vue 的小型应用那么没有必要使用 vuex这个时候使用 vuex 反而会带来负担。组件之间的 状态传递使用 props、自定义事件来传递即可。 但是如果 涉及到 vue 的大型应用 那么就需要类似于 vuex 这样的集中管 理状态的状态机来管理所有 组件的状态。例如登录状态、加入购物车、音乐播放等总之只要是开发 vue 的大型应用都推荐使 用 vuex 来管理所有组件状态 主要包括以下几个模块 State:定义了应用状态的数据结构可以在这里设置默认的初始化状态。 Getter:允许组件从Store中获取数据mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。 Mutation:是唯一更改 store 中状态的方法且必须是同步函数。 Action:用于提交 mutation而不是直接变更状态可以包含任意异步请求。 Module:允许将单一的 Store 拆分更多个 store 且同时保存在单一的状态树中面试题 17 . 请叙述Vue 中使用了哪些设计模式 参考回答 1 工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode。 2 单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件3注册方法 install 判断如果系统存在实例就直接返回掉。 3 发布-订阅模式。vue 事件机制 4 观察者模式。响应式数据原理 5 装饰器模式装饰器的用法 6 策略模式策略模式指对象有某个行为但是在不同的场景中该行为有不同的实现方案 - 比如选项的合并策略。面试题 18 . 简单描述Vue的组件渲染流程 参考回答 1、给组件创建个构造函数基于Vue。 export default function globalApiMixin(Vue){ Vue.options {} Vue.mixin function (options){ this.options mergeOptions(this.options,options);//合并options } Vue.options.components {}; Vue.options._base Vue Vue.component function (id,definition){ //保证组件的隔离每个组件都会产生一个新的类去继承父类 definition this.options._base.extend(definition); this.options.components[id] definition; } //给个对象返回类 Vue.extend function (definition){//extend方法就是返回一个继承于Vue的类 //并且身上应该有父类的所有功能 let Super this; let Sub function VueComponent(options){ this._init(options); } //原型继承 Sub.prototype Object.create(Super.prototype); Sub.prototype.constructor Sub; Sub.options mergeOptions(Super.options, definition);//只和vue.options合并 return Sub } } 2、开始生成虚拟节点对组件进行特殊处理 data.hook {init(){}} export function createElement(vm, tag, data {}, ...children) { if(isReservedTag(tag)){ return vnode(vm, tag, data, data.key, children, undefined); }else{ const Ctor vm.$options.components[tag]; return createComponent(vm, tag, data, data.key, undefined, undefined,Ctor); } } function createComponent(vm, tag, data, key, children, text, Ctor) {if(isObject(Ctor)){ Ctor vm.$options._base.extend(Ctor) } data.hook { init(vnode){ let vm vnode.componentInstance new Ctor({_isComponent:true})//new sub debugger vm.$mount(); } } return vnode(vm,vue-component-${tag},data,key,undefined,undefined,{Ctor,children}); } export function createTextElement(vm, text) { return vnode(vm, undefined, undefined, undefined, undefined, text); } function vnode(vm, tag, data, key, children, text,componentOptions) { return { vm, tag, data, key, children, text, componentOptions }; }function isReservedTag(str){ //判断是否是组件 let strList a,div,span,p,ul,li; return strList.includes(str); } 3、生成dom元素如果当前虚拟节点上有hook.init属性说明是组件 function createComponent(vnode){ let i vnode.data; if((i i.hook) (i i.init)){ i(vnode);//调用init方法 } if (vnode.componentInstance) { //有属性说明子组件new完毕了并且组件的真实dom挂载到了vnode。componentInstance return true; } } function createElm(vnode){ debugger let {vm,tag,data,children,text} vnode; if(typeof tag string){ //判断是否是组件 if( createComponent(vnode)){ //返回组件对应的真实节点 console.log(vnode.componentInstance.$el); return vnode.componentInstance.$el } vnode.el document.createElement(tag); if(children.length){ children.forEach(child{ vnode.el.appendChild(createElm(child)); }) } }else{ vnode.el document.createTextNode(text); } return vnode.el; } 4、对组件进行new 组件().$mount()vm.$el; 将组件的$el插入到父容器中 父组件Vue.prototype.$mount function (el) { debugger const vm this; const options vm.$options; el document.querySelector(el); vm.$el el; //将模板转化成对应的渲染函数》虚拟函数概念 vnode 》diff算法更新虚拟 dom 》产生真实节点更新 if (!options.render) { //没有render 用template目前没有render let template options.template; if (!template el) { //用户也没有传入template就取页面上的el template el.outerHTML; } let render compileToFunction(template); //options.render 就是渲染函数 options.render render; } debugger mountComponent(vm, el); //组件的挂载流程 };面试题 19 . 请说明Vue key的作用及原理 参考回答 key是虚拟DOM对象的标识当数据发生变化时Vue会根据[新数据]生成[新的虚拟DOM]随后Vue进行[新虚拟DOM]与[旧虚拟DOM]的差异比较 原理比较规则 1.旧虚拟DOM中找到了与新虚拟DOM相同的key: a.若虚拟DOM中内容没变直接使用之前的直DOM! b.若虚拟DOM中内容变了则生成新的真实DOM随后替换掉页面中之前的真实DOM。 2.旧虚拟DOM中未找到与新虚拟DOM相同的key: a.创建新的真实DOM随后渲染到到页面。index作为key可能会引发的问题: 1.若对数据进行:逆序添加、逆序删除等破坏顺序操作: a.会产生没有必要的真实DOM更新 界面效果没问题但效率低。 2.如果结构中还包含输入类的DOM: a.会产生错误DOM更新界面有问题面试题 20 . 请说明Vue的filter的理解与用法 参考回答 1全局过滤器必须写在vue实例创建之前Vue.filter(testfilter, function (value,text) { // 返回处理后的值 return valuetext }) 2局部写法在组件实例对象里挂载。filters: { changemsg:(val,text)\{ return val text } } 3使用方式只能使用在{{}}和v-bind中定义时第一个参数固定为预处理的数后面的数为调用时传入的参数调用时参数第一个对应定义时第二个参数依次往后类推{{test|changemsg(4567)}}//多个过滤器也可以串行使用 {{name|filter1|filter2|filter3}}4vue-cli项目中注册多个全局过滤器写法//1.创建一个单独的文件定义并暴露函数对象 const filter1 function (val) { return val --1 } const filter2 function (val) { return val --2 } const filter3 function (val) { return val --3 }export default { filter1, filter2, filter3 }//2.导入main.js(在vue实例之前) import filters from ./filter/filter.js//3.循环注册过滤器 Object.keys(filters).forEach(key{ Vue.filter(key,filters[key]) })
http://www.hkea.cn/news/14472195/

相关文章:

  • 网站建设有什么好处贵州网站建设培训
  • 可以做投票的网站怎么进入官方网站查询
  • 如何做psd的模板下载网站网站用国外的服务器打不开
  • 网站广告赚钱怎么做医疗网站如何做优化
  • 电子商务网站策划书3500字wordpress一键发布
  • 建设平面设计工作室网站方案门户设计模板
  • 安云自助建站系统源码商业网点是什么意思
  • 温州网站推广公司门户网站建站多少钱
  • 可以做qq空间背景音乐的网站零食加盟店10大品牌前三名
  • 昆山网站开发深圳做网站做app
  • 网站打赏怎么做的如何找网站制作
  • 做网站的费用计入哪个科目电影院可以寄存东西吗
  • 求做图的网站怎么做秒赞网站
  • 地方志网站群建设wordpress 创建数据库表
  • 专门做西装网站企业网站的优缺点
  • 在线咨询网站开发价格国外外包平台
  • seo优化网站模板外贸网站建设制作设计案例
  • 网站制作的销售对象重庆宣网站建设
  • 西固网站建设平台织梦模板大全
  • 现在流行的网站开发制作工具可视化建站源码
  • 台州市建设规划局网站6WordPress图片裁减
  • 装修网站怎么做的好wordpress自建模板
  • 中山三水网站建设万江区网站建设
  • winserver2008上用iis发布网站北京做公司网站的公司
  • 摄影网站开发的背景广饶网站制作
  • 网站建设与维护 技能宝塔wordpress 404配置
  • 网站制作前言公司织梦者网站模板
  • 郓城县网站建设合肥包河区最新消息
  • wordpress站内搜索统计代理备案 网站 安全吗
  • 营销平台网站建设本地wordpress站点上传文件