关于vue.js:vue源码分析基础的数据代理检测

简略回顾一下这个系列的前两节,前两节花了大量的篇幅介绍了Vue的选项合并,选项合并是Vue实例初始化的开始,Vue为开发者提供了丰盛的选项配置,而每个选项都严格规定了合并的策略。然而这只是初始化中的第一步,这一节咱们将对另一个重点的概念深刻的剖析,他就是数据代理,咱们晓得Vue大量利用了代理的思维,而除了响应式零碎外,还有哪些场景也须要进行数据代理呢?这是咱们这节剖析的重点。2.1 数据代理的含意数据代理的另一个说法是数据劫持,当咱们在拜访或者批改对象的某个属性时,数据劫持能够拦挡这个行为并进行额定的操作或者批改返回的后果。而咱们晓得Vue响应式零碎的外围就是数据代理,代理使得数据在拜访时进行依赖收集,在批改更新时对依赖进行更新,这是响应式零碎的外围思路。而这所有离不开Vue对数据做了拦挡代理。然而响应式并不是本节探讨的重点,这一节咱们将看看数据代理在其余场景下的利用。在剖析之前,咱们须要把握两种实现数据代理的办法: Object.defineProperty 和 Proxy。 2.1.1 Object.defineProperty官网定义:Object.defineProperty()办法会间接在一个对象上定义一个新属性,或者批改一个对象的现有属性, 并返回这个对象。根本用法: Object.defineProperty(obj, prop, descriptor)Object.defineProperty()能够用来准确增加或批改对象的属性,只须要在descriptor对象中将属性个性形容分明,descriptor的属性描述符有两种模式,一种是数据描述符,另一种是存取描述符,咱们别离看看各自的特点。 数据描述符,它领有四个属性配置configurable:数据是否可删除,可配置enumerable:属性是否可枚举value:属性值,默认为undefinedwritable:属性是否可读写存取描述符,它同样领有四个属性选项configurable:数据是否可删除,可配置enumerable:属性是否可枚举get:一个给属性提供 getter 的办法,如果没有 getter 则为 undefined。set:一个给属性提供 setter 的办法,如果没有 setter 则为 undefined。须要留神的是: 数据描述符的value,writable 和 存取描述符中的get, set属性不能同时存在,否则会抛出异样。 有了Object.defineProperty办法,咱们能够不便的利用存取描述符中的getter/setter来进行数据的监听,这也是响应式构建的雏形。getter办法能够让咱们在拜访数据时做额定的操作解决,setter办法使得咱们能够在数据更新时批改返回的后果。看看上面的例子,因为设置了数据代理,当咱们拜访对象o的a属性时,会触发getter执行钩子函数,当批改a属性的值时,会触发setter钩子函数去批改返回的后果。 var o = {}var value;Object.defineProperty(o, 'a', { get() { console.log('获取值') return value }, set(v) { console.log('设置值') value = qqq }})o.a = 'sss' // 设置值console.log(o.a)// 获取值// 'qqq'后面说到Object.defineProperty的get和set办法是对对象进行监测并响应变动,那么数组类型是否也能够监测呢,参照监听属性的思路,咱们用数组的下标作为属性,数组的元素作为拦挡对象,看看Object.defineProperty是否能够对数组的数据进行监控拦挡。 var arr = [1,2,3];arr.forEach((item, index) => { Object.defineProperty(arr, index, { get() { console.log('数组被getter拦挡') return item }, set(value) { console.log('数组被setter拦挡') return item = value } })})arr[1] = 4;console.log(arr)// 后果数组被setter拦挡数组被getter拦挡4显然,已知长度的数组是能够通过索引属性来设置属性的拜访器属性的。然而数组的增加确无奈进行拦挡,这个也很好了解,不论是通过arr.push()还是arr[10] = 10增加的数据,数组所增加的索引值并没有事后退出数据拦挡中,所以天然无奈进行拦挡解决。这个也是应用Object.defineProperty进行数据代理的弊病。为了解决这个问题,Vue在响应式零碎中对数组的办法进行了重写,间接的解决了这个问题,具体细节能够参考后续的响应式系统分析。 ...

November 1, 2022 · 4 min · jiezi

关于vue.js:这可能是你需要的vue考点梳理

对 React 和 Vue 的了解,它们的异同相似之处: 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;都应用了Virtual DOM(虚构DOM)进步重绘性能;都有props的概念,容许组件间的数据传递;都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。不同之处 : 1)数据流 Vue默认反对数据双向绑定,而React始终提倡单向数据流 2)虚构DOM Vue2.x开始引入"Virtual DOM",打消了和React在这方面的差别,然而在具体的细节还是有各自的特点。 Vue声称能够更快地计算出Virtual DOM的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。对于React而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate这个生命周期办法来进行管制,但Vue将此视为默认的优化。3)组件化 React与Vue最大的不同是模板的编写。 Vue激励写近似惯例HTML的模板。写起来很靠近规范 HTML元素,只是多了一些属性。React举荐你所有的模板通用JavaScript的语法扩大——JSX书写。具体来讲:React中render函数是反对闭包个性的,所以import的组件在render中能够间接调用。然而在Vue中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。 4)监听数据变动的实现原理不同 Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的从新渲染。这是因为 Vue 应用的是可变数据,而React更强调数据的不可变。5)高阶组件 react能够通过高阶组件(HOC)来扩大,而Vue须要通过mixins来扩大。 高阶组件就是高阶函数,而React的组件自身就是纯正的函数,所以高阶函数对React来说大海捞针。相同Vue.js应用HTML模板创立视图组件,这时模板无奈无效的编译,因而Vue不能采纳HOC来实现。 6)构建工具 两者都有本人的构建工具: React ==> Create React APPVue ==> vue-cli7)跨平台 React ==> React NativeVue ==> Weex理解nextTick吗?异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeout、promise那些),定义了一个异步办法,屡次调用nextTick会将办法存入队列,通过异步办法清空以后队列。 v-if和v-for哪个优先级更高实际中不应该把v-for和v-if放一起在vue2中,v-for的优先级是高于v-if,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3中则齐全相同,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异样通常有两种状况下导致咱们这样做: 为了过滤列表中的我的项目 (比方 v-for="user in users" v-if="user.isActive")。此时定义一个计算属性 (比方 activeUsers),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive))为了防止渲染本应该被暗藏的列表 (比方 v-for="user in users" v-if="shouldShowUsers")。此时把 v-if 挪动至容器元素上 (比方 ul、ol)或者外面包一层template即可文档中明确指出永远不要把 v-if 和 v-for 同时用在同一个元素上,显然这是一个重要的注意事项源码外面对于代码生成的局部,可能清晰的看到是先解决v-if还是v-for,程序上vue2和vue3正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的vue2.x源码剖析 ...

November 1, 2022 · 8 min · jiezi

关于vue.js:VuenextTick的原理是什么vue面试进阶

原理性的货色就会文字较多,请耐下心来,细细品味 Vue中DOM更新机制当你威风凛凛地应用Vue大展宏图的时候,忽然发现,咦,我明明对这个数据进行更改了,然而当我获取它的时候怎么是上一次的值(自己比拟懒,就不具体举例了) 此时,Vue就会说:“小样,这你就不懂了吧,我的DOM是异步更新的呀!!!” 简略的说,Vue的响应式并不是只数据发生变化之后,DOM就立即发生变化,而是依照肯定的策略进行DOM的更新。这样的益处是能够防止一些对DOM不必要的操作,进步渲染性能。 在Vue官网文档中是这样阐明的: 可能你还没有留神到,Vue异步执行DOM更新。只有察看到数据变动,Vue将开启一个队列,并缓冲在同一事件循环中产生的所有数据扭转。如果同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和DOM操作上十分重要。而后,在下一个的事件循环“tick”中,Vue刷新队列并执行理论 (已去重的) 工作。文言一点就是说,其实这是和JS当中的事件循环是非亲非故的,就是Vue不可能对每一个数据变动都做一次渲染,它会把这些变动先放在一个异步的队列当中,同时它还会对这个队列外面的操作进行去重,比方你批改了这个数据三次,它只会保留最初一次。这些变动是都能够通过队列的模式保存起来,那当初的问题就来到了,那vue是在事件循环的哪个机会来对DOM进行批改呢? Vue有两种抉择,一个是在本次事件循环的最初进行一次DOM更新,另一种是把DOM更新放在下一轮的事件循环当中。z这时,尤雨溪拍了拍胸脯说:“这两种办法,我都有!” 然而因为本轮事件循环最初执行会比放在下一轮事件循环要快很多,所以Vue优先选择第一种,只有当环境不反对的时候才触发第二种机制。(结尾的链接让你懂事件循环) 尽管性能上进步了很多,但这个时候问题就呈现了,咱们都晓得在一轮事件循环中,同步执行栈中代码执行实现之后,才会执行异步队列当中的内容,那咱们获取DOM的操作是一个同步的呀!!那岂不是尽管我曾经把数据改掉了,然而它的更新异步的,而我在获取的时候,它还没有来得及改,所以会呈现文章结尾的那个问题。 这。。。我的确须要进行这样操作,那这么办呢?? 没关系啦,尤大很贴心的为咱们提供了Vue.$nextTick() Vue.$nextTick()其实一句话就能够把$nextTick这个货色讲明确:就是你放在$nextTick 当中的操作不会立刻执行,而是等数据更新、DOM更新实现之后再执行,这样咱们拿到的必定就是最新的了。 再精确一点来讲就是$nextTick办法将回调提早到下次DOM更新循环之后执行。(看不懂这句人话的,能够看下面[狗头]) 意思咱们都懂了,那$nextTick是怎么实现这个神奇的性能的呢? 外围如下: Vue在外部对异步队列尝试应用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不反对,则会采纳 setTimeout(fn, 0)代替。认真地看这句话,你就能够发现这不就是利用 JavaScript 的这些异步回调工作队列,来实现 Vue 框架中本人的异步回调队列。这其实就是一个典型的将底层 JavaScript 执行原理利用到具体案例中的示例。 我在这里略微总结一下:就是$nextTick将回调函数放到微工作或者宏工作当中以提早它地执行程序;(总结的也比拟懒) 重要的是了解源码中它的三个参数的意思: callback:咱们要执行的操作,能够放在这个函数当中,咱们没执行一次$nextTick就会把回调函数放到一个异步队列当中;pending:标识,用以判断在某个事件循环中是否为第一次退出,第一次退出的时候才触发异步执行的队列挂载timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程了解之后,在看整个$nextTick外面的执行过程,其实就是把一个个$nextTick中的回调函数压入到callback队列当中,而后依据事件的性质期待执行,轮到它执行的时候,就执行一下,而后去掉callback队列中相应的事件。 参考:前端vue面试题具体解答 应用说了这么多,怎么用它呢? 很简略很简略 mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered })}应用场景created中获取DOM的操作须要应用它就是咱们下面的例子,你如果想要获取最新值,就用它还有一些第三方插件应用过程中,应用到的状况,具体问题具体分析补充之前我始终搞不懂一个的问题,$nextTick既然把它传入的办法变成微工作了,那它和其它微工作的执行程序是怎么的呢? 这简略来说就是谁先挂载Promise对象的问题,在调用$nextTick办法时就会将其闭包外部保护的执行队列挂载到Promise对象,在数据更新时Vue外部首先就会执行$nextTick办法,之后便将执行队列挂载到了Promise对象上,其实在明确Js的Event Loop模型后,将数据更新也看做一个$nextTick办法的调用,并且明确$nextTick办法会一次性执行所有推入的回调,就能够明确执行程序的问题了 还有$nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的实质是一样的,$nextTick是实例办法,nextTick是类的静态方法而已;实例办法的一个益处就是,主动给你绑定为调用实例的this罢了。

November 1, 2022 · 1 min · jiezi

关于vue.js:怎样刷vue面试题

Vue的长处轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十 kb ;简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;双向数据绑定:保留了 angular 的特点,在数据操作方面更为简略;组件化:保留了 react 的长处,实现了 html 的封装和重用,在构建单页面利用方面有着独特的劣势;视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;虚构DOM:dom 操作是十分消耗性能的,不再应用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种形式;运行速度更快:相比拟于 react 而言,同样是操作虚构 dom,就性能而言, vue 存在很大的劣势。Vue2.x 响应式数据原理整体思路是数据劫持+观察者模式 对象外部通过 defineReactive 办法,应用 Object.defineProperty 来劫持各个属性的 setter、getter(只会劫持曾经存在的属性),数组则是通过重写数组7个办法来实现。当页面应用对应属性时,每个属性都领有本人的 dep 属性,寄存他所依赖的 watcher(依赖收集),当属性变动后会告诉本人对应的 watcher 去更新(派发更新) Object.defineProperty根本应用 function observer(value) { // proxy reflect if (typeof value === 'object' && typeof value !== null) for (let key in value) { defineReactive(value, key, value[key]); }}function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { // 收集对应的key 在哪个办法(组件)中被应用 return value; }, set(newValue) { if (newValue !== value) { observer(newValue); value = newValue; // 让key对应的办法(组件从新渲染)从新执行 } } })}let obj1 = { school: { name: 'poetry', age: 20 } };observer(obj1);console.log(obj1)源码剖析 ...

November 1, 2022 · 7 min · jiezi

关于vue.js:Vue是怎样监听数组的变化的

上周五跟着一个师姐面试一个三年工作教训的前端开发,我在一边审慎的观摩。想着已经我也被他人面试过,现在面试他人,感觉其实情绪是一样的。前言 工作三年的Vue使用者应该懂什么?为何工作几年的根底越来越弱?工作如何挤出工夫学习?一道面试题其实咱们并不是要你把答案都记下来,而是把其中的思维学习到。就像你接触一个新的畛域react,你也一样能够把根本思维提炼进去。 面试题: Vue是如何对数据进行监听的? 这其实是陈词滥调的问题,凡是你有一点基础知识,你也能答出一二。师姐跟我说,其实问题不只是问题自身,而是跟这个常识顺带进去的体系。 01 对象数据是怎么被监听的 在vue2.x版本中,数据监听是用过Object.defineProperty这个API来实现的,咱们能够来看一个例子 var text = 'vue';const data = {};Object.defineProperty(data, 'text', { get() { return text; }, set(newVal) { text = newVal; }});data.text // 'vue'data.text = 'react' // 'react'当咱们拜访或设置对象的属性的时候,都会触发绝对应的函数,而后在这个函数里返回或设置属性的值。咱们当然能够在触发函数的时候做咱们本人想做的事件,这也就是“劫持”操作。 在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并创立一个监听器,当数据发生变化的时候发出通知。 var data = { name:'hello', age:18}Object.keys(data).forEach(function(key){ Object.defineProperty(data,key,{ enumerable:true, // 是否能在for...in循环中遍历进去或在Object.keys中列举进去。 configurable:true, // false,不可批改、删除指标属性或批改属性性以下个性 get:function(){ console.log('获取数据'); }, set:function(){ console.log('监听到数据产生了变动'); } })});data.name //控制台会打印出 “获取数据”data.name = 'world' //控制台会打印出 "监听到数据产生了变动"02 数组数据是怎么被监听的 咱们晓得,下面是对对象的数据进行监听的,咱们不能对数组进行数据的“劫持”。那么Vue是怎么做的呢? import { def } from '../util/index'const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']methodsToPatch.forEach(function (method) { // 缓存原来的办法 const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result })})看来Vue能对数组进行监听的起因是,把数组的办法重写了。总结起来就是这几步: ...

November 1, 2022 · 2 min · jiezi

关于vue.js:Vue-中的-Ajax

1.1 应用代理服务器1.1.1 形式一在 vue.config.js 中增加如下配置: devServer:{ proxy:"http://localhost:5000"}阐明: 长处:配置简略,申请资源时间接发给前端(8080)即可。毛病:不能配置多个代理,不能灵便的管制申请是否走代理。工作形式:若依照上述配置代理,当申请了前端不存在的资源时,那么该申请会转发给服务器 (优先匹配前端资源)1.1.2 形式二编写 vue.config.js 配置具体代理规定: module.exports = { devServer: { proxy: { '/api1': { // 匹配所有以 '/api1'结尾的申请门路 target: 'http://localhost:5000',// 代理指标的根底门路 changeOrigin: true, pathRewrite: {'^/api1': ''} }, '/api2': { // 匹配所有以 '/api2'结尾的申请门路 target: 'http://localhost:5001',// 代理指标的根底门路 changeOrigin: true, pathRewrite: {'^/api2': ''} } } }}/*changeOrigin设置为true时,服务器收到的申请头中的host为:localhost:5000changeOrigin设置为false时,服务器收到的申请头中的host为:localhost:8080changeOrigin默认值为true*/阐明: 长处:能够配置多个代理,且能够灵便的管制申请是否走代理。毛病:配置稍微繁琐,申请资源时必须加前缀。1.2 Vue 我的项目中罕用的 2 个 Ajax 库1.2.1 Axios阐明:通用的 Ajax 申请库,官网举荐,应用宽泛装置:npm install axios应用步骤: 引入 import axios from "axios";应用 axios.get("http://localhost:8080/api/students").then( (response) => { console.log("申请胜利了", response.data); }, (error) => { console.log("申请失败了", error.message); });1.2.2 vue-resourceVue 插件库,Vue 1.x 应用宽泛,官网已不保护 ...

November 1, 2022 · 1 min · jiezi

关于vue.js:Vue模板是怎样编译的

这一章咱们开始讲模板解析编译:总结来说就是通过compile函数把tamplate解析成render Function模式的字符串compiler/index.js import { parse } from './parser/index'import { optimize } from './optimizer'import { generate } from './codegen/index'import { createCompilerCreator } from './create-compiler'// `createCompilerCreator` allows creating compilers that use alternative// parser/optimizer/codegen, e.g the SSR optimizing compiler.// Here we just export a default compiler using the default parts.export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns }})咱们能够看出createCompiler函数外部运行的是parse、optimize、generate三个函数,而生成的是ast,render,staticRenderFns三个对象 ...

October 31, 2022 · 13 min · jiezi

关于vue.js:Vue组件是怎样挂载的

咱们先来关注一下$mount是实现什么性能的吧: 咱们关上源码门路core/instance/init.js: export function initMixin (Vue: Class<Component>) { ...... initLifecycle(vm) // 事件监听初始化 initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props //初始化vm状态 prop/data/computed/watch实现初始化 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ...... // 配置项里有el属性, 则会挂载到实在DOM上, 实现视图的渲染 // 这里的$mount办法,实质上调用了core/instance/liftcycle.js中的mountComponent办法 if (vm.$options.el) { vm.$mount(vm.$options.el) } }}在这里咱们怎么了解这个挂载状态呢?先来看Vue官网给的一段形容 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。能够应用 vm.$mount() 手动地挂载一个未挂载的实例。如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素。并且你必须应用原生DOM API 把它插入文档中。那咱们来看一下$mount外部机制吧: * 缓存之前的$mount的办法以便前面返回实例, */const mount = Vue.prototype.$mount/** * 手动地挂载一个未挂载的根元素,并返回实例本身(Vue实例) */Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && query(el) /* istanbul ignore if */ /** * 挂载对象不能为body和html标签 */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function /** * 判断$options是否有render办法 * 有:判断是String还是Element,获取他们的innerHTMl * 无:在实例Vue时候在vnode里创立一个创立一个空的正文节点 见办法createEmptyVNode */ if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } /** * 获取的Element的类型 * 具体见 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/outerHTML */ } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ /** * 用于监控compile 的性能 */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // 如果不存在 render 函数,则会将模板转换成render函数 const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ /** * 用于监控compile 的性能 */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating)}而$mount实现的是mountComponent函数性能 ...

October 31, 2022 · 4 min · jiezi

关于vue.js:Vue虚拟dom是如何被创建的

先来看生成虚构dom的入口文件: ... import { parse } from './parser/index' import { optimize } from './optimizer' import { generate } from './codegen/index' ... const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) ...咱们看到了AST通过三轮加工,最终本性难移成为render function code。那么咱们这一章就来讲AST的第三次试炼。入口文件./codegen/index export function generate (ast: ASTElement | void,options: CompilerOptions): CodegenResult {const state = new CodegenState(options)const code = ast ? genElement(ast, state) : '_c("div")'return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns}}咱们来看generate函数外部结构,返回render是一个with(this){return ${code}}包起来的一串货色,staticRenderFns是在编译过程中会把那些不会变的动态节点打上标记,设置为true,而后在渲染阶段独自渲染。那么genElement函数的作用是什么呢? ...

October 31, 2022 · 6 min · jiezi

关于vue.js:vue源码分析动态组件

后面花了两节的内容介绍了组件,从组件的原理讲到组件的利用,包含异步组件和函数式组件的实现和应用场景。家喻户晓,组件是贯通整个Vue设计理念的货色,并且也是领导咱们开发的核心思想,所以接下来的几篇文章,将从新回到组件的内容去做源码剖析,首先会从罕用的动静组件开始,包含内联模板的原理,最初会简略的提到内置组件的概念,为之后的文章埋下伏笔。12.1 动静组件动静组件我置信大部分在开发的过程中都会用到,当咱们须要在不同的组件之间进行状态切换时,动静组件能够很好的满足咱们的需要,其中的外围是component标签和is属性的应用。 12.1.1 根本用法例子是一个动静组件的根本应用场景,当点击按钮时,视图依据this.chooseTabs值在组件child1,child2,child3间切换。 // vue<div id="app"> <button @click="changeTabs('child1')">child1</button> <button @click="changeTabs('child2')">child2</button> <button @click="changeTabs('child3')">child3</button> <component :is="chooseTabs"> </component></div>// jsvar child1 = { template: '<div>content1</div>',}var child2 = { template: '<div>content2</div>'}var child3 = { template: '<div>content3</div>'}var vm = new Vue({ el: '#app', components: { child1, child2, child3 }, methods: { changeTabs(tab) { this.chooseTabs = tab; } }})12.1.2 AST解析<component>的解读和后面几篇内容统一,会从AST解析阶段说起,过程也不会专一每一个细节,而是把和以往解决形式不同的中央特地阐明。针对动静组件解析的差别,集中在processComponent上,因为标签上is属性的存在,它会在最终的ast树上打上component属性的标记。 // 针对动静组件的解析function processComponent (el) { var binding; // 拿到is属性所对应的值 if ((binding = getBindingAttr(el, 'is'))) { // ast树上多了component的属性 el.component = binding; } if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true; }}最终的ast树如下: ...

October 31, 2022 · 3 min · jiezi

关于vue.js:vue源码分析响应式系统一

从这一大节开始,正式进入Vue源码的外围,也是难点之一,响应式零碎的构建。这一节将作为剖析响应式构建过程源码的入门,次要分为两大块,第一块是针对响应式数据props,methods,data,computed,wather初始化过程的剖析,另一块则是在保留源码设计理念的前提下,尝试手动构建一个根底的响应式零碎。有了这两个根底内容的铺垫,下一篇进行源码具体细节的剖析会更加得心应手。7.1 数据初始化回顾一下之前的内容,咱们对Vue源码的剖析是从初始化开始,初始化_init会执行一系列的过程,这个过程包含了配置选项的合并,数据的监测代理,最初才是实例的挂载。而在实例挂载前还有意疏忽了一个重要的过程,数据的初始化(即initState(vm))。initState的过程,是对数据进行响应式设计的过程,过程会针对props,methods,data,computed和watch做数据的初始化解决,并将他们转换为响应式对象,接下来咱们会逐渐剖析每一个过程。 function initState (vm) { vm._watchers = []; var opts = vm.$options; // 初始化props if (opts.props) { initProps(vm, opts.props); } // 初始化methods if (opts.methods) { initMethods(vm, opts.methods); } // 初始化data if (opts.data) { initData(vm); } else { // 如果没有定义data,则创立一个空对象,并设置为响应式 observe(vm._data = {}, true /* asRootData */); } // 初始化computed if (opts.computed) { initComputed(vm, opts.computed); } // 初始化watch if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); }}7.2 initProps简略回顾一下props的用法,父组件通过属性的模式将数据传递给子组件,子组件通过props属性接管父组件传递的值。 ...

October 31, 2022 · 5 min · jiezi

关于vue.js:vue源码分析响应式系统三

上一节,咱们深入分析了以data,computed为数据创立响应式零碎的过程,并对其中依赖收集和派发更新的过程进行了具体的剖析。然而在应用和剖析过程中仍然存在或多或少的问题,这一节咱们将针对这些问题开展剖析,最初咱们也会剖析一下watch的响应式过程。这篇文章将作为响应式系统分析的完结篇。7.12 数组检测在之前介绍数据代理章节,咱们曾经具体介绍过Vue数据代理的技术是利用了Object.defineProperty,Object.defineProperty让咱们能够不便的利用存取描述符中的getter/setter来进行数据的监听,在get,set钩子中别离做不同的操作,达到数据拦挡的目标。然而Object.defineProperty的get,set办法只能检测到对象属性的变动,对于数组的变动(例如插入删除数组元素等操作),Object.defineProperty却无奈达到目标,这也是利用Object.defineProperty进行数据监控的缺点,尽管es6中的proxy能够完满解决这一问题,但毕竟有兼容性问题,所以咱们还须要钻研Vue在Object.defineProperty的根底上如何对数组进行监听检测。 7.12.1 数组办法的重写既然数组曾经不能再通过数据的getter,setter办法去监听变动了,Vue的做法是对数组办法进行重写,在保留原数组性能的前提下,对数组进行额定的操作解决。也就是从新定义了数组办法。 var arrayProto = Array.prototype;// 新建一个继承于Array的对象var arrayMethods = Object.create(arrayProto);// 数组领有的办法var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];arrayMethods是基于原始Array类为原型继承的一个对象类,因为原型链的继承,arrayMethod领有数组的所有办法,接下来对这个新的数组类的办法进行改写。 methodsToPatch.forEach(function (method) { // 缓冲原始数组的办法 var original = arrayProto[method]; // 利用Object.defineProperty对办法的执行进行改写 def(arrayMethods, method, function mutator () {});});function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }这里对数组办法设置了代理,当执行arrayMethods的数组办法时,会代理执行mutator函数,这个函数的具体实现,咱们放到数组的派发更新中介绍。 仅仅创立一个新的数组办法合集是不够的,咱们在拜访数组时,如何不调用原生的数组办法,而是将过程指向这个新的类,这是下一步的重点。 回到数据初始化过程,也就是执行initData阶段,上一篇内容花了大篇幅介绍过数据初始化会为data数据创立一个Observer类,过后咱们只讲述了Observer类会为每个非数组的属性进行数据拦挡,从新定义getter,setter办法,除此之外对于数组类型的数据,咱们无意跳过剖析了。这里,咱们重点看看对于数组拦挡的解决。 var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 将__ob__属性设置成不可枚举属性。内部无奈通过遍历获取。 def(value, '__ob__', this); // 数组解决 if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { // 对象解决 this.walk(value); }}数组解决的分支分为两个,hasProto的判断条件,hasProto用来判断以后环境下是否反对__proto__属性。而数组的解决会依据是否反对这一属性来决定执行protoAugment, copyAugment过程, ...

October 31, 2022 · 5 min · jiezi

关于vue.js:说说你对Vue的keepalive的理解

什么是 keep-alive在平时开发中,有局部组件没有必要屡次初始化,这时,咱们须要将组件进行长久化,使组件的状态维持不变,在下一次展现时,也不会进行从新初始化组件。 也就是说,keepalive 是 Vue 内置的一个组件,能够使被蕴含的组件保留状态,或防止从新渲染 。也就是所谓的组件缓存 <keep-alive>是Vue的内置组件,能在组件切换过程中将状态保留在内存中,避免反复渲染DOM。 <keep-alive> 包裹动静组件时,会缓存不流动的组件实例,而不是销毁它们。和 <transition> 类似,<keep-alive> 是一个形象组件:它本身不会渲染一个 DOM 元素,也不会呈现在父组件链中。prop: include: 字符串或正则表达式。只有匹配的组件会被缓存。exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。keep-alive的申明周期执行页面第一次进入,钩子的触发程序 created-> mounted-> activated, 退出时触发 deactivated 当再次进入(后退或者后退)时,只触发 activated事件挂载的办法等,只执行一次的放在 mounted 中;组件每次进去执行的办法放在 activated 中;根本用法<!--被keepalive蕴含的组件会被缓存--><keep-alive> <component><component /></keep-alive>被keepalive蕴含的组件不会被再次初始化,也就意味着不会重走生命周期函数 然而有时候是心愿咱们缓存的组件能够可能再次进行渲染,这时 Vue 为咱们解决了这个问题被蕴含在 keep-alive 中创立的组件,会多出两个生命周期的钩子: activated 与 deactivated: activated 当 keepalive 蕴含的组件再次渲染的时候触发deactivated 当 keepalive 蕴含的组件销毁的时候触发keepalive是一个形象的组件,缓存的组件不会被 mounted,为此提供activated和deactivated钩子函数 参数了解keepalive 能够接管3个属性做为参数进行匹配对应的组件进行缓存: include 蕴含的组件(能够为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)exclude 排除的组件(认为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)max 缓存组件的最大值(类型为字符或者数字,能够管制缓存组件的个数)注:当应用正则表达式或者数组时,肯定要应用 v-bind <!-- 将(只)缓存组件name为a或者b的组件, 联合动静组件应用 --><keep-alive include="a,b"> <component></component></keep-alive><!-- 组件name为c的组件不缓存(能够保留它的状态或防止从新渲染) --><keep-alive exclude="c"> <component></component></keep-alive><!-- 应用正则表达式,需应用v-bind --><keep-alive :include="/a|b/"> <component :is="view"></component></keep-alive><!-- 动静判断 --><keep-alive :include="includedComponents"> <router-view></router-view></keep-alive><!-- 如果同时应用include,exclude,那么exclude优先于include, 上面的例子只缓存a组件 --><keep-alive include="a,b" exclude="b"> <component></component></keep-alive><!-- 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件 --><keep-alive exclude="c" max="5"> <component></component></keep-alive>遇见 vue-router 联合router应用,缓存局部页面所有门路下的视图组件都会被缓存<keep-alive> <router-view> <!-- 所有门路匹配到的视图组件都会被缓存! --> </router-view></keep-alive>如果只想要router-view外面的某个组件被缓存,怎么办?应用 include/exclude应用 meta 属性1、用 include (exclude例子相似) ...

October 31, 2022 · 2 min · jiezi

关于vue.js:三年经验前端vue面试记录

router-link和router-view是如何起作用的剖析 vue-router中两个重要组件router-link和router-view,别离起到导航作用和内容渲染作用,然而答复如何失效还真有肯定难度 答复范例 vue-router中两个重要组件router-link和router-view,别离起到路由导航作用和组件内容渲染作用应用中router-link默认生成一个a标签,设置to属性定义跳转path。实际上也能够通过custom和插槽自定义最终的展示模式。router-view是要显示组件的占位组件,能够嵌套,对应路由配置的嵌套关系,配合name能够显示具名组件,起到更强的布局作用。router-link组件外部依据custom属性判断如何渲染最终生成节点,外部提供导航办法navigate,用户点击之后理论调用的是该办法,此办法最终会批改响应式的路由变量,而后从新去routes匹配出数组后果,router-view则依据其所处深度deep在匹配数组后果中找到对应的路由并获取组件,最终将其渲染进去。vuex是什么?怎么应用?哪种性能场景应用它?Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源寄存地,对应于个别 vue 对象外面的 data 外面寄存的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据产生扭转,依赖这相数据的组件也会产生更新它通过 mapState 把全局的 state 和 getters 映射到以后组件的 computed 计算属性vuex 个别用于中大型 web 单页利用中对利用的状态进行治理,对于一些组件间关系较为简单的小型利用,应用 vuex 的必要性不是很大,因为齐全能够用组件 prop 属性或者事件来实现父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据。应用Vuex解决非父子组件之间通信问题 vuex 是通过将 state 作为数据中心、各个组件共享 state 实现跨组件通信的,此时的数据齐全独立于组件,因而将组件间共享的数据置于 State 中能无效解决多层级组件嵌套的跨组件通信问题vuex 的 State 在单页利用的开发中自身具备一个“数据库”的作用,能够将组件中用到的数据存储在 State 中,并在 Action 中封装数据读写的逻辑。这时候存在一个问题,个别什么样的数据会放在 State 中呢? 目前次要有两种数据会应用 vuex 进行治理:组件之间全局共享的数据通过后端异步申请的数据 包含以下几个模块 state:Vuex 应用繁多状态树,即每个利用将仅仅蕴含一个store 实例。外面寄存的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据产生扭转,依赖这相数据的组件也会产生更新。它通过 mapState 把全局的 state 和 getters 映射到以后组件的 computed 计算属性mutations:更改Vuex的store中的状态的惟一办法是提交mutationgetters:getter 能够对 state 进行计算操作,它就是 store 的计算属性尽管在组件内也能够做计算属性,然而 getters 能够在多给件之间复用如果一个状态只在一个组件内应用,是能够不必 gettersaction:action 相似于 muation, 不同在于:action 提交的是 mutation,而不是间接变更状态action 能够蕴含任意异步操作modules:面对简单的应用程序,当治理的状态比拟多时;咱们须要将vuex的store对象宰割成模块(modules) ...

October 31, 2022 · 17 min · jiezi

关于vue.js:面试官vue2和vue3的区别有哪些

一、Vue3 与 Vue2 区别详述1. 生命周期对于生命周期来说,整体上变动不大,只是大部分生命周期钩子名称上 + “on”,性能上是相似的。不过有一点须要留神,Vue3 在组合式API(Composition API,上面开展)中应用生命周期钩子时须要先引入,而 Vue2 在选项API(Options API)中能够间接调用生命周期钩子,如下所示。 // vue3<script setup> import { onMounted } from 'vue'; // 应用前需引入生命周期钩子onMounted(() => { // ...});// 可将不同的逻辑拆开成多个onMounted,仍然按程序执行,不会被笼罩onMounted(() => { // ...});</script>// vue2<script> export default { mounted() { // 间接调用生命周期钩子 // ... }, }</script> 罕用生命周期比照如下表所示。 vue2vue3beforeCreate created beforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeDestroyonBeforeUnmountdestroyedonUnmountedTips: setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不须要显式地去定义。2. 多根节点相熟 Vue2 的敌人应该分明,在模板中如果应用多个根节点时会报错,如下所示。 // vue2中在template里存在多个根节点会报错<template> <header></header> <main></main> <footer></footer></template>// 只能存在一个根节点,须要用一个<div>来包裹着<template> <div> <header></header> <main></main> <footer></footer> </div></template>然而,Vue3 反对多个根节点,也就是 fragment。即以下多根节点的写法是被容许的。 ...

October 31, 2022 · 6 min · jiezi

关于vue.js:Vue3-实现-Windows-10-的描边效果

Demohttps://huodoushigemi.github....仓库https://github.com/huodoushig...须要环境Chorme 76+Vue 3+装置npm i -S vue-reveal-effect在 JS 中应用<h1 id="reveal1">REVEAL EFFECT</h1><h1 id="reveal2">REVEAL EFFECT</h1><h1 id="reveal3">REVEAL EFFECT</h1><script> import { useRevealEffect } from "vue-reveal-effect"; const options2 = { borderWidth: 4, borderColor: 'rgba(255, 0, 0, 40%)', borderSize: 40, bgSize: 40 bgColor: '#00a1ff' } const options3 = { borderWidth: 2, clickEffect: false } useRevealEffect(document.getElementById("reveal1")); useRevealEffect(document.getElementById("reveal2"), options2); useRevealEffect(document.getElementById("reveal3"), options3);</script>在 Vue3 中应用import { createApp } from 'vue'import VueRevealEffect from 'vue-reveal-effect'createApp(App).use(VueRevealEffect).mount('#app')<template> <h1 v-reveal-effect>VUE REVEAL EFFECT</h1> <h1 v-reveal-effect="options1">VUE REVEAL EFFECT</h1> <h1 v-reveal-effect="options2">VUE REVEAL EFFECT</h1></template><script setup> const options1 = { borderWidth: 4, borderColor: 'rgba(255, 0, 0, 40%)', borderSize: 40, bgSize: 40 bgColor: '#00a1ff' } const options2 = { borderWidth: 2, clickEffect: false }</script>属性nameTypeDefaultDescriptionborderWidthMaybeRef<number>1 borderColorMaybeRef<string> borderGradientSizeMaybeRef<number>100 bgColorMaybeRef<string> bgGradientSizeMaybeRef<number>130 clickEffectMaybeRef<boolean>trueEnable ripple click effectlightMaybeRef<boolean> disabledMaybeRef<boolean>false 不反对不反对 css 的 border-radius 属性不反对 html 的 <img /> 标签最初你的点赞⭐对我十分重要,也是我保持的能源 ...

October 31, 2022 · 1 min · jiezi

关于vue.js:Vue-脚手架编程

1.1 初始化脚手架1.1.1 阐明Vue 脚手架是 Vue 官网提供的标准化开发工具(开发平台)最新的版本是 4.x文档1.1.2 具体步骤第一步(仅第一次执行):全局装置 @vue/cli npm install -g @vue/cli第二步: 切换到要创立我的项目的目录 ,而后应用命令创立我的项目 vue create xxxxxxxx: 我的项目名 第三步:启动我的项目 npm run serve备注: 如呈现:下载迟缓请配置淘宝镜像: npm config set registry https://registry.npm.taobao.orgVue 脚手架暗藏了所有 webpack 相干的配置,若想查看具体的 webpack配置,请执行: vue inspect > output.js1.1.3 模板我的项目的构造├──node_modules├──public│ ├──favicon.ico:页签图标│ └──index.html:主页面├──src│ ├──assets:寄存动态资源│ │ └──logo.png│ │──component:寄存组件│ │ └──HelloWorld.vue│ │──App.vue:汇总所有组件│ │──main.js:入口文件├──.gitignore:git版本管制疏忽的配置├──babel.config.js:babel的配置文件├──package.json:利用包配置文件├──README.md:利用形容文件├──package-lock.json:包版本控制文件1.1.4 不同版本的 Vuevue.js 与 vue.runtime.xxx.js 的区别: vue.js 是完整版的 Vue,蕴含:外围性能 + 模板解析器。vue.runtime.xxx.js 是运行版的 Vue,只蕴含:外围性能;没有模板解析器因为 vue.runtime.xxx.js 没有模板解析器,所以不能应用 template 这个配置项,须要应用 render 函数接管到的 createElement 函数去指定具体内容1.1.5 vue.config.js 配置文件应用 vue inspect > output.js 能够查看到Vue脚手架的默认配置。应用 vue.config.js 能够对脚手架进行个性化定制,详情1.2 ref1.2.1 应用阐明被用来给元素或子组件注册援用信息(id 的替代者)利用在 html 标签上获取的是实在 DOM 元素,利用在组件标签上是组件实例对象(vc)应用形式: ...

October 30, 2022 · 10 min · jiezi

关于vue.js:能不能手写Vue响应式前端面试进阶

Vue 视图更新原理Vue 的视图更新原理次要波及的是响应式相干API Object.defineProperty 的应用,它的作用是为对象的某个属性对外提供 get、set 办法,从而实现内部对该属性的读和写操作时可能被外部监听,实现后续的同步视图更新性能 一、实现响应式的外围API:Object.definePropertyObject.defineProperty的用法介绍:MDN-Object.defineProperty,上面是模仿 Vue data 值的更新对API接口进行初步理解 // 模仿 Vue 中的 dataconst data = {}// 对外不可见的外部变量let _myName = 'Yimwu'// 响应式监听 data 中的 nameObject.defineProperty(data, "name", { // 应用 data.name 时 get 办法被调用,返回外部存储变量值 get: () => { console.log('get') return _myName }, // 应用 data.name = xxx 批改变量时,set 办法被调用,设置外部存储变量值 set: (newVal) => { console.log('set') _myName = newVal }})console.log(data.name) // 输入 Yimwu getdata.name = 'Mr.Wu' // 输入 set (监听胜利)二、视图更新初步实现1、updateView为了不便 模仿视图更新,这里创立了一个函数 updateView ,当数据更新时,调用 updateView ,模仿进行了视图更新(在 Vue 中体现为 template 模板中援用了该变量值的 DOM 元素的变动) ...

October 29, 2022 · 3 min · jiezi

关于vue.js:阿里前端面试问到的vue问题

Vue.set的实现原理给对应和数组自身都减少了dep属性当给对象新增不存在的属性则触发对象依赖的watcher去更新当批改数组索引时,咱们调用数组自身的splice去更新数组(数组的响应式原理就是从新了splice等办法,调用splice就会触发视图更新)根本应用 以下办法调用会扭转原始数组:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )调用办法:Vue.set(target, key, value ) target:要更改的数据源(能够是对象或者数组)key:要更改的具体数据value :从新赋的值<div id="app">{{user.name}} {{user.age}}</div><div id="app"></div><script> // 1. 依赖收集的特点:给每个属性都减少一个dep属性,dep属性会进行收集,收集的是watcher // 2. vue会给每个对象也减少一个dep属性 const vm = new Vue({ el: '#app', data: { // vm._data user: {name:'poetry'} } }); // 对象的话:调用defineReactive在user对象上定义一个age属性,减少到响应式数据中,触发对象自身的watcher,ob.dep.notify()更新 // 如果是数组 通过调用 splice办法,触发视图更新 vm.$set(vm.user, 'age', 20); // 不能给根属性增加,因为给根增加属性 性能耗费太大,须要做很多解决 // 批改必定是同步的 -> 更新都是一步的 queuewatcher</script>相干源码 // src/core/observer/index.js 44export class Observer { // new Observer(value) value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() // 给所有对象类型减少dep属性 }}// src/core/observer/index.js 201export function set (target: Array<any> | Object, key: any, val: any): any { // 1.是开发环境 target 没定义或者是根底类型则报错 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 2.如果是数组 Vue.set(array,1,100); 调用咱们重写的splice办法 (这样能够更新视图) if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) // 利用数组的splice变异办法触发响应式 target.splice(key, 1, val) return val } // 3.如果是对象自身的属性,则间接增加即可 if (key in target && !(key in Object.prototype)) { target[key] = val // 间接批改属性值 return val } // 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义) const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 5.如果不是响应式的也不须要将其定义成响应式属性 if (!ob) { target[key] = val return val } // 6.将属性定义成响应式的 defineReactive(ob.value, key, val) // 告诉视图更新 ob.dep.notify() return val}咱们浏览以上源码可知,vm.$set 的实现原理是: ...

October 29, 2022 · 8 min · jiezi

关于vue.js:说说Vue响应式系统中的Watcher和Dep的关系面试进阶

引言在这里我先提出两个问题(文章开端会进行解答): 在Vue的数据响应零碎中,Dep和Watcher各自分担什么工作?Vue的数据响应零碎的外围是Object.defineproperty肯定是最好的吗?有什么弊病和破绽吗?一、什么是响应零碎中的Watcher,它的作用是什么?响应零碎中的Watcher即这个零碎的观察者,它是响应零碎中观察者模式的载体,当响应零碎中的数据产生扭转的时候,它可能晓得并且执行相应的函数以达到某种业务逻辑的目标。打个比方,如果你是一个商家,要寄一批货别离给不同的客户,那么watcher就是一个个快递员,收回的动作就是数据产生扭转。你只须要负责寄出去这个动作就行了,如何找到、送到客户则是watcher的事件。 每个watcher和数据之间的关系要么是1对1,要么是多对多关系(这与watcher的类型无关),要不是没有分割。watcher和业务逻辑只有1对1关系。 二、Watcher的类型在Vue源码中是没有体现出Watcher的类型的,我在这里给Watcher增加类型是为了更好地了解Watcher这个对象。Watcher在一般的业务逻辑上能够分为以下三类: 一般的Watcher:与数据1对1关系。lazy型Watcher:与数据1对1关系,然而它是一个惰性求值的观察者,怎么体现呢?对它进行赋值是不会扭转它的值,只有当获取它的值的时候,才会更新最新版的数据(在Vue中体现为computed办法,个别求值是通过办法来求值的)。render型Watcher:与数据是1对多(不思考传参进子组件)的关系,在一个组件中,渲染函数观察者肯定是最初生成的,所以执行观察者队列的时候,渲染函数观察者在一个组件中是最初执行的。在这里多嘴一下lazy型的观察者是怎么回事吧。lazy型观察者在Vue中体现为computed属性,个别这个属性是一个函数,以下是一个例子: computed: { // getCount最初解决成一个属性,而后这个办法被存储在Watcher的某个属性中 getCount() { return this.a + this.b; }}lazy观察者外面有一个dirty属性,也就是一个开关作用,只有它为true的时候应用getCount的getter办法的时候,才会进行调用这个函数。 如果lazy观察者所援用的数据(a或者b属性)产生扭转后,会将这个放到观察者执行队列中,而后执行这个观察者的时候把dirty赋值为true(代表下次访问getter办法的时候会执行一遍lazy的求值办法(求值后会将dirty赋值为false))。等到下一次须要获取这个数据的时候才进行求值,所以它叫做惰性求值。这种形式可能节俭不必要执行函数的开销。 三、Watcher和Dep的关系看过Vue源码的defineReactive这个办法,就会发现一个被察看的对象外面每个属性会有一个Dep依赖筐来寄存所有察看它的Watcher。而defineReactive办法只有初始化每个属性的dep却并没有创立观察者(要分清初始化和创立观察者是离开这个事实)。那么这个Dep如何增加观察者呢?Vue应用了全局变量,这个变量叫做Dep.target,它是一个Watcher类型的变量,来将Watcher和Dep进行相互绑定。数据的绑定用图来示意的话如下: 咱们能够明确以下区别: $watch办法创立的观察者的时候,如果不设定immediate属性,那么是不会进行调用的,而computed和render是会进行调用办法的。数据的Dep的subs数组寄存这个数据所绑定的观察者对象,观察者对象的deps数组中寄存着与这个观察者无关的数据Dep。所以数据的Dep与Watcher其实是多对多关系$watch和computed观察者是在created生命钩子函数前就创立结束并且绑定的,而render观察者是在mounted之前创立并绑定的,所以同一个组件中,render观察者的id会大于其余观察者(id是在前面执行队列外面升序排序的时候的根据)。 换句话说,在同一个组件的观察者中,当数据产生扭转的时候,渲染函数观察者肯定是最初执行的。 这个很好了解,其余观察者中难免会对数据进行批改,如果渲染函数观察者先执行了,而后其余观察者对数据进行扭转的话,那么没方法将数据精确出现在页面上,导致数据不一致性。参考:前端vue面试题具体解答 四、讲一下观察者执行队列机制Vue是如何实现性能优化的呢?最显著的两个点: 观察者设定执行队列,批量执行。diff算法缩小渲染开销。第二个不在这外面解说,咱们看一下第一个是怎么回事? 这个队列的长度是怎么定量的呢? 最大长度是100,源码摆在那里。 以一个事件循环时间段为收集工夫。(什么是事件循环?能够看一下本博客零碎的其余优良文章)它的流程是如下的: 未执行时候:如果有更改过数据,那么就将对应的观察者间接推动队列中(执行的时候会进行依据id升序排序后执行)在执行中的时候,如果有新的观察者进来了(观察者中更改数据,而后这个数据又绑定观察者),依照id升序来进行插入(这相当于在有序数组外面进行插入,能够看做插入排序的其中一步,所以某种意义上来说它就是排序)。五、解答后面的问题Dep是负责存放数据所绑定所有的观察者的对象的容器,只有数据产生扭转,就会通过这个Dep来告诉所有观察者进行批改数据。(每个数据都有举世无双的Dep)。而Watcher更偏差于一个动作,也就是规定的业务逻辑或者渲染函数,是一个执行者。在ES5是很轻便的,很好的。然而在ES6呈现后,它就肯定不是最好的,因为ES6有一个Proxy代理来对立进行解决。(ES6应用代理实现Vue数据响应零碎(TypeScript)) 弊病:如果一个数据有1000个属性,那么就要给这1000个属性应用Object.defineProperty,这样在初始化页面的时候会造成卡顿。如果用代理的话,那么只须要执行一次就能够了。破绽:如果咱们拿到了vm实例,那么用户是能够在运行的时候通过批改对象属性的描述符(descriptor)来进行批改它,会造成零碎的不确定性。这是因为响应零碎的模式导致必须将数据的描述符的configuration设为true,所以在运行的时候可能对它进行批改。

October 29, 2022 · 1 min · jiezi

关于vue.js:每日一题之Vue的异步更新实现原理是怎样的

最近面试总是会被问到这么一个问题:在应用vue的时候,将for循环中申明的变量i从1减少到100,而后将i展现到页面上,页面上的i是从1跳到100,还是会怎么?答案当然是只会显示100,并不会有跳转的过程。 怎么能够让页面上有从1到100显示的过程呢,就是用setTimeout或者Promise.then等办法去模仿。 讲道理,如果不在vue里,独自运行这段程序的话,输入肯定是从1到100,然而为什么在vue中就不一样了呢? for(let i=1; i<=100; i++){ console.log(i);}这就波及到Vue底层的异步更新原理,也要说一说nextTick的实现。不过在说nextTick之前,有必要先介绍一下JS的事件运行机制。 JS运行机制家喻户晓,JS是基于事件循环的单线程的语言。执行的步骤大抵是: 当代码执行时,所有同步的工作都在主线程上执行,造成一个执行栈;在主线程之外还有一个工作队列(task queue),只有异步工作有了运行后果就在工作队列中搁置一个事件;一旦执行栈中所有同步工作执行结束(主线程代码执行结束),此时主线程不会闲暇而是去读取工作队列。此时,异步的工作就完结期待的状态被执行。主线程一直反复以上的步骤。 咱们把主线程执行一次的过程叫一个tick,所以nextTick就是下一个tick的意思,也就是说用nextTick的场景就是咱们想在下一个tick做一些事的时候。所有的异步工作后果都是通过工作队列来调度的。而工作分为两类:宏工作(macro task)和微工作(micro task)。它们之间的执行规定就是每个宏工作完结后都要将所有微工作清空。常见的宏工作有setTimeout/MessageChannel/postMessage/setImmediate,微工作有MutationObsever/Promise.then。 nextTick原理派发更新大家都晓得vue的响应式的靠依赖收集和派发更新来实现的。在批改数据之后的派发更新过程,会触发setter的逻辑,执行dep.notify(): // src/core/observer/watcher.jsclass Dep { notify() { //subs是Watcher的实例数组 const subs = this.subs.slice() for(let i=0, l=subs.length; i<l; i++){ subs[i].update() } }}遍历subs里每一个Watcher实例,而后调用实例的update办法,上面咱们来看看update是怎么去更新的: class Watcher { update() { ... //各种状况判断之后 else{ queueWatcher(this) } }}update执行后又走到了queueWatcher,那就持续去看看queueWatcher干啥了(心愿不要持续套娃了: //queueWatcher 定义在 src/core/observer/scheduler.jsconst queue: Array<Watcher> = []let has: { [key: number]: ?true } = {}let waiting = falselet flushing = falselet index = 0export function queueWatcher(watcher: Watcher) { const id = watcher.id //依据id是否反复做优化 if(has[id] == null){ has[id] = true if(!flushing){ queue.push(watcher) }else{ let i=queue.length - 1 while(i > index && queue[i].id > watcher.id){ i-- } queue.splice(i + 1, 0, watcher) } if(!waiting){ waiting = true //flushSchedulerQueue函数: Flush both queues and run the watchers nextTick(flushSchedulerQueue) } }}这里queue在pushwatcher时是依据id和flushing做了一些优化的,并不会每次数据扭转都触发watcher的回调,而是把这些watcher先增加到⼀个队列⾥,而后在nextTick后执⾏flushSchedulerQueue。 ...

October 28, 2022 · 2 min · jiezi

关于vue.js:一面高频vue面试题

组件通信组件通信的形式如下: (1) props / $emit父组件通过props向子组件传递数据,子组件通过$emit和父组件通信 1. 父组件向子组件传值props只能是父组件向子组件进行传值,props使得父子组件之间造成了一个单向上行绑定。子组件的数据会随着父组件不断更新。props 能够显示定义一个或一个以上的数据,对于接管的数据,能够是各种数据类型,同样也能够传递一个函数。props属性名规定:若在props中应用驼峰模式,模板中须要应用短横线的模式// 父组件<template> <div id="father"> <son :msg="msgData" :fn="myFunction"></son> </div></template><script>import son from "./son.vue";export default { name: father, data() { msgData: "父组件数据"; }, methods: { myFunction() { console.log("vue"); }, }, components: { son },};</script>// 子组件<template> <div id="son"> <p>{{ msg }}</p> <button @click="fn">按钮</button> </div></template><script>export default { name: "son", props: ["msg", "fn"] };</script>2. 子组件向父组件传值$emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接管参数。// 父组件<template> <div class="section"> <com-article :articles="articleList" @onEmitIndex="onEmitIndex" ></com-article> <p>{{ currentIndex }}</p> </div></template><script>import comArticle from "./test/article.vue";export default { name: "comArticle", components: { comArticle }, data() { return { currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] }; }, methods: { onEmitIndex(idx) { this.currentIndex = idx; }, },};</script>//子组件<template> <div> <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)" > {{ item }} </div> </div></template><script>export default { props: ["articles"], methods: { emitIndex(index) { this.$emit("onEmitIndex", index); // 触发父组件的办法,并传递参数index }, },};</script>(2)eventBus事件总线($emit / $on)eventBus事件总线实用于父子组件、非父子组件等之间的通信,应用步骤如下: (1)创立事件核心治理组件之间的通信 ...

October 28, 2022 · 10 min · jiezi

关于vue.js:每日一题之请描述Vue组件渲染流程

组件化是 Vue, React 等这些框架的一个核心思想,通过把页面拆成一个个高内聚、低耦合的组件,能够极大水平进步咱们的代码复用度,同时也使得我的项目更加易于保护。所以,本文就来剖析下组件的渲染流程。咱们通过上面这个例子来进行剖析: <div id="demo"> <comp></comp></div><script> Vue.component('comp', { template: '<div>I am comp</div>', }) const app = new Vue({ el: '#demo', })</script>这里咱们分为两步来剖析:组件申明、组件创立及渲染 组件申明首先,咱们看下 Vue.component 是什么货色,它的申明在 core/global-api/assets.js: export function initAssetRegisters(Vue: GlobalAPI) { // ASSET_TYPES是数组:['component','directive','filter'] ASSET_TYPES.forEach((type) => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } // 组件申明相干代码 if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id // _base是Vue // Vue.extend({})返回组件构造函数 definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = {bind: definition, update: definition} } // 注册到components选项中去 // 在Vue原始选项上增加组件配置,未来其余组件继承,它们都有这些组件注册 this.options[type + 's'][id] = definition return definition } } })}这里 this.options._base.extend(definition) 调用的其实就是 Vue.extend(definition): ...

October 28, 2022 · 6 min · jiezi

关于vue.js:Vue响应式系统原理并实现一个双向绑定

这一章就着重讲两个点: 响应式零碎如何收集依赖响应式零碎如何更新视图 咱们晓得通过Object.defineProperty做了数据劫持,当数据扭转的时候,get办法收集依赖,进而set办法调用dep.notify办法去告诉Watcher调用自身update办法去更新视图。那么咱们抛开其余问题,就探讨get,notify,update等办法,间接上代码:get( ) get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }咱们晓得Dep.target在创立Watcher的时候是null,并且它只是起到一个标记的作用,当咱们创立Watcher实例的时候,咱们的Dep.target就会被赋值到Watcher实例,进而放入target栈中,咱们这里调用的是pushTarget函数: // 将watcher实例赋值给Dep.target,用于依赖收集。同时将该实例存入target栈中export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target}那咱们继续执行到if (Dep.target)语句的时候就会调用Dep.depend函数: // 将本身退出到全局的watcher中 depend () { if (Dep.target) { Dep.target.addDep(this) } }那上面的childOb是啥货色呢? let childOb = !shallow && observe(val)咱们通过这个变量判断以后属性上面是否还有ob属性,如果有的话持续调用Dep.depend函数,没有的话则不解决。咱们还须要解决以后传入的value类型,是数组属性的话则会调用dependArray收集数组依赖 ...

October 27, 2022 · 4 min · jiezi

关于vue.js:Vue实战必会的几个技巧

键盘事件在 js 中咱们通常通过绑定一个事件,去获取按键的编码,再通过 event 中的 keyCode 属性去取得编码如果咱们须要实现固定的键能力触发事件时就须要一直的判断,其实很麻烦let button = document.querySelector('button')button.onkeyup = function (e) { console.log(e.key) if (e.keyCode == 13) { console.log('我是回车键') }}vue 中给一些罕用的按键提供了别名,咱们只有在事件后加上响应的别名即可vue 中常见别名有:up/向上箭头、down/向下箭头、left/左箭头、right/右箭头、space/空格、tab/换行、esc/退出、enter/回车、delete/删除// 只有按下回车键时才会执行 send 办法<input v-on:keyup.enter="send" type="text">对于 Vue 中未提供别名的键,能够应用原始的 key 值去绑定,所谓 key 值就是 event.key 所取得的值如果 key 值是单个字母的话间接应用即可,如果是由多个单词组成的驼峰命名,就须要将其拆开,用 - 连贯// 只有按下q键时才会执行send办法<input v-on:keyup.Q="send" type="text">// 只有按下capslock键时才会执行send办法<input v-on:keyup.caps-lock="send" type="text">对于零碎修饰符 ctrl、alt、shift 这些比较复杂的键应用而言,分两种状况因为这些键能够在按住的同时,去按其余键,造成组合快捷键当触发事件为 keydown 时,咱们能够间接按下修饰符即可触发当触发事件为 keyup 时,按下润饰键的同时要按下其余键,再开释其余键,事件能力被触发。// keydown事件时按下alt键时就会执行send办法<input v-on:keydown.Alt="send" type="text">// keyup事件时须要同时按下组合键才会执行send办法<input v-on:keyup.Alt.y="send" type="text">当然咱们也能够自定义按键别名通过 Vue.config.keyCodes.自定义键名=键码 的形式去进行定义// 只有按下回车键时才会执行send办法<input v-on:keydown.autofelix="send" type="text">// 13是回车键的键码,将他的别名定义为autofelixVue.config.keyCodes.autofelix=13 图片预览在我的项目中咱们常常须要应用到图片预览,viewerjs 是一款十分炫酷的图片预览插件性能反对包含图片放大、放大、旋转、拖拽、切换、拉伸等装置 viewerjs 扩大npm install viewerjs --save引入并配置性能//引入import Vue from 'vue';import 'viewerjs/dist/viewer.css';import Viewer from 'v-viewer';//按需引入Vue.use(Viewer);Viewer.setDefaults({ 'inline': true, 'button': true, //右上角按钮 "navbar": true, //底部缩略图 "title": true, //以后图片题目 "toolbar": true, //底部工具栏 "tooltip": true, //显示缩放百分比 "movable": true, //是否能够挪动 "zoomable": true, //是否能够缩放 "rotatable": true, //是否可旋转 "scalable": true, //是否可翻转 "transition": true, //应用 CSS3 适度 "fullscreen": true, //播放时是否全屏 "keyboard": true, //是否反对键盘 "url": "data-source", ready: function (e) { console.log(e.type, '组件以初始化'); }, show: function (e) { console.log(e.type, '图片显示开始'); }, shown: function (e) { console.log(e.type, '图片显示完结'); }, hide: function (e) { console.log(e.type, '图片暗藏实现'); }, hidden: function (e) { console.log(e.type, '图片暗藏完结'); }, view: function (e) { console.log(e.type, '视图开始'); }, viewed: function (e) { console.log(e.type, '视图完结'); // 索引为 1 的图片旋转20度 if (e.detail.index === 1) { this.viewer.rotate(20); } }, zoom: function (e) { console.log(e.type, '图片缩放开始'); }, zoomed: function (e) { console.log(e.type, '图片缩放完结'); }})应用图片预览插件单个图片应用<template> <div> <viewer> <img :src="cover" style="cursor: pointer;" height="80px"> </viewer> </div></template><script>export default { data() { return { cover: "//www.autofelix.com/images/cover.png" } }}</script>多个图片应用<template> <div> <viewer :images="imgList"> <img v-for="(imgSrc, index) in imgList" :key="index" :src="imgSrc" /> </viewer> </div></template><script>export default { data() { return { imgList: [ "//www.autofelix.com/images/pic_1.png", "//www.autofelix.com/images/pic_2.png", "//www.autofelix.com/images/pic_3.png", "//www.autofelix.com/images/pic_4.png", "//www.autofelix.com/images/pic_5.png" ] } }}</script>参考vue实战视频解说:进入学习 ...

October 27, 2022 · 4 min · jiezi

关于vue.js:vue源码分析事件机制

这个系列讲到这里,Vue根本外围的货色曾经剖析完,然而Vue之所以弱小,离不开它提供给用户的一些实用功能,开发者能够更偏差于业务逻辑而非基本功能的实现。例如,在日常开发中,咱们将@click=***用得飞起,然而咱们是否思考,Vue如何在前面为咱们的模板做事件相干的解决,并且咱们常常利用组件的自定义事件去实现父子间的通信,那这个事件和和原生dom事件又有不同的中央吗,可能实现通信的原理又是什么,带着纳闷,咱们深刻源码开展剖析。9.1. 模板编译Vue在挂载实例前,有相当多的工作是进行模板的编译,将template模板进行编译,解析成AST树,再转换成render函数,而有了render函数后才会进入实例挂载过程。对于事件而言,咱们常常应用v-on或者@在模板上绑定事件。因而对事件的第一步解决,就是在编译阶段对事件指令做收集解决。 从一个简略的用法剖析编译阶段收集的信息: <div id="app"> <div v-on:click.stop="doThis">点击</div> <span>{{count}}</span></div><script>var vm = new Vue({ el: '#app', data() { return { count: 1 } }, methods: { doThis() { ++this.count } }})</script>咱们之前在将模板编译的时候大抵说过编译的流程,模板编译的入口是在var ast = parse(template.trim(), options);中,parse通过拆分模板字符串,将其解析为一个AST树,其中对于属性的解决,在processAttr中,因为分支较多,咱们只剖析例子中的流程。 var dirRE = /^v-|^@|^:/;function processAttrs (el) { var list = el.attrsList; var i, l, name, rawName, value, modifiers, syncGen, isDynamic; for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name; // v-on:click value = list[i].value; // doThis if (dirRE.test(name)) { // 匹配v-或者@结尾的指令 el.hasBindings = true; modifiers = parseModifiers(name.replace(dirRE, ''));// parseModifiers('on:click') if (modifiers) { name = name.replace(modifierRE, ''); } if (bindRE.test(name)) { // v-bind分支 // ...留到v-bind指令时剖析 } else if (onRE.test(name)) { // v-on分支 name = name.replace(onRE, ''); // 拿到真正的事件click isDynamic = dynamicArgRE.test(name);// 动静事件绑定 if (isDynamic) { name = name.slice(1, -1); } addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic); } else { // normal directives // 其余指令相干逻辑 } else {} } }processAttrs的逻辑尽管较多,然而了解起来较为简单,var dirRE = /^v-|^@|^:/;是匹配事件相干的正则,命中匹配的记功会失去事件指令相干内容,包含事件自身,事件回调以及事件修饰符。最终通过addHandler办法,为AST树增加事件相干的属性。而addHandler还有一个重要性能是对事件修饰符进行非凡解决。 ...

October 27, 2022 · 10 min · jiezi

关于vue.js:vue源码分析从new-Vue开始

初学vue,你得晓得咱们是从new Vue开始的: new Vue({ el: '#app', data: obj, ...})那你感觉是不是很有意思,咱们new Vue之后,就能够应用他那么多的性能,可见Vue是暴进去的一个一个性能类函数,咱们进入源码一探到底: import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'//判断是不是服务端渲染import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'/** * 增加全局的API */initGlobalAPI(Vue)/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})/** * 服务端渲染须要 */Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})/** * vue版本号 这里的'__VERSION__'为占位符,公布版本时将被主动替换 */Vue.version = '__VERSION__'export default Vue那么咱们看到咱们的外围Vue来自'./instance/index'那咱们再去一探到底,可想而知外面必然有一个Vue函数类 ...

October 27, 2022 · 11 min · jiezi

关于vue.js:在vue的vfor中key为什么不能用index

写在后面在前端中,次要波及的基本上就是 DOM的相干操作 和 JS,咱们都晓得 DOM 操作是比拟耗时的,那么在咱们写前端相干代码的时候,如何缩小不必要的 DOM 操作便成了前端优化的重要内容。 虚构DOM(virtual DOM)在 jQuery 时代,基本上所有的 DOM 相干的操作都是由咱们本人编写(当然博主是没有写过 jQuery 滴,可能因为博主太年老了吧,错过了 jQuery 大法的时代),如何操作 DOM, 操作 DOM 的机会应该如何安顿成了决定性能的要害,而到了 Vue、React 这些框架流行的时代,框架采纳数据驱动视图,封装了大量的 DOM 操作细节,使得更多的 DOM 操作细节的优化从开发者本人抉择、管制转移到了框架外部,那么在学会应用框架后,如果想要更加深刻学习框架,那就须要搞懂框架封装的底层原理,其中十分外围的一部分就是虚构DOM(virtual DOM) 什么是虚构 DOM简而言之,就是通过 JS 来模仿 DOM 构造,对于纠结以什么 JS 数据结构来模仿 DOM 并没有一套规范,只有能齐全笼罩 DOM 的所有构造即可,上面以较为通用的形式演示一下。 通过对 DOM 构造的剖析,咱们能够用 tag 示意 DOM 节点的类型,props 示意 DOM 节点的所有属性,包含 style、class 等,children 示意子节点(没有子节点则示意内容),这样子咱们就把整个 DOM 通过 JS 模仿进去了,而后呢? 而后看看下一章~~~ // DOM<div class="container"> <h1 style="color: black;" class="title">HeiHei~~</h1> <div class="inner-box"> <span class="myname">I am Yimwu</span> </div></div>// VDOMlet vdom = { tag: 'div', props: { classname: 'container', }, children: [ { tag: 'h1', props: { classname: 'title', style: { color: 'black' } }, children: 'HeiHei~~' }, { tag: 'div', props: { classname: 'inner-box', }, children: [ { tag: 'span', props: { classname: 'myname' }, children: 'I am Yimwu' } ] } ]}虚构 DOM 的作用当咱们可能在 JS 中模拟出 DOM 构造后,咱们就能够通过 JS 来对 DOM 操作进行优化了,怎么优化呢,这个时候 diff 算法就该退场了。当咱们通过 JS 对 DOM 进行批改后,并不会间接触发 DOM 更新,而是会学生成一个新的虚构 DOM,而后利用 diff 算法与批改前生成的虚构 DOM 进行比拟,找出须要批改的点,最初进行真正的 DOM 更新操作 ...

October 27, 2022 · 2 min · jiezi

关于vue.js:前端高频vue面试题合集

路由的hash和history模式的区别Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。 1. hash模式简介: hash模式是开发中默认的模式,它的URL带着一个# 特点:hash值会呈现在URL外面,然而不会呈现在HTTP申请中,对后端齐全没有影响。所以扭转hash值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的IE浏览器也反对这种模式。hash路由被称为是前端路由,曾经成为SPA(单页面利用)的标配。 原理: hash模式的次要原理就是onhashchange()事件: window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1);}应用onhashchange()事件的益处就是,在页面的hash值发生变化时,无需向后端发动申请,window就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash值变动对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的hash值和对应的URL关联起来了。 2. history模式简介: history模式的URL中没有#,它应用的是传统的路由散发模式,即用户在输出一个URL时,服务器会接管这个申请,并解析这个URL,而后做出相应的逻辑解决。 特点: 相比hash模式更加难看。然而,history模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回404。 API: history api能够分为两大部分,切换历史状态和批改历史状态: 批改历史状态:包含了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了url,但浏览器不会立刻向后端发送申请。如果要做到扭转url但又不刷新页面的成果,就须要前端用上这两个API。切换历史状态: 包含forward()、back()、go()三个办法,对应浏览器的后退,后退,跳转操作。尽管history模式抛弃了俊俏的#。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。 如果想要切换到history模式,就要进行以下配置(后端也要进行配置): const router = new VueRouter({ mode: 'history', routes: [...]})3. 两种模式比照调用 history.pushState() 相比于间接批改 hash,存在以下劣势: pushState() 设置的新 URL 能够是与以后 URL 同源的任意 URL;而 hash 只可批改 # 前面的局部,因而只能设置与以后 URL 同文档的 URL;pushState() 设置的新 URL 能够与以后 URL 截然不同,这样也会把记录增加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录增加到栈中;pushState() 通过 stateObject 参数能够增加任意类型的数据到记录中;而 hash 只可增加短字符串;pushState() 可额定设置 title 属性供后续应用。hash模式下,仅hash符号之前的url会被蕴含在申请中,后端如果没有做到对路由的全笼罩,也不会返回404谬误;history模式下,前端的url必须和理论向后端发动申请的url统一,如果没有对用的路由解决,将返回404谬误。hash模式和history模式都有各自的劣势和缺点,还是要依据理论状况选择性的应用。 ...

October 27, 2022 · 10 min · jiezi

关于vue.js:每日一题之Vue数据劫持原理是什么

什么是数据劫持?定义: 数据劫持,指的是在拜访或者批改对象的某个属性时,通过一段代码拦挡这个行为,进行额定的操作或者批改返回后果。 简略地说,就是当咱们 触发函数的时候 动一些手脚做点咱们本人想做的事件,也就是所谓的 "劫持"操作 数据劫持的两种计划:Object.definePropertyProxy1).Object.defineProperty语法:Object.defineProperty(obj,prop,descriptor) 参数: obj:指标对象prop:须要定义的属性或办法的名称descriptor:指标属性所领有的个性可供定义的个性列表: value:属性的值writable:如果为false,属性的值就不能被重写。get: 一旦指标属性被拜访就会调回此办法,并将此办法的运算后果返回用户。set:一旦指标属性被赋值,就会调回此办法。configurable:如果为false,则任何尝试删除指标属性或批改属性性以下个性(writable, configurable, enumerable)的行为将被有效化。enumerable:是否能在for...in循环中遍历进去或在Object.keys中列举进去。例子在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知,如下: var data = {name:'test'}Object.keys(data).forEach(function(key){ Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ console.log('get'); }, set:function(){ console.log('监听到数据产生了变动'); } })});data.name //控制台会打印出 “get”data.name = 'hxx' //控制台会打印出 "监听到数据产生了变动"下面的这个例子能够看出,咱们齐全能够管制对象属性的设置和读取。在Vue中,在很多中央都十分奇妙的使用了Object.defineProperty这个办法,具体用在哪里并且它又解决了哪些问题,上面就简略的说一下: 监听对象属性的变动它通过observe每个对象的属性,增加到订阅器dep中,当数据发生变化的时候收回一个notice。 相干源代码如下:(作者采纳的是ES6+flow写的,代码在src/core/observer/index.js模块外面) export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function) { const dep = new Dep()//创立订阅对象 const property = Object.getOwnPropertyDe述 //属性的形容个性外面如果configurable为false则属性的任何批改将有效 if (property && property.configurable === false) { return }scriptor(obj, key)//获取obj对象的key属性的描 // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set let childOb = observe(val)//创立一个观察者对象 Object.defineProperty(obj, key, { enumerable: true,//可枚举 configurable: true,//可批改 get: function reactiveGetter () { const value = getter ? getter.call(obj) : val//先调用默认的get办法取值 //这里就劫持了get办法,也是作者一个奇妙设计,在创立watcher实例的时候,通过调用对象的get办法往订阅器dep上增加这个创立的watcher实例 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value//返回属性值 }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val//先取旧值 if (newVal === value) { return } //这个是用来判断生产环境的,能够忽视 if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = observe(newVal)//持续监听新的属性值 dep.notify()//这个是真正劫持的目标,要对订阅者发告诉了 } })}以上是Vue监听对象属性的变动,那么问题来了,咱们常常在传递数据的时候往往不是一个对象,很有可能是一个数组,那是不是就没有方法了呢,答案显然是否则的。那么上面就看看作者是如何监听数组的变动: ...

October 27, 2022 · 4 min · jiezi

关于vue.js:Vue-组件化编程

1.1 模块与组件、模块化与组件化1.1.1 模块了解:向外提供特定性能的 js 程序,个别就是一个 js 文件为什么:js 文件很多很简单作用:复用 js,简化 js 的编写,进步 js 运行效率1.1.2 组件了解:用来实现部分(特定)性能成果的代码汇合( html/css/js/image.....)为什么:一个界面的性能很简单作用:复用编码,简化我的项目编码,进步运行效率1.1.3 模块化当利用中的 js 都以模块来编写的,那这个利用就是一个模块化的利用。 1.1.4 组件化当利用中的性能都是多组件的形式来编写的,那这个利用就是一个组件化的利用。 1.2 非单文件组件1.2.1 阐明模板编写没有提醒没有构建过程,无奈将 ES6 转换成 ES5不反对组件的 CSS真正开发中简直不必1.2.2 根本应用<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>根本应用</title> <script src="../js/vue.js"></script></head><body> <!-- Vue中应用组件的三大步骤: 一、定义组件(创立组件) 二、注册组件 三、应用组件(写组件标签) 一、如何定义一个组件? 应用 Vue.extend(options) 创立,其中 options 和 new Vue(options) 时传入的那个 options 简直一样,但也有点区别; 区别如下: 1.el 不要写,为什么? ——— 最终所有的组件都要通过一个 vm 的治理,由 vm 中的 el 决定服务哪个容器。 2.data 必须写成函数,为什么? ———— 防止组件被复用时,数据存在援用关系。 备注:应用 template 能够配置组件构造。 二、如何注册组件? 1.部分注册:靠 new Vue 的时候传入 components 选项 2.全局注册:靠 Vue.component('组件名',组件) 三、编写组件标签: <school></school> --> <div id="root"> <!-- <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{schoolAddress}}</h2> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{studentAge}}</h2> --> <!-- 第三步:应用组件 --> <xuexiao></xuexiao> <hr></hr> <student></student> </div> <div id="root2"> <xuexiao></xuexiao> <hr></hr> <student></student> </div> <script> // 阻止 vue 在启动时生成生产提醒 Vue.config.productionTip = false // 第一步:创立组件 const school = Vue.extend({ template: ` <div> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{schoolAddress}}</h2> </div>`, data() { return { schoolName: "厦门大学", schoolAddress: "厦门", } } }) // 第一步:创立组件 const student = Vue.extend({ template: ` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{studentAge}}</h2> </div>`, // 组件定义时,肯定不要写 el 配置项,因为最终所有的组件都要被一个 vm 治理,由 vm 决定服务于哪个容器 // el: "#rtoot", data() { return { studentName: "李四", studentAge: 22, } } }) // 全局注册组件 Vue.component("xuexiao", school) Vue.component("student", student) new Vue({ el: "#root", data: { schoolName: "北京大学", schoolAddress: "北京", studentName: "张三", studentAge: 18, }, // 第二步:注册组件 components: { xuexiao: school, student } }) new Vue({ el: "#root2", data: { } }) </script></body></html>1.2.3 注意事项<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>几个留神点</title> <script src="../js/vue.js"></script></head><body> <!-- 几个留神点: 1.对于组件名: 一个单词组成: 第一种写法(首字母小写):school 第二种写法(首字母大写):School 多个单词组成: 第一种写法( kebab-case 命名):my-school 第二种写法(CamelCase 命名):MySchool (须要 Vue 脚手架反对) 备注: (1).组件名尽可能回避 HTML 中已有的元素名称,例如:h2、H2 都不行。 (2).能够应用 name 配置项指定组件在开发者工具中出现的名字。 2.对于组件标签: 第一种写法:<school></school> 第二种写法:<school/> 备注:不必应用脚手架时,<school/> 会导致后续组件不能渲染。 3.一个简写形式: const school = Vue.extend(options) 可简写为:const school = options --> <div id="root"> <h2>{{ message }}</h2> <!-- 写法一:首字母小写 --> <school></school> <!-- 写法二:首字母大写 --> <School></School> <!-- 写法三 --> <!-- 不必脚手架时,当应用多个组件,会导致后续组件不能渲染 --> <school /> <school /> </div> <script> // 阻止 vue 在启动时生成生产提醒 Vue.config.productionTip = false // 定义组件 // const school = Vue.extend({ // name: "MySchool", // template: ` // <div> // <h2>学校名称:{{ name }}</h2> // <h2>学校地址:{{ address }}</h2> // </div> // `, // data() { // return { // name: "北京大学", // address: "北京" // } // } // }) // 定义组件 const school = { name: "MySchool", template: ` <div> <h2>学校名称:{{ name }}</h2> <h2>学校地址:{{ address }}</h2> </div> `, data() { return { name: "北京大学", address: "北京" } } } new Vue({ el: "#root", data: { message: "欢送学习 Vue!" }, // 注册组件 components: { school } }) </script></body></html>1.2.4 组件的嵌套 ...

October 26, 2022 · 5 min · jiezi

关于vue.js:P5-vue3-Class-与-Style-绑定

操作元素的 class 列表和内联款式是数据绑定的一个常见需要。因为它们都是 attribute,所以咱们能够用 v-bind 解决它们:只须要通过表达式计算出字符串后果即可。不过,字符串拼接麻烦且易错。因而,在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的加强。表达式后果的类型除了字符串之外,还能够是对象或数组。对象语法<script>export default { data() { return { test57: 'hellokugou', isActive: true }; },};</script><template> <!--first 搁置字符 --> <div> <p class="active">{{ test57 }}</p> <!--second 搁置对象 --> <p :class="{active:isActive}"> hello </p> <button @click="isActive=!isActive">change_active</button> </div></template><style>.active{ font-size: 80px; color: blue;}</style> 对象<script>export default { data() { return { test57: 'hellokugou', isActive: true, error:{}, classObj:{ active: true, helloWorld: true, } }; }, computed:{ chassObjCom:function (){ return{ active:this.isActive && !this.error, helloWorld: this.error } } }};</script><template> <!--first 搁置字符 --> <div> <p class="active">{{ test57 }}</p> <!--second 搁置对象 --> <p :class="{active:isActive}"> hello </p> <!--和一般class一起不会笼罩 --> <p :class="{active:isActive}" class="helloWorld"> hello </p> <p :class="classObj">{{ test57 }}</p> <button @click="isActive=!isActive">change_active</button> <p :class="chassObjCom">hellokugou computed</p> </div></template><style>.active{ font-size: 80px; color: blue;}.helloWorld{ background: aqua;}</style> ...

October 26, 2022 · 1 min · jiezi

关于vue.js:Vue中的diff算法深度解析

模板tamplate通过parse,optimize,generate等一些列操作之后,把AST转为render function code进而生成虚构VNode,模板编译阶段根本曾经实现了,那么这一章,咱们来探讨一下Vue中的一个算法策略--dom diff 首先来介绍下什么叫dom diff 什么是虚构dom咱们通过后面的章节学习曾经晓得,要晓得渲染实在DOM的开销是很大的,比方有时候咱们批改了某个数据,如果间接渲染到实在dom上会引起整个dom树的重绘和重排,有没有可能咱们只更新咱们批改的那一小块dom而不要更新整个dom呢? 为了解决这个问题,咱们的解决方案是--依据实在DOM生成一颗virtual DOM,当virtual DOM某个节点的数据扭转后会生成一个新的Vnode,而后Vnode和oldVnode作比照,发现有不一样的中央就间接批改在实在的DOM上,而后使oldVnode的值为Vnode。这也就是咱们所说的一个虚构dom diff的过程 图示 传统的Diff算法所消耗的工夫复杂度为O(n^3),那么这个O(n^3)是怎么算进去的? 传统diff算法工夫复杂度为n(第一次Old与新的所有节点比照)----O(n)传统diff算法工夫复杂度为n(第二次Old树的所有节点与新的所有节点比照)----O(n^2)新树的生成,节点可变编辑,工夫复杂度为n(遍历以后树)----O(n^3)第一次比照 (1:n) 第二次比照 (1:n) 第n次比照 (n:n) 到这里那么n个节点与n个节点暴力比照就比照完了,那么就开启第三轮可编辑树节点遍历,更改之后的树由vdom(old)到vdom(new) 故而传统diff算法O(n^3)是这么算进去的,然而这不是咱们明天钻研的重点。 古代diff算法古代diff算法策略说的是,同层级比拟,广度优先 那么这里的话咱们要深刻源码了,在深刻源码之前咱们在心中应该造成这样一个概念,整个diff的流程是什么?咱们再比照着源码解读 diff算法流程图 深刻源码咱们在Vue初始化的时候调用lifecycleMixin函数的时候,会给Vue的原型上挂载_update办法 _updateVue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { //会调用申明周期中的beforeUpdate回调函数 callHook(vm, 'beforeUpdate') } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. //若组件自身的vnode未生成,间接用传入的vnode生成dom if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ) // no need for the ref nodes after initial patch // this prevents keeping a detached DOM tree in memory (#5851) vm.$options._parentElm = vm.$options._refElm = null } else { //对新旧vnode进行diff // updates vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el }咱们在这里能够看到vm.$el = vm.__patch__办法,追根溯源_patch_的定义: ...

October 26, 2022 · 22 min · jiezi

关于vue.js:Vue响应式依赖收集原理分析vue高级必备

背景在 Vue 的初始化阶段,_init 办法执行的时候,会执行 initState(vm) ,它的定义在 src/core/instance/state.js 中。在初始化 data 和 props option 时咱们留神 initProps 和 initData 办法中都调用了 observe 办法。通过 observe (value),就能够将数据变成响应式。 export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }}initProps ...

October 26, 2022 · 9 min · jiezi

关于vue.js:vue源码分析diff算法核心原理

这一节,仍然是深刻分析Vue源码系列,上几节内容介绍了Virtual DOM是Vue在渲染机制上做的优化,而渲染的外围在于数据变动时,如何高效的更新节点,这就是diff算法。因为源码中对于diff算法局部流程简单,间接分析每个流程不易于了解,所以这一节咱们换一个思路,参考源码来手动实现一个简易版的diff算法。之前讲到Vue在渲染机制的优化上,引入了Virtual DOM的概念,利用Virtual DOM形容一个实在的DOM,实质上是在JS和实在DOM之间架起了一层缓冲层。当咱们通过大量的JS运算,并将最终后果反馈到浏览器进行渲染时,Virtual DOM能够将多个改变合并成一个批量的操作,从而缩小 dom 重排的次数,进而缩短了生成渲染树和绘制节点所花的工夫,达到渲染优化的目标。之前的章节,咱们简略的介绍了Vue中Vnode的概念,以及创立Vnode到渲染Vnode再到实在DOM的过程。如果有遗记流程的,能够参考后面的章节剖析。 从render函数到创立虚构DOM,再到渲染实在节点,这一过程是残缺的,也是容易了解的。然而引入虚构DOM的外围不在这里,而在于当数据发生变化时,如何最优化数据变动到视图更新的过程。这一个过程才是Vnode更新视图的外围,也就是常说的diff算法。上面跟着我来实现一个简易版的diff算法 8.1 创立根底类代码编写过程会遇到很多根本类型的判断,第一步须要先将这些办法封装。 class Util { constructor() {} // 检测根底类型 _isPrimitive(value) { return (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean') } // 判断值不为空 _isDef(v) { return v !== undefined && v !== null }}// 工具类的应用const util = new Util()8.2 创立VnodeVnode这个类在之前章节曾经剖析过源码,实质上是用一个对象去形容一个实在的DOM元素,简易版关注点在于元素的tag标签,元素的属性汇合data,元素的子节点children,text为元素的文本节点,简略的形容类如下: class VNode { constructor(tag, data, children) { this.tag = tag; this.data = data; this.children = children; this.elm = '' // text属性用于标记Vnode节点没有其余子节点,只有纯文本 this.text = util._isPrimitive(this.children) ? this.children : '' }}8.3 模仿渲染过程接下来须要创立另一个类模仿将render函数转换为Vnode,并将Vnode渲染为实在DOM的过程,咱们将这个类定义为Vn,Vn具备两个根本的办法createVnode, createElement, 别离实现创立虚构Vnode,和创立实在DOM的过程。 ...

October 26, 2022 · 4 min · jiezi

关于vue.js:vue源码分析vmodel的本质

双向数据绑定这个概念或者大家并不生疏,视图影响数据,数据同样影响视图,两者间有双向依赖的关系。在响应式零碎构建的上,中,下篇我曾经对数据影响视图的原理具体论述分明了。而如何实现视图影响数据这一关联?这就是本节探讨的重点:指令v-model。因为v-model和后面介绍的插槽,事件统一,都属于vue提供的指令,所以咱们对v-model的剖析形式和以往大同小异。剖析会围绕模板的编译,render函数的生成,到最初实在节点的挂载程序执行。最终咱们仍然会失去一个论断,v-model无论什么应用场景,实质上都是一个语法糖。 11.1 表单绑定11.1.1 根底应用v-model和表单脱离不了关系,之所以视图能影响数据,实质上这个视图须要可交互的,因而表单是实现这一交互的前提。表单的应用以<input > <textarea> <select>为外围,更细的划分联合v-model的应用如下: // 一般输入框<input type="text" v-model="value1">// 多行文本框<textarea v-model="value2" cols="30" rows="10"></textarea>// 单选框<div class="group"> <input type="radio" value="one" v-model="value3"> one <input type="radio" value="two" v-model="value3"> two</div> // 原生单选框的写法 注:原生单选框的写法须要通过name绑定一组单选,两个radio的name属性雷同,能力体现为互斥<div class="group"> <input type="radio" name="number" value="one">one <input type="radio" name="number" value="two">two</div>// 多选框 (原始值: value4: [])<div class="group"> <input type="checkbox" value="jack" v-model="value4">jack <input type="checkbox" value="lili" v-model="value4">lili</div>// 下拉选项<select name="" id="" v-model="value5"> <option value="apple">apple</option> <option value="banana">banana</option> <option value="bear">bear</option></select>接下来的剖析,咱们以一般输入框为例 <div id="app"> <input type="text" v-model="value1"></div>new Vue({ el: '#app', data() { return { value1: '' } }})进入注释前先回顾一下模板到实在节点的过程。 模板解析成AST树;AST树生成可执行的render函数;render函数转换为Vnode对象;依据Vnode对象生成实在的Dom节点。接下来,咱们先看看模板解析为AST树的过程。 11.1.2 AST树的解析模板的编译阶段,会调用var ast = parse(template.trim(), options)生成AST树,parse函数的其余细节这里不开展剖析,后面的文章或多或少都波及过,咱们还是把关注点放在模板属性上的解析,也就是processAttrs函数上。 应用过vue写模板的都晓得,vue模板属性由两局部组成,一部分是指令,另一部分是一般html标签属性。z这也是属性解决的两大分支。而在指令的细分畛域,又将v-on,v-bind做非凡的解决,其余的一般分支会执行addDirective过程。 // 解决模板属性function processAttrs(el) { var list = el.attrsList; var i, l, name, rawName, value, modifiers, syncGen, isDynamic; for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name; // v-on:click value = list[i].value; // doThis if (dirRE.test(name)) { // 1.针对指令的属性解决 ··· if (bindRE.test(name)) { // v-bind分支 ··· } else if(onRE.test(name)) { // v-on分支 ··· } else { // 除了v-bind,v-on之外的一般指令 ··· // 一般指令会在AST树上增加directives属性 addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]); if (name === 'model') { checkForAliasModel(el, value); } } } else { // 2. 一般html标签属性 } }}在深刻分析Vue源码 - 揭秘Vue的事件机制这一节,咱们介绍了AST产生阶段对事件指令v-on的解决是为AST树增加events属性。相似的,一般指令会在AST树上增加directives属性,具体看addDirective函数。 ...

October 26, 2022 · 7 min · jiezi

关于vue.js:假如问你是怎样优化Vue项目的该怎么回答

咱们在开发Vue我的项目时候都晓得,在vue开发中某些问题如果后期疏忽掉,过后不会呈现显著的成果,然而越向后开发越难做,而且我的项目做久了就会呈现问题,这就是所说的蝴蝶效应,这样前期的保护老本会十分高,并且我的项目上线后还会影响用户体验,也会呈现加载慢等一系列的性能问题,上面举一个简略的例子。 举个简略的例子如果加载我的项目的时候加载一张图片须要0.1s,其实算不了什么能够忽略不计,然而如果我有20张图片,这就是2s的工夫, 2s的工夫不算长一下就过来了,然而这仅仅的只是加载了图片,还有咱们的js,css都须要加载,那就须要更长的工夫,可能是5s,6s...,比方加载工夫是5s,用户可能等都不会等,间接敞开咱们的网站,最初导致咱们网站流量很少,流量少就没人用,没人用就没有钱,没有钱就涨不了工资,涨不了工资最初就是跑路了。通过下面的例子能够看出性能问题是如许的重要甚至关系到了咱们薪资,那如何防止这些问题呢?废话不多说,上面分享一下本人在写我的项目的时用到的一些优化计划以及注意事项。 1.不要将所有的数据都放在data中能够将一些不被视图渲染的数据申明到实例内部而后在外部援用援用,因为Vue2初始化数据的时候会将data中的所有属性遍历通过Object.definePrototype从新定义所有属性;Vue3是通过Proxy去对数据包装,外部也会波及到递归遍历,在属性比拟多的状况下很消耗性能 <template> <button @click="updateValue">{{msg}}</button></template><script>let keys=true;export default { name:'Vueinput', data(){ return { msg:'true' } }, created(){ this.text = 'text' }, methods:{ updateValue(){ keys = !keys this.msg = keys?'true':'false' } }}</script> 2.watch 尽量不要应用deep:true深层遍历因为watch不存在缓存,是指定监听对象,如果deep:true,并且监听对象类型状况下,会递归解决收集依赖,最初触发更新回调3. vue 在 v-for 时给每项元素绑定事件须要用事件代理vue源码中是通过addEventLisener去给dom绑定事件的,比方咱们应用v-for须要渲染100条数据并且并为每个节点增加点击事件,如果每个都绑定事件那就存在很多的addEventLisener,这里不用说性能上必定不好,那咱们就须要应用事件代理解决这个问题<template> <ul @click="EventAgent"> <li v-for="(item) in mArr" :key="item.id" :data-set="item">{{item.day}}</li> </ul></template><script>let keys=true;export default { name:'Vueinput', data(){ return { mArr:[{ day:1, id:'xx1' },{ day:2, id:'xx2' },{ day:2, id:'xx2' }, ... ] } }, methods:{ EventAgent(e){ // 留神这里 在我的项目中千万不要写的这么简略,我只是为了不便了解才这么写的 console.log(e.target.getAttribute('data-set')) } }}</script>4. v-for尽量不要与v-if一起应用vue的编译过程是template->vnode,看上面的例子// 假如data中存在一个arr数组<div id="container"> <div v-for="(item,index) in arr" v-if="arr.length" key="item.id">{{item}}</div></div>下面的例子有可能大家常常这么做,其实这么做也能达到成果然而在性能下面不是很好,因为Ast在转化为render函数的时候会将每个遍历生成的对象都会退出if判断,最初在渲染的时候每次都每个遍历对象都会判断一次须要不须要渲染,这样就很节约性能,为了防止这个问题咱们把代码略微改一下<div id="container" v-if="arr.length"> <div v-for="(item,index) in arr" >{{item}}</div></div>这样就只判断一次就能达到渲染成果了,是不是更好一些那参考 前端vue面试题具体解答 ...

October 26, 2022 · 3 min · jiezi

关于vue.js:前端一面高频vue面试题总结

对Vue SSR的了解Vue.js 是构建客户端应用程序的框架。默认状况下,能够在浏览器中输入 Vue 组件,进行生成 DOM 和操作 DOM。然而,也能够将同一个组件渲染为服务端的 HTML 字符串,将它们间接发送到浏览器,最初将这些动态标记"激活"为客户端上齐全可交互的应用程序。 SSR也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端 长处 :SSR 有着更好的 SEO、并且首屏加载速度更快 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会期待 Ajax 异步实现后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax获取到的内容;而 SSR 是间接由服务端返回曾经渲染好的页面(数据曾经蕴含在页面中),所以搜索引擎爬取工具能够抓取渲染好的页面更快的内容达到工夫(首屏加载更快): SPA 会期待所有 Vue 编译后的 js 文件都下载实现后,才开始进行页面的渲染,文件下载等须要肯定的工夫等,所以首屏渲染须要肯定的工夫;SSR 间接由服务端渲染好页面间接返回显示,无需期待下载 js 文件及再去渲染等,所以 SSR 有更快的内容达到工夫毛病 : 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子,当咱们须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境。服务器会有更大的负载需要 在 Node.js 中渲染残缺的应用程序,显然会比仅仅提供动态文件的 server 更加大量占用CPU资源 (CPU-intensive - CPU 密集),因而如果你意料在高流量环境 ( high traffic ) 下应用,请筹备相应的服务器负载,并明智地采纳缓存策略其根本实现原理 app.js 作为客户端与服务端的专用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 应用。客户端 entry 次要作用挂载到 DOM 上,服务端 entry 除了创立和返回实例,还进行路由匹配与数据预获取。webpack 为客服端打包一个 Client Bundle ,为服务端打包一个 Server Bundle 。服务器接管申请时,会依据 url,加载相应组件,获取和解析异步数据,创立一个读取 Server Bundle 的 BundleRenderer,而后生成 html 发送给客户端。客户端混合,客户端收到从服务端传来的 DOM 与本人的生成的 DOM 进行比照,把不雷同的 DOM 激活,使其能够可能响应后续变动,这个过程称为客户端激活 。为确保混合胜利,客户端与服务器端须要共享同一套数据。在服务端,能够在渲染之前获取数据,填充到 stroe 里,这样,在客户端挂载到 DOM 之前,能够间接从 store里取数据。首屏的动态数据通过 window.__INITIAL_STATE__发送到客户端Vue SSR 的实现,次要就是把 Vue 的组件输入成一个残缺 HTML, vue-server-renderer 就是干这事的Vue SSR须要做的事多点(输入残缺 HTML),除了complier -> vnode,还需如数据获取填充至 HTML、客户端混合(hydration)、缓存等等。相比于其余模板引擎(ejs, jade 等),最终要实现的目标是一样的,性能上可能要差点 ...

October 26, 2022 · 5 min · jiezi

关于vue.js:写过vue自定义指令吗原理是什么

背景看了一些自定义指令的文章,然而探索其原理的文章却不多见,所以我决定水一篇。 如何自定义指令?其实对于这个问题官网文档上曾经有了很好的示例的,咱们先来温故一下。 除了外围性能默认内置的指令 (v-model 和 v-show),Vue 也容许注册自定义指令。留神,在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下: 当页面加载时,该元素将取得焦点 (留神: autofocus 在挪动版 Safari 上不工作)。事实上,只有你在关上这个页面后还没点击过任何内容,这个输入框就该当还是处于聚焦状态。当初让咱们用指令来实现这个性能: // 注册一个全局自定义指令 `v-focus`Vue.directive('focus', {// 当被绑定的元素插入到 DOM 中时…… inserted: function (el) { // 聚焦元素 el.focus() }})如果想注册部分指令,组件中也承受一个 directives 的选项: directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } }}而后你能够在模板中任何元素上应用新的 v-focus property,如下: <input v-focus>指令外部提供的钩子函数一个指令定义对象能够提供如下几个钩子函数 (均为可选): bind: 只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。inserted: 被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。update: 所在组件的 VNode 更新时调用,然而可能产生在其子 VNode 更新之前。指令的值可能产生了扭转,也可能没有。然而你能够通过比拟更新前后的值来疏忽不必要的模板更新 (具体的钩子函数参数见下)。componentUpdated: 指令所在组件的 VNode 及其子VNode全副更新后调用。unbind: 只调用一次,指令与元素解绑时调用。钩子函数参数指令钩子函数会被传入以下参数: el: 指令所绑定的元素,能够用来间接操作 DOM 。binding: 一个对象,蕴含以下 property:name:指令名,不包含 v- 前缀。value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否扭转都可用。expression:字符串模式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。arg: 传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。modifiers:一个蕴含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。vnode: Vue 编译生成的虚构节点。能够参考官网的 VNode API 来理解更多详情。oldVnode:上一个虚构节点,仅在 update 和 componentUpdated 钩子中可用。除了 el 之外,其它参数都应该是只读的,切勿进行批改。如果须要在钩子之间共享数据,倡议通过元素的 dataset 来进行。 ...

October 26, 2022 · 4 min · jiezi

关于vue.js:Vue27正式发布终于可以在Vue2项目中使用Vue3的特性了真香

前言只管当初 Vue3 是默认版本,但还有许多用户、相干库、周边生态应用的是 Vue2,且因为依赖兼容性、浏览器反对要求或没有足够的带宽降级,导致不得不持续应用 Vue2。难道 Vue3 公布了这么多“真香”的个性,咱们 Vue2 的用户与我的项目就只能眼巴巴地看着?当然不是!有一个好消息是,前两天 Vue2.7 正式公布了。在此版本中,从 Vue3 向后移植了一些最重要的性能,以便 Vue2 用户也能够从中受害。 注释一、向后移植的性能在 Vue2.7 中,Vue3 的很多性能将会向后移植,以便于 Vue2 的很多我的项目能够应用 Vue3 的一些很好用的新个性,例如: Composition API (组合式 API)SFC < script setup> (单文件组件 < script setup>)SFC CSS v-bind (单文件组件 CSS 中的 v-bind)此外,还反对以下 API: defineComponent():具备改良的类型推断(与Vue.extend相比);h()、useSlot()、useAttrs()、useCssModules();set()、del() 和 nextTick() 在 ESM 构建中也作为命名导出提供;反对 emits,但仅用作类型查看用处(不影响运行时行为)。Vue2.7 还反对在模板表达式中应用 ESNext 语法。应用构建零碎时,编译后的模板渲染函数将通过为一般 JavaScript 配置的雷同 loaders / plugins。这意味着如果为.js文件配置了 Babel,它也将利用于 SFC 模板中的表达式。 当初你终于能够在模版外面用可选链 formData?.userInfo?.userId,而不必写一长串 && ,也能够间接应用零合并操作符 ?? 来给变量赋一个默认值了,而不须要用可能导致 bug 的或 ||。 留神:在 ESM 构建中,这些 API 作为命名导出提供(仅限于命名导出) ...

October 25, 2022 · 2 min · jiezi

关于vue.js:vuecli3多环境打包配置

需要一个我的项目别离有开发环境、测试环境和线上环境等多个环境,因为不同环境所须要的环境变量不一样,例如http申请的域名或门路不同,故须要对不同环境进行打包配置。 实现思路模式Vue CLI默认有三个模式:development、test、production,当运行vue-cli-service命令时能够通过 --mode 指定模式执行。 举个例子:启动开发环境服务命令(默认为development环境,可不必指定mode):vue-cli-service serve启动测试环境服务命令:vue-cli-service serve --mode test测试环境打包命令:vue-cli-service build --mode test正式环境打包命令(production,可不必指定mode):vue-cli-service build环境变量在我的项目根目录创立文件 .env.[mode](mode为模式名称) 能够定义对应环境的环境变量。 举个例子:.env.development(开发环境配置).env.test(测试环境配置).env.production(生产环境配置)文件内容只蕴含环境变量的“键=值”对,如: NODE_ENV = 'production'VUE_APP_BASE_API = 'http://xxx'NODE_ENV为模式名称;VUE_APP_为前缀的变量可通过 process.env.VUE_APP_XXX 的形式进行拜访 利用:http申请门路在不同环境下打包配置新建3个环境变量文件,别离是.env.development、.env.test、.env.production .env.development(/api为代理门路) NODE_ENV = 'development'VUE_APP_BASE_API = '/api'.env.test NODE_ENV = 'test'VUE_APP_BASE_API = 'http://www.test.com'.env.production NODE_ENV = 'production'VUE_APP_BASE_API = 'http://www.pro.com'创立axios对象进行http申请 request.js const request = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 60 * 1000, method: 'get'})package.json配置打包命令。默认有生产环境和开发环境命令,现加上测试环境命令 build-test "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build-test": "vue-cli-service build --mode test" }执行打包命令测试环境:npm run build-test线上环境:npm run build

October 25, 2022 · 1 min · jiezi

关于vue.js:Vue3必会技巧自定义Hooks

Vue3自定义Hooks定义:集体了解:一些可复用的办法像钩子一样挂着,能够随时被引入和调用以实现高内聚低耦合的指标,应该都能算是hook;为什么Vue3要用自定义Hook?:论断:就是为了让Compoosition Api更好用更饱满,让写Vue3更畅快!像写诗一样写代码!其实这个问题更深意义是为什么Vue3比Vue2更好!无外呼性能大幅度晋升,其实编码体验也是Vue3的长处Composition Api的引入(解决Option Api在代码量大的状况下的强耦合) 让开发者有更好的开发体验。 集体碎碎念:然而这些所谓的进步开发体验都是须要开发者一直学习养成编码好习惯,同样是Vue3写Compoosition Api有的人就能写得和诗一样,有的人却能写得像一样(衷心希望每个开发者都有一颗对技术热衷的心,不要为了开发而开发,前人写翔让前人尝!道歉最近因为保护老我的项目太多感叹) 写Vue3请解脱Vue2无脑this的思维:写Vue2中很多同学养成了 Option Api无脑this的习惯,来到Vue3的Composition Api还是习惯性想用this,更有人为了写this不惜引入getCurrentInstance!这大可不必!Composition Api的长处之一就是解脱无脑this导致的强耦合,性能之间相互this,变量和办法在各个办法混淆,无处不在的this是强耦合的,尽管不便,然而碎片化的option api 前期保护是麻烦的。 我置信写Vue2的同学,肯定深有感触,一个组件下定义大量变和大量办法,办法嵌套办法,办法之间相互共享变量,保护这样的代码,看似容易了解的Option Api写法,咱们须要在methos、data、template之间来回切,Option Api这种写法,代码量和性能玲珑时是非常简单明了的,然而代码量一多,性能一简单,我置信review代码的时候头都痛。 绝对的Composition Api在性能简单、代码量微小的组件下,咱们配合自定义Hooks,将代码通过性能分块写,响应变量和办法在一起定义和调用,这样前期咱们改性能A只须要关注性能A块下的代码,不会像Vue2在Option Api须要同时关注methos和data。。。。。 几张动图再来温习一遍 Composition Api 好!谢谢 大帅老猿 老师做的动图,Composition Api VS Option Api 的优缺点非常明了展现在了动画上!Option Api代码量少还好,代码量多容易导致高耦合! 阐明:下面是Vue2 Option Api的写法,一个组件下含有data 、methos、computed、watch,同一个性能须要离开写在这些函数上,如果代码量少,那看起来仿佛非常明了清晰。一旦代码量大性能简单,各个性能离开写,保护的时候data 、methos、computed、watch都须要来回切,反而显得过于扩散,又高度耦合。 Composition Api解耦Vue2 Option Api实现低耦合高内聚 阐明:如果是Composition Api在性能简单、代码量微小的组件下,咱们配合自定义Hook,将代码按性能分块写,变量和办法在一起定义和调用,比方A性能下集成了响应式变量和办法,咱们前期保护只须要改变A功能模块下的代码,不会像Vue2在Option Api须要同时关注逻辑扩散的methos和data。 所以自定义Hook的写Vue3必须把握的!它无不体现Vue3 Composition Api 低耦合高内聚的思维! 笔者在看了官网文档和开源的admin模板都是大量应用自定义Hooks的! 参考vue实战视频解说:进入学习 大胆定义一下Vue3的自定义Hook:尽管官网没有明确指明或定义什么是自定义Hooks,然而却无处不在用; 以函数模式抽离一些可复用的办法像钩子一样挂着,随时能够引入和调用,实现高内聚低耦合的指标; 将可复用性能抽离为内部JS文件函数名/文件名以use结尾,形如:useXX援用时将响应式变量或者办法显式解构裸露进去如:const {nameRef,Fn} = useXX() (在setup函数解构出自定义hooks的变量和办法) 实例:简略的加减法计算,将加法和减法抽离为2个自定义Hooks,并且互相传递响应式数据加法性能-Hookimport { ref, watch } from 'vue';const useAdd= ({ num1, num2 }) =>{ const addNum = ref(0) watch([num1, num2], ([num1, num2]) => { addFn(num1, num2) }) const addFn = (num1, num2) => { addNum.value = num1 + num2 } return { addNum, addFn }}export default useAdd减法性能-Hook//减法性能-Hookimport { ref, watch } from 'vue';export function useSub ({ num1, num2 }){ const subNum = ref(0) watch([num1, num2], ([num1, num2]) => { subFn(num1, num2) }) const subFn = (num1, num2) => { subNum.value = num1 - num2 } return { subNum, subFn }}加减法计算组件<template> <div> num1:<input v-model.number="num1" style="width:100px" /> <br /> num2:<input v-model.number="num2" style="width:100px" /> </div> <span>加法等于:{{ addNum }}</span> <br /> <span>减法等于:{{ subNum }}</span></template><script setup>import { ref } from 'vue'import useAdd from './useAdd.js' //引入主动hook import { useSub } from './useSub.js' //引入主动hook const num1 = ref(2)const num2 = ref(1)//加法性能-自定义Hook(将响应式变量或者办法模式裸露进去)const { addNum, addFn } = useAdd({ num1, num2 })addFn(num1.value, num2.value)//减法性能-自定义Hook (将响应式变量或者办法模式裸露进去)const { subNum, subFn } = useSub({ num1, num2 })subFn(num1.value, num2.value)</script>通过上述示例再来说说Vue3自定义Hooks和Vue2时代Mixin的关系:Mixin有余在 Vue 2 中,mixin 是将局部组件逻辑形象成可重用块的次要工具。然而,他们有几个问题:1、Mixin 很容易发生冲突:因为每个 mixin 的 property 都被合并到同一个组件中,所以为了防止 property 名抵触,你依然须要理解其余每个个性。2、可重用性是无限的:咱们不能向 mixin 传递任何参数来扭转它的逻辑,这升高了它们在形象逻辑方面的灵活性。下面这段是Vue3官网文档的内容,能够概括和补充为: ...

October 25, 2022 · 2 min · jiezi

关于vue.js:Vue3知识点之数据侦测

Vue 的外围之一就是响应式零碎,通过侦测数据的变动,来驱动更新视图。 实现可响应对象的形式通过可响应对象,实现对数据的侦测,从而告知外界数据变动。实现可响应对象的形式: getter 和 setterdefinePropertyProxy对于前两个 API 的应用形式不多赘述,繁多的拜访器 getter/setter 性能绝对简略,而作为 Vue2.x 实现可响应对象的 API - defineProperty ,API 自身存在较多问题。 Vue2.x 中,实现数据的可响应,须要对 Object 和 Array 两种类型采纳不同的解决形式。 Object 类型通过 Object.defineProperty 将属性转换成 getter/setter ,这个过程须要递归侦测所有的对象 key,来实现深度的侦测。 为了感知 Array 的变动,对 Array 原型上几个扭转数组本身的内容的办法做了拦挡,尽管实现了对数组的可响应,但同样存在一些问题,或者说不够不便的状况。同时,defineProperty 通过递归实现 getter/setter 也存在肯定的性能问题。 更好的实现形式是通过 ES6 提供的 Proxy API。 Proxy API 的一些细节Proxy API 具备更加弱小的性能,相比旧的 defineProperty API ,Proxy 能够代理数组,并且 API 提供了多个 traps ,能够实现诸多性能。 这里次要说两个trap: get 、 set , 以及其中的一些比拟容易被疏忽的细节。 细节一:trap 默认行为let data = { foo: 'foo' }let p = new Proxy(data, { get(target, key, receiver) { return target[key] }, set(target, key, value, receiver) { console.log('set value') target[key] = value // ? }})p.foo = 123// set value通过 proxy 返回的对象 p 代理了对原始数据的操作,当对 p 设置时,便能够侦测到变动。然而这么写实际上是有问题,当代理的对象数据是数组时,会报错。 ...

October 25, 2022 · 6 min · jiezi

关于vue.js:vue源码中的nextTick是怎样实现的

一、Vue.nextTick 外部逻辑在执行 initGlobalAPI(Vue) 初始化 Vue 全局 API 中,这么定义 Vue.nextTick。 function initGlobalAPI(Vue) { //... Vue.nextTick = nextTick;}能够看出是间接把 nextTick 函数赋值给 Vue.nextTick,就能够了,非常简单。 二、vm.$nextTick 外部逻辑Vue.prototype.$nextTick = function (fn) { return nextTick(fn, this)};能够看出是 vm.$nextTick 外部也是调用 nextTick 函数。 三、前置常识nextTick 函数的作用能够了解为异步执行传入的函数,这里先介绍一下什么是异步执行,从 JS 运行机制说起。 1、JS 运行机制JS 的执行是单线程的,所谓的单线程就是事件工作要排队执行,前一个工作完结,才会执行后一个工作,这就是同步工作,为了防止前一个工作执行了很长时间还没完结,那下一个工作就不能执行的状况,引入了异步工作的概念。JS 运行机制简略来说能够按以下几个步骤。 所有同步工作都在主线程上执行,造成一个执行栈(execution context stack)。主线程之外,还存在一个工作队列(task queue)。只有异步工作有了运行后果,会把其回调函数作为一个工作增加到工作队列中。一旦执行栈中的所有同步工作执行结束,就会读取工作队列,看看外面有那些工作,将其增加到执行栈,开始执行。主线程一直反复下面的第三步。也就是常说的事件循环(Event Loop)。2、异步工作的类型nextTick 函数异步执行传入的函数,是一个异步工作。异步工作分为两种类型。 主线程的执行过程就是一个 tick,而所有的异步工作都是通过工作队列来一一执行。工作队列中寄存的是一个个的工作(task)。标准中规定 task 分为两大类,别离是宏工作(macro task)和微工作 (micro task),并且每个 macro task 完结后,都要清空所有的 micro task。 用一段代码形象介绍 task的执行程序。 for (macroTask of macroTaskQueue) { handleMacroTask(); for (microTask of microTaskQueue) { handleMicroTask(microTask); }}在浏览器环境中,常见的创立 macro task 的办法有 ...

October 25, 2022 · 5 min · jiezi

关于vue.js:vue源码中的渲染过程是怎样的

4.1 Virtual DOM4.1.1 浏览器的渲染流程当浏览器接管到一个Html文件时,JS引擎和浏览器的渲染引擎便开始工作了。从渲染引擎的角度,它首先会将html文件解析成一个DOM树,与此同时,浏览器将辨认并加载CSS款式,并和DOM树一起合并为一个渲染树。有了渲染树后,渲染引擎将计算所有元素的地位信息,最初通过绘制,在屏幕上打印最终的内容。JS引擎和渲染引擎尽管是两个独立的线程,然而JS引擎却能够触发渲染引擎工作,当咱们通过脚本去批改元素地位或外观时,JS引擎会利用DOM相干的API办法去操作DOM对象,此时渲染引擎变开始工作,渲染引擎会触发回流或者重绘。上面是回流重绘的两个概念: 回流: 当咱们对DOM的批改引发了元素尺寸的变动时,浏览器须要从新计算元素的大小和地位,最初将从新计算的后果绘制进去,这个过程称为回流。重绘: 当咱们对DOM的批改只单纯扭转元素的色彩时,浏览器此时并不需要从新计算元素的大小和地位,而只有从新绘制新款式。这个过程称为重绘。很显然回流比重绘更加消耗性能。 通过理解浏览器根本的渲染机制,咱们很容易联想到当一直的通过JS批改DOM时,不经意间会触发到渲染引擎的回流或者重绘,这个性能开销是十分微小的。因而为了升高开销,咱们须要做的是尽可能减少DOM操作。有什么办法能够做到呢? 4.1.2 缓冲层-虚构DOM虚构DOM是为了解决频繁操作DOM引发性能问题的产物。虚构DOM(上面称为Virtual DOM)是将页面的状态形象为JS对象的模式,实质上是JS和实在DOM的中间层,当咱们想用JS脚本大批量进行DOM操作时,会优先作用于Virtual DOM这个JS对象,最初通过比照将要改变的局部告诉并更新到实在的DOM。只管最终还是操作实在的DOM,但Virtual DOM能够将多个改变合并成一个批量的操作,从而缩小 DOM 重排的次数,进而缩短了生成渲染树和绘制所花的工夫。 咱们看一个实在的DOM蕴含了什么: 浏览器将一个实在DOM设计得很简单,不仅蕴含了本身的属性形容,大小地位等定义,也囊括了DOM领有的浏览器事件等。正因为如此简单的构造,咱们频繁去操作DOM或多或少会带来浏览器的性能问题。而作为数据和实在DOM之间的一层缓冲,Virtual DOM 只是用来映射到实在DOM的渲染,因而不须要蕴含操作 DOM 的办法,它只有在对象中重点关注几个属性即可。 // 实在DOM<div id="real"><span>dom</span></div>// 实在DOM对应的JS对象{ tag: 'div', data: { id: 'real' }, children: [{ tag: 'span', children: 'dom' }]}4.2 VnodeVue在渲染机制的优化上,同样引进了virtual dom的概念,它是用Vnode这个构造函数去形容一个DOM节点。 4.2.1 Vnode构造函数var VNode = function VNode (tag,data,children,text,elm,context,componentOptions,asyncFactory) { this.tag = tag; // 标签 this.data = data; // 数据 this.children = children; // 子节点 this.text = text; ··· ··· };Vnode定义的属性差不多有20几个,显然用Vnode对象要比实在DOM对象形容的内容要简略得多,它只用来单纯形容节点的要害属性,例如标签名,数据,子节点等。并没有保留跟浏览器相干的DOM办法。除此之外,Vnode也会有其余的属性用来扩大Vue的灵活性。 ...

October 25, 2022 · 4 min · jiezi

关于vue.js:vue面试之CompositionAPI响应式包装对象原理

本文次要分以下两个局部对 Composition API 的原理进行解读: reactive API 原理ref API 原理reactive API 原理关上源码能够找到reactive的入口,在composition-api/src/reactivity/reactive.ts,咱们先从函数入口开始剖析reactive产生了什么事件,通过之前的学习咱们晓得,reactive用于创立响应式对象,须要传递一个一般对象作为参数。 export function reactive<T = any>(obj: T): UnwrapRef<T> { if (process.env.NODE_ENV !== 'production' && !obj) { warn('"reactive()" is called without provide an "object".'); // @ts-ignore return; } if (!isPlainObject(obj) || isReactive(obj) || isNonReactive(obj) || !Object.isExtensible(obj)) { return obj as any; } // 创立一个响应式对象 const observed = observe(obj); // 标记一个对象为响应式对象 def(observed, ReactiveIdentifierKey, ReactiveIdentifier); // 初始化对象的访问控制,便于拜访ref属性时主动解包装 setupAccessControl(observed); return observed as UnwrapRef<T>;}首先,在开发环境下,会进行传参测验,如果没有传递对应的obj参数,开发环境下会给予开发者一个正告,在这种状况,为了不影响生产环境,生产环境下会将正告放过。 ...

October 25, 2022 · 4 min · jiezi

关于vue.js:腾讯前端常考vue面试题整理

什么是 mixin ?Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、 办法等,则能够将其编写为 mixin,并在组件中简略的援用它。而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。Vue中diff算法原理DOM操作是十分低廉的,因而咱们须要尽量地缩小DOM操作。这就须要找出本次DOM必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用diff算法 vue的diff算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归的形式+双指针(头尾都加指针)的形式进行比拟。简略来说,Diff算法有以下过程 同级比拟,再比拟子节点(依据key和tag标签名判断)先判断一方有子节点和一方没有子节点的状况(如果新的children没有子节点,将旧的子节点移除)比拟都有子节点的状况(外围diff)递归比拟子节点失常Diff两个树的工夫复杂度是O(n^3),但理论状况下咱们很少会进行跨层级的挪动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才须要用外围的Diff算法进行同层级比拟。Vue2的外围Diff算法采纳了双端比拟的算法,同时从新旧children的两端开始进行比拟,借助key值找到可复用的节点,再进行相干操作。相比React的Diff算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅在创立VNode时就确定其类型,以及在mount/patch的过程中采纳位运算来判断一个VNode的类型,在这个根底之上再配合外围的Diff算法,使得性能上较Vue2.x有了晋升 vue3中采纳最长递增子序列来实现diff优化答复范例 思路 diff算法是干什么的它的必要性它何时执行具体执行形式拔高:说一下vue3中的优化答复范例 Vue中的diff算法称为patching算法,它由Snabbdom批改而来,虚构DOM要想转化为实在DOM就须要通过patch办法转换最后Vue1.x视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构DOM和patching算法反对,然而这样粒度过细导致Vue1.x无奈承载较大利用;Vue 2.x中为了升高Watcher粒度,每个组件只有一个Watcher与之对应,此时就须要引入patching算法能力准确找到发生变化的中央并高效更新vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数取得最新的虚构DOM,而后执行patch函数,并传入新旧两次虚构DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM操作patch过程是一个递归过程,遵循深度优先、同层比拟的策略;以vue3的patch为例首先判断两个节点是否为雷同同类节点,不同则删除从新创立如果单方都是文本则更新文本内容如果单方都是元素节点则递归更新子元素,同时更新元素属性更新子节点时又分了几种状况 新的子节点是文本,老的子节点是数组则清空,并设置文本;新的子节点是文本,老的子节点是文本则间接更新文本;新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节blablavue3中引入的更新策略:动态节点标记等vdom中diff算法的繁难实现 以下代码只是帮忙大家了解diff算法的原理和流程 将vdom转化为实在dom:const createElement = (vnode) => { let tag = vnode.tag; let attrs = vnode.attrs || {}; let children = vnode.children || []; if(!tag) { return null; } //创立元素 let elem = document.createElement(tag); //属性 let attrName; for (attrName in attrs) { if(attrs.hasOwnProperty(attrName)) { elem.setAttribute(attrName, attrs[attrName]); } } //子元素 children.forEach(childVnode => { //给elem增加子元素 elem.appendChild(createElement(childVnode)); }) //返回实在的dom元素 return elem;}用繁难diff算法做更新操作function updateChildren(vnode, newVnode) { let children = vnode.children || []; let newChildren = newVnode.children || []; children.forEach((childVnode, index) => { let newChildVNode = newChildren[index]; if(childVnode.tag === newChildVNode.tag) { //深层次比照, 递归过程 updateChildren(childVnode, newChildVNode); } else { //替换 replaceNode(childVnode, newChildVNode); } })}</details> ...

October 25, 2022 · 7 min · jiezi

关于vue.js:vue面试考察知识点全梳理

一、简介vue几个核心思想: 数据驱动组件化虚构dom、diff部分最优更新源码目录介绍Vue.js 的源码在 src 目录下,其目录构造如下。 src├── compiler # 编译相干 ├── core # 外围代码 ├── platforms # 不同平台的反对├── server # 服务端渲染├── sfc # .vue 文件解析├── shared # 共享代码compiler:编译相干的代码。它包含把模板解析成 ast 语法树,ast 语法树优化,代码生成等性能。core:外围代码,包含内置组件、全局 API 封装,Vue 实例化、观察者、虚构 DOM、工具函数等等。platform:不同平台的反对,是 Vue.js 的入口,2 个目录代表 2 个次要入口,别离打包成运行在 web 上和 weex 上的 Vue.js。server:服务端渲染,把组件渲染为服务器端的 HTML 字符串,将它们间接发送到浏览器,最初将动态标记"混合"为客户端上齐全交互的应用程序。sfc: .vue 文件内容解析成一个 JavaScript 的对象。shared:浏览器端和服务端所共享的工具办法。源码构建基于 Rollup 构建,相干配置在 scripts 目录下。 构建时通过不同的命令执行不同的脚本,去读取不同用途的配置,而后生成适宜各种场景的Vue源码。 vue2.0有以下几种场景: 浏览器端服务端渲染配合weex平台在客户端应用类型查看在vue2.x版本中应用 Flow 作为js动态类型查看工具,3.x版本应用typescript实现,自带类型查看。 二、数据驱动vue核心思想之一就是数据驱动,指数据驱动生成视图,通过批改数据主动实现对视图的批改。这里次要剖析模板和数据是如何渲染成最终的DOM的。 1. new Vue的过程Vue 初始化次要就干了几件事件, 合并配置初始化生命周期初始化事件核心初始化渲染初始化 data、props、computed、watcher 等等。2. 实例挂载$mount办法 Vue 不能挂载在 body、html 这样的根节点上;如果没有定义 render 办法,则会把 el 或者 template 字符串转换成 render 办法在 Vue 2.0 版本中所有 Vue 的组件的渲染最终都须要 render 办法,是一个“在线编译”的过程;挂载组件: mountComponent外围就是先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 办法,在此办法中调用 vm._render 办法学生成虚构 Node,最终调用 vm._update 更新 DOM。 ...

October 25, 2022 · 5 min · jiezi

关于vue.js:听说你很了解-Vue3-响应式

前言<< 舒适揭示 >> 本文内容偏干,倡议边喝水边食用,如有不适请及时点赞!【A】:能不能说说 Vue3 响应式都解决了哪些数据类型?都怎么解决的呀? 【B】:能,只能说一点点... 【A】:... 只有问到 Vue 相干的内容,仿佛总绕不过 响应式原理 的话题,随之而来的答复必然是围绕着 Object.defineProperty 和 Proxy 来开展(即 Vue2 和 Vue3),但若持续诘问某些具体实现是不是就仓促完结答复了(你跑我追,你不跑我还追)。 本文就不再过多介绍 Vue2 中响应式的解决,感兴趣能够参考 从 vue 源码看问题 —— 如何了解 Vue 响应式?,然而会有简略提及,上面就来看看 Vue3 中是如何解决 原始值、Object、Array、Set、Map 等数据类型的响应式。 从 Object.defineProperty 到 Proxy所有的所有还得从 Object.defineProperty 开始讲起,那是一个不一样的 API ... (bgm 响起,自行领会) Object.definePropertyObject.defineProperty(obj, prop, descriptor) 办法会间接在一个对象上定义一个 新属性,或批改一个 对象 的 现有属性,并返回此对象,其参数具体为: obj:要定义属性的对象prop:要定义或批改的 属性名称 或 Symbol descriptor:要定义或批改的 属性描述符从以上的形容就能够看出一些限度,比方: 指标是 对象属性,不是 整个对象一次只能 定义或批改一个属性 当然有对应的一次解决多个属性的办法 Object.defineProperties(),但在 vue 中并不实用,因为 vue 不能提前晓得用户传入的对象都有什么属性,因而还是得通过相似 Object.keys() + for 循环的形式获取所有的 key -> value,而这其实是没有必要应用 Object.defineProperties()在 Vue2 中的缺点Object.defineProperty() 理论是通过 定义 或 批改 对象属性 的描述符来实现 数据劫持,其对应的毛病也是没法被疏忽的: ...

October 25, 2022 · 11 min · jiezi

关于vue.js:VuenextTick核心原理

置信大家在写vue我的项目的时候,肯定会发现一个神奇的api,Vue.nextTick。为什么说它神奇呢,那是因为在你做某些操作不失效时,将操作写在Vue.nextTick内,就神奇的失效了。那这是什么起因呢? 让咱们一起来钻研一下。 简述vue 实现响应式并不是数据发生变化后 DOM 立刻变动,而是依照肯定策略异步执行 DOM 更新的vue 在批改数据后,视图不会立即进行更新,而是要等同一事件循环机制内所有数据变动实现后,再对立进行DOM更新nextTick 能够让咱们在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM。事件循环机制在探讨Vue.nextTick之前,须要先搞清楚事件循环机制,算是实现的基石了,那咱们来看一下。 在浏览器环境中,咱们能够将咱们的执行工作分为宏工作和微工作, 宏工作: 包含整体代码script,setTimeout,setInterval 、setImmediate、 I/O 操作、UI 渲染微工作: Promise.then、MuationObserver 事件循环的程序,决定js代码的执行程序。事件循环如下: 用代码解释,浏览器中事件循环的程序同如下代码: for (macroTask of macroTaskQueue) { // 1. 执行一个宏工作 handleMacroTask(); // 2. 执行所有的微工作 for (microTask of microTaskQueue) { handleMicroTask(microTask); } }vue数据驱动视图的解决(异步变动DOM)<template> <div> <div>{{count}}</div> <div @click="handleClick">click</div> </div></template>export default { data () { return { number: 0 }; }, methods: { handleClick () { for(let i = 0; i < 10000; i++) { this.count++; } } }}剖析上述代码: ...

October 24, 2022 · 3 min · jiezi

关于vue.js:Vue3-setup语法糖Composition-API全方位解读

起初 Vue3.0 裸露变量必须 return 进去,template 中能力应用;Vue3.2 中 只须要在 script 标签上加上 setup 属性,组件在编译的过程中代码运行的上下文是在 setup() 函数中,无需 return,template 可间接应用。本文章以Vue2的角度学习Vue3的语法,让你疾速了解Vue3的Composition Api本文章第十四节为状态库 Pinia 的装置、应用解说一、文件构造Vue2中,<template> 标签中只能有一个根元素,在Vue3中没有此限度 <template> // ...</template><script setup> // ...</script><style lang="scss" scoped> // 反对CSS变量注入v-bind(color)</style>二、data<script setup> import { reactive, ref, toRefs } from 'vue' // ref申明响应式数据,用于申明根本数据类型 const name = ref('Jerry') // 批改 name.value = 'Tom' // reactive申明响应式数据,用于申明援用数据类型 const state = reactive({ name: 'Jerry', sex: '男' }) // 批改 state.name = 'Tom' // 应用toRefs解构 const {name, sex} = toRefs(state) // template可间接应用{{name}}、{{sex}}</script>三、method<template> // 调用办法 <button @click='changeName'>按钮</button> </template><script setup> import { reactive } from 'vue' const state = reactive({ name: 'Jery' }) // 申明method办法 const changeName = () => { state.name = 'Tom' } </script>四、computed<script setup> import { computed, ref } from 'vue' const count = ref(1) // 通过computed取得doubleCount const doubleCount = computed(() => { return count.value * 2 }) // 获取 console.log(doubleCount.value)</script>五、watch<script setup> import { watch, reactive } from 'vue' const state = reactive({ count: 1 }) // 申明办法 const changeCount = () => { state.count = state.count * 2 } // 监听count watch( () => state.count, (newVal, oldVal) => { console.log(state.count) console.log(`watch监听变动前的数据:${oldVal}`) console.log(`watch监听变动后的数据:${newVal}`) }, { immediate: true, // 立刻执行 deep: true // 深度监听 } )</script>六、props父传子子组件<template> <span>{{props.name}}</span> // 可省略【props.】 <span>{{name}}</span></template><script setup> // import { defineProps } from 'vue' // defineProps在<script setup>中主动可用,无需导入 // 需在.eslintrc.js文件中【globals】下配置【defineProps: true】 // 申明props const props = defineProps({ name: { type: String, default: '' } }) </script>父组件引入子组件,组件会主动注册 ...

October 24, 2022 · 7 min · jiezi

关于vue.js:Vue3源码解读之patch

例子代码本篇将要解说dom diff,那么咱们联合上面的例子来进行解说,这个例子是在上一篇文章的根底上,加了一个数据变更,也就是list的值产生了扭转。html中减少了一个按钮change,通过点击change按钮来调用change函数,来扭转list的值。例子位于源代码/packages/vue/examples/classic/目录下,上面是例子的代码: const app = Vue.createApp({ data() { return { list: ['a', 'b', 'c', 'd'] } }, methods: { change() { this.list = ['a', 'd', 'e', 'b'] } }});app.mount('#demo')<!DOCTYPE html><html><head> <meta name="viewport" content="initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no,target-densitydpi=medium-dpi,viewport-fit=cover" /> <title>Vue3.js hello example</title> <script src="../../dist/vue.global.js"></script></head><body><div id="demo"> <ul> <li v-for="item in list" :key="item"> {{item}} </li> </ul> <button @click="change">change</button></div><script src="./hello.js"></script></body></html>源码解读对于Vue3中数据产生变更,最终影响到页面发生变化的过程,咱们本篇文章只对componentEffect以及当前的代码进行解说,对于数据变更后,是如何执行到componentEffect函数,以及为何会执行componentEffect,前面的文章再进行解说。 componentEffect来看下componentEffect更新局部的代码: // @file packages/runtime-core/src/renderer.ts function componentEffect() { if (!instance.isMounted) { // first render } else { let {next, bu, u, parent, vnode} = instance let originNext = next let vnodeHook: VNodeHook | null | undefined if (next) { updateComponentPreRender(instance, next, optimized) } else { next = vnode } next.el = vnode.el // beforeUpdate hook if (bu) { invokeArrayFns(bu) } // onVnodeBeforeUpdate if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) { invokeVNodeHook(vnodeHook, parent, next, vnode) } const nextTree = renderComponentRoot(instance) const prevTree = instance.subTree instance.subTree = nextTree if (instance.refs !== EMPTY_OBJ) { instance.refs = {} } patch( prevTree, nextTree, hostParentNode(prevTree.el!)!, getNextHostNode(prevTree), instance, parentSuspense, isSVG ) next.el = nextTree.el if (originNext === null) { updateHOCHostEl(instance, nextTree.el) } // updated hook if (u) { queuePostRenderEffect(u, parentSuspense) } // onVnodeUpdated if ((vnodeHook = next.props && next.props.onVnodeUpdated)) { queuePostRenderEffect(() => { invokeVNodeHook(vnodeHook!, parent, next!, vnode) }, parentSuspense) } } }当数据发生变化的时候,最终会走到下面的else的逻辑局部。Vue3源码视频解说:进入学习 ...

October 24, 2022 · 6 min · jiezi

关于vue.js:Vue源码解读之InitState

后面咱们讲到了_init函数的执行流程,简略回顾下: 初始化生命周期-initLifecycle初始化事件-initEvents初始化渲染函数-initRender调用钩子函数-beforeCreate初始化依赖注入-initInjections初始化状态信息-initState初始化依赖提供-initProvide调用钩子函数-created一共通过下面8步,init函数执行实现,开始mount渲染。初始化状态信息本章咱们次要解说initState函数的处理过程,咱们先看下init的主函数 function initState(vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) { initProps(vm, opts.props) } if (opts.methods) { initMethods(vm, opts.methods) } if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) { initComputed(vm, opts.computed) } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }}看下面代码,先申明了一个_watchers的空数组;而后顺次判断传递进来的options是否蕴含系列参数;顺次执行initProps、initMethods、initData、initComputed、initWatch。 initPropsinitProps函数次要是解决传进来的props对象,然而这个props对象是在上一篇文章中讲到的normalizeProps函数解决后的对象,不是传递进来的原对象。来看下initProps的代码: function initProps(vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) defineReactive(props, key, value) if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true)}下面代码解读: ...

October 24, 2022 · 4 min · jiezi

关于vue.js:P4-vue3-计算属性和侦听器

计算属性<script>export default{ data(){ return{ test57: '57-6666', test56: 56 } }, computed:{ //计算属性 ,只有依赖值不变就不会从新计算。 reverseMsg:function (){ console.log("计算属性") return this.test57.split('').reverse().join('') } }, methods:{ reverseMessage:function (){ console.log("methods") return this.test57.split('').reverse().join('') } },};</script><template> <div> <p>{{test57}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>----下方为计算属性---</p><!-- 连()都不须要加--> <p>{{reverseMsg}}</p> <p>{{reverseMsg}}</p> <p>{{reverseMsg}}</p> <p>----下方为methods----</p> <p>{{reverseMessage()}}</p> <p>{{reverseMessage()}}</p> <p>{{reverseMessage()}}</p> </div></template><style> #d1{ color: red; } #d2{ color: blue; } .d1{ font-size: 50px; }</style> computed只执行1次(只有依赖值不变,就不会从新计算而冲缓存中获取)methods执行3次计算属性批改原始值<script>export default{ data(){ return{ test57: '57-6666', test56: 56 } }, computed:{ //计算属性 ,只有依赖值不变就不会从新计算。 reverseMsg:function (){ console.log("计算属性") return this.test57.split('').reverse().join('') } }, methods:{ reverseMessage:function (){ console.log("methods") return this.test57.split('').reverse().join('') } },};</script><template> <div> <p>{{test57}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>{{test57.split('').reverse().join('')}}</p> <p>----下方为计算属性---</p><!-- 连()都不须要加--> <p>{{reverseMsg}}</p> <p>{{reverseMsg}}</p> <p>{{reverseMsg}}</p> <p>----下方为methods----</p> <p>{{reverseMessage()}}</p> <p>{{reverseMessage()}}</p> <p>{{reverseMessage()}}</p> <button @click="test57='hellokugou'">扭转test57</button> </div></template><style> #d1{ color: red; } #d2{ color: blue; } .d1{ font-size: 50px; }</style> ...

October 24, 2022 · 2 min · jiezi

关于vue.js:vue组件通信方式有哪些

vue组件通信形式一、props(父向子传值----自定义属性) / $emit(子向父传值----- 自定义事件)父组件通过props的形式向子组件传递数据,而通过$emit 子组件能够向父组件通信。 1. 父组件向子组件传值(props)上面通过一个例子阐明父组件如何向子组件传递数据:在子组件article.vue中如何获取父组件section.vue中的数据articles:['红楼梦', '西游记','三国演义'] // section父组件<template> <div class="section"> <com-article :articles="articleList"></com-article> </div></template><script>import comArticle from './test/article.vue'export default { name: 'HelloWorld', components: { comArticle }, data() { return { articleList: ['红楼梦', '西游记', '三国演义'] } }}</script>// 子组件 article.vue<template> <div> <span v-for="(item, index) in articles" :key="index">{{item}}</span> </div></template><script>export default { props: ['articles']}</script>留神: prop 能够从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被批改,强行批改能失效,然而控制台会有错误信息。 在子组件批改父组件传入的值的办法:1 .sync 父组件v-on绑定自定义属性时增加修饰符.sync 在子组件中通过调用emit(′update:自定义属性′,要批改的新值)==>emit('update:自定义属性',新值) 固定写法 此时子组件中接管的值就更新成了新值(父组件中的原始值会跟着变动,控制台不会报错) 父组件中: <child :value.sync='xxx'/> 子组件中: this.$emit('update:value',yyy) 2.在子组件data中申明本人的数据,让接管的数据作为这个数据的值 ==> 子组件的数据=this.value (这种办法理论批改的是本人的数据 父组件的数据没变) ...

October 24, 2022 · 4 min · jiezi

关于vue.js:一文梳理vue面试题知识点

Vue3.0 和 2.0 的响应式原理区别Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。 相干代码如下 import { mutableHandlers } from "./baseHandlers"; // 代理相干逻辑import { isObject } from "./util"; // 工具办法export function reactive(target) { // 依据不同参数创立不同响应式对象 return createReactiveObject(target, mutableHandlers);}function createReactiveObject(target, baseHandler) { if (!isObject(target)) { return target; } const observed = new Proxy(target, baseHandler); return observed;}const get = createGetter();const set = createSetter();function createGetter() { return function get(target, key, receiver) { // 对获取的值进行喷射 const res = Reflect.get(target, key, receiver); console.log("属性获取", key); if (isObject(res)) { // 如果获取的值是对象类型,则返回以后对象的代理对象 return reactive(res); } return res; };}function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { console.log("属性新增", key, value); } else if (hasChanged(value, oldValue)) { console.log("属性值被批改", key, value); } return result; };}export const mutableHandlers = { get, // 当获取属性时调用此办法 set, // 当批改属性时调用此办法};mixin 和 mixins 区别mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。 ...

October 24, 2022 · 6 min · jiezi

关于vue.js:vue这些原理你都知道吗面试版

前言在之前面试的时候我本人也常常会遇到一些vue原理的问题, 我也总结了下本人的常常的用到的,不便本人学习,明天也给大家分享进去, 欢送大家一起学习交换, 有更好的办法欢送评论区指出, 后序我也将继续整顿总结~ 形容 Vue 与 React 区别阐明概念:vue:是一套用于构建用户界面的渐进式框架,Vue 的外围库只关注视图层react:用于构建用户界面的 JavaScript 库 申明式, 组件化 定位vue 渐进式 响应式React 单向数据流写法 vue:template,jsx react: jsxHooks:vue3 和 react16 反对 hookUI 更新文化vue 官网提供React 第三方提供,本人抉择整个 new Vue 阶段做了什么?vue.prototype._init(option)initState(vm)Observer(vm.data)new Observer(data)调用 walk 办法,遍历 data 中的每个属性,监听数据的变动执行 defineProperty 监听数据读取和设置数据描述符绑定实现后,咱们就能失去以下的流程图 图中咱们能够看出,vue 初始化的时候,进行了数据的 get\set 绑定,并创立了一个dep 对象就是用来依赖收集, 他实现了一个公布订阅模式,完后了数据 data 的渲染视图 watcher 的订阅class Dep { // 依据 ts 类型提醒,咱们能够得出 Dep.target 是一个 Watcher 类型。 static target: ?Watcher; // subs 寄存收集到的 Watcher 对象汇合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 收集所有应用到这个 data 的 Watcher 对象。 this.subs.push(sub); } depend() { if (Dep.target) { // 收集依赖,最终会调用下面的 addSub 办法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 调用对应的 Watcher,更新视图 subs[i].update(); } }}形容 vue 的响应式原理 ...

October 24, 2022 · 6 min · jiezi

关于vue.js:htmx后端主导的前端框架是啥样的

大家好,我卡颂。 前端畛域这几年涌现了很多新兴的前端框架,比方Qwik、Svelte、Astro等。 这些框架多以前端工程师作为受众。 那么,以后端工程师作为受众的前端框架是啥样的,他与前者有什么区别呢? 欢送退出人类高质量前端框架群,带飞 介绍htmxhtmx是一款在Django技术栈最近比拟热门的前端框架。 他的理念是 —— 让网页回归HTML的实质,不再受JS解放。是不是很有web1.0的格调? 他是怎么做到的呢?答案是:通过大量预制的自定义HTML属性。 当你在页面中引入htmx.org.js后,能够在HTML中书写以hx-结尾的自定义属性。比方上面的代码: <button hx-post="/data" hx-trigger="click"> 点我申请data</button>点击按钮(hx-trigger指定的click事件)后,会向data接口(hx-post指定)发动post申请。 那申请返回的数据如何显示呢?咱们再减少2个自定义属性: <button hx-post="/data" hx-trigger="click" hx-target="#parent-div" hx-swap="outerHTML"> 点我申请data</button>hx-target指代返回的HTML构造会被注入到哪里。这里会被注入到id为parent-div的DOM中。 hx-swap指代返回的HTML构造会以什么模式注入。这里会间接替换id为parent-div的DOM。 如果hx-swap="innerHTML",则代表会以id为parent-div的DOM的innerHTML模式注入。 如果要表白简单的逻辑,须要联合很多自定义属性与属性值,比方上面的代码: <input type="text" hx-get="/trigger_delay" hx-trigger="keyup changed delay:500ms" hx-target="#search-results" placeholder="Search...">当input触发keyup事件且值扭转后,提早500ms,向trigger_delay接口发动申请,返回的HTML构造被注入到id为search-results的DOM中。 与其说htmx是一款前端框架,更贴切的说,他应该是一款HTML自定义属性工具库。 他将很多常见JS交互逻辑收敛到自定义HTML属性中,借此缩小JS代码量。 古代前端框架通常是状态驱动UI,而htmx的理念是过程驱动UI(相似jQuery时代编写页面的形式)。 如果心愿引入状态,须要以插件的模式引入alpine-morph。 相比于: React:基于JSXVue:基于模版语法alpine是一款基于HTML的前端框架。 这意味着应用alpine须要间接在HTML中以自定义属性的模式书写状态(与Vue v1相似)。所以,他能很好的融入htmx的体系中。 比方上面这段代码是段联合htmx与alpine的HTML,其中以hx-结尾的是htmx属性,以x-结尾的是alphine属性: <div hx-target="this" hx-ext="alpine-morph" hx-swap="morph"> <div x-data="{ count: 0, replaced: false, message: 'Change me, then press the button!' }"> <input type="text" x-model="message"> <div x-text="count"></div> <button x-bind:style="replaced && {'backgroundColor': '#fecaca'}" x-on:click="replaced = true; count++" hx-get="/swap"> Morph </button> </div></div>这段代码蕴含了交互逻辑与前端状态,最重要的是:他是非法的HTML(而不是JSX或模版语法这样的DSL),这意味着他能轻松的在前后端之间传递,并在前端展现。 ...

October 24, 2022 · 1 min · jiezi

关于vue.js:Vue中的diff算法深度解析

模板tamplate通过parse,optimize,generate等一些列操作之后,把AST转为render function code进而生成虚构VNode,模板编译阶段根本曾经实现了,那么这一章,咱们来探讨一下Vue中的一个算法策略--dom diff 首先来介绍下什么叫dom diff 什么是虚构dom咱们通过后面的章节学习曾经晓得,要晓得渲染实在DOM的开销是很大的,比方有时候咱们批改了某个数据,如果间接渲染到实在dom上会引起整个dom树的重绘和重排,有没有可能咱们只更新咱们批改的那一小块dom而不要更新整个dom呢? 为了解决这个问题,咱们的解决方案是--依据实在DOM生成一颗virtual DOM,当virtual DOM某个节点的数据扭转后会生成一个新的Vnode,而后Vnode和oldVnode作比照,发现有不一样的中央就间接批改在实在的DOM上,而后使oldVnode的值为Vnode。这也就是咱们所说的一个虚构dom diff的过程 图示 传统的Diff算法所消耗的工夫复杂度为O(n^3),那么这个O(n^3)是怎么算进去的? 传统diff算法工夫复杂度为n(第一次Old与新的所有节点比照)----O(n)传统diff算法工夫复杂度为n(第二次Old树的所有节点与新的所有节点比照)----O(n^2)新树的生成,节点可变编辑,工夫复杂度为n(遍历以后树)----O(n^3)第一次比照 (1:n) 第二次比照 (1:n) 第n次比照 (n:n) 到这里那么n个节点与n个节点暴力比照就比照完了,那么就开启第三轮可编辑树节点遍历,更改之后的树由vdom(old)到vdom(new) 故而传统diff算法O(n^3)是这么算进去的,然而这不是咱们明天钻研的重点。 古代diff算法古代diff算法策略说的是,同层级比拟,广度优先 那么这里的话咱们要深刻源码了,在深刻源码之前咱们在心中应该造成这样一个概念,整个diff的流程是什么?咱们再比照着源码解读 diff算法流程图 深刻源码咱们在Vue初始化的时候调用lifecycleMixin函数的时候,会给Vue的原型上挂载_update办法 _updateVue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { //会调用申明周期中的beforeUpdate回调函数 callHook(vm, 'beforeUpdate') } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. //若组件自身的vnode未生成,间接用传入的vnode生成dom if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ) // no need for the ref nodes after initial patch // this prevents keeping a detached DOM tree in memory (#5851) vm.$options._parentElm = vm.$options._refElm = null } else { //对新旧vnode进行diff // updates vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el }咱们在这里能够看到vm.$el = vm.__patch__办法,追根溯源_patch_的定义: ...

October 21, 2022 · 22 min · jiezi

关于vue.js:Vue虚拟dom是如何被创建的

先来看生成虚构dom的入口文件: ... import { parse } from './parser/index' import { optimize } from './optimizer' import { generate } from './codegen/index' ... const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) ...咱们看到了AST通过三轮加工,最终本性难移成为render function code。那么咱们这一章就来讲AST的第三次试炼。入口文件./codegen/index export function generate (ast: ASTElement | void,options: CompilerOptions): CodegenResult {const state = new CodegenState(options)const code = ast ? genElement(ast, state) : '_c("div")'return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns}}咱们来看generate函数外部结构,返回render是一个with(this){return ${code}}包起来的一串货色,staticRenderFns是在编译过程中会把那些不会变的动态节点打上标记,设置为true,而后在渲染阶段独自渲染。那么genElement函数的作用是什么呢? ...

October 21, 2022 · 6 min · jiezi

关于vue.js:Vue-核心技术

1.1 Vue简介1.1.1 官网英文官网中武官网1.1.2 介绍与形容动静构建用户界面的 渐进式 JavaScript 框架作者:尤雨溪1.1.3 Vue的特点遵循 MVVM 模式编码简洁,体积小,运行效率高,适宜挪动/PC端开发它自身只关注UI,也能够引入其它第三方库开发我的项目1.1.4 与其它JS框架的关联借鉴 Angular 的模板和数据绑定技术借鉴 React 的组件化和虚构 DOM 技术1.1.5 Vue 周边库Vue CLI: 我的项目脚手架Vue ResourceAxiosVue Router: 路由Vuex: 状态治理element-ui:基于 Vue 的 UI 组件库(PC端) ...... 1.2 初识 Vue<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初识Vue</title> <script src="../js/vue.js"></script></head><body> <!-- 初识Vue: 1.想让 Vue工作,就必须创立一个 Vue实例,且要传入一个配置对象; 2.root 容器里的代码仍然合乎 html 标准,只不过混入了一些非凡的 Vue语法; 3.root 容器里的代码被称为【Vue模板】; 4.Vue 实例和容器是一一对应的; 5.实在开发中只有一个 Vue 实例,并且会配合着组件一起应用; 6.{{xxx}} 中的 xxx 要写 js 表达式,且 xxx 能够主动读取到 data 中的所有属性; 7.一旦 data 中的数据产生扭转,那么页面中用到该数据的中央也会自动更新; 留神辨别:js 表达式和 js 代码(语句) 1.表达式:一个表达式会产生一个值,能够放在任何一个须要值的中央: (1). a (2). a+b (3). demo(1) (4). x === y ? 'a' : 'b' 2.js代码(语句) (1). if(){} (2). for(){} --> <div id="root"> <h1>Hello {{name}}</h1> </div> <script> // 阻止 Vue 在启动时生成生产提醒 // You are running Vue in development mode. // Make sure to turn on production mode when deploying for production. // See more tips at https://vuejs.org/guide/deployment.html Vue.config.productionTip = false // 创立 Vue 实例 new Vue({ // el 用于指定以后 Vue 实例为哪个容器服务,值通常为 css 选择器字符串 el: '#root', // data 用于存储数据,数据供 el 所指定的容器去应用 data: { name: "张三" }, }) </script></body></html>1.3 模板语法1.3.1 模板的了解html 中蕴含了一些 js 语法代码,语法分为两种,别离为: ...

October 19, 2022 · 19 min · jiezi

关于vue.js:Vue组件是怎样挂载的

咱们先来关注一下$mount是实现什么性能的吧: 咱们关上源码门路core/instance/init.js: export function initMixin (Vue: Class<Component>) { ...... initLifecycle(vm) // 事件监听初始化 initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props //初始化vm状态 prop/data/computed/watch实现初始化 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ...... // 配置项里有el属性, 则会挂载到实在DOM上, 实现视图的渲染 // 这里的$mount办法,实质上调用了core/instance/liftcycle.js中的mountComponent办法 if (vm.$options.el) { vm.$mount(vm.$options.el) } }}在这里咱们怎么了解这个挂载状态呢?先来看Vue官网给的一段形容 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。能够应用 vm.$mount() 手动地挂载一个未挂载的实例。如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素。并且你必须应用原生DOM API 把它插入文档中。那咱们来看一下$mount外部机制吧: * 缓存之前的$mount的办法以便前面返回实例, */const mount = Vue.prototype.$mount/** * 手动地挂载一个未挂载的根元素,并返回实例本身(Vue实例) */Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { el = el && query(el) /* istanbul ignore if */ /** * 挂载对象不能为body和html标签 */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function /** * 判断$options是否有render办法 * 有:判断是String还是Element,获取他们的innerHTMl * 无:在实例Vue时候在vnode里创立一个创立一个空的正文节点 见办法createEmptyVNode */ if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } /** * 获取的Element的类型 * 具体见 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/outerHTML */ } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ /** * 用于监控compile 的性能 */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // 如果不存在 render 函数,则会将模板转换成render函数 const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ /** * 用于监控compile 的性能 */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating)}而$mount实现的是mountComponent函数性能 ...

October 19, 2022 · 4 min · jiezi

关于vue.js:Vue模板是怎样编译的

这一章咱们开始讲模板解析编译:总结来说就是通过compile函数把tamplate解析成render Function模式的字符串compiler/index.js import { parse } from './parser/index'import { optimize } from './optimizer'import { generate } from './codegen/index'import { createCompilerCreator } from './create-compiler'// `createCompilerCreator` allows creating compilers that use alternative// parser/optimizer/codegen, e.g the SSR optimizing compiler.// Here we just export a default compiler using the default parts.export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns }})咱们能够看出createCompiler函数外部运行的是parse、optimize、generate三个函数,而生成的是ast,render,staticRenderFns三个对象 ...

October 19, 2022 · 13 min · jiezi

关于vue.js:Vue响应式系统原理并实现一个双向绑定

这一章就着重讲两个点: 响应式零碎如何收集依赖响应式零碎如何更新视图 咱们晓得通过Object.defineProperty做了数据劫持,当数据扭转的时候,get办法收集依赖,进而set办法调用dep.notify办法去告诉Watcher调用自身update办法去更新视图。那么咱们抛开其余问题,就探讨get,notify,update等办法,间接上代码:get( ) get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }咱们晓得Dep.target在创立Watcher的时候是null,并且它只是起到一个标记的作用,当咱们创立Watcher实例的时候,咱们的Dep.target就会被赋值到Watcher实例,进而放入target栈中,咱们这里调用的是pushTarget函数: // 将watcher实例赋值给Dep.target,用于依赖收集。同时将该实例存入target栈中export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target}那咱们继续执行到if (Dep.target)语句的时候就会调用Dep.depend函数: // 将本身退出到全局的watcher中 depend () { if (Dep.target) { Dep.target.addDep(this) } }那上面的childOb是啥货色呢? let childOb = !shallow && observe(val)咱们通过这个变量判断以后属性上面是否还有ob属性,如果有的话持续调用Dep.depend函数,没有的话则不解决。咱们还须要解决以后传入的value类型,是数组属性的话则会调用dependArray收集数组依赖 ...

October 19, 2022 · 4 min · jiezi

关于vue.js:vue源码分析响应式系统工作原理

上一章,咱们讲到了Vue初始化做的一些操作,那么咱们这一章来讲一个Vue外围概念响应式零碎。咱们先来看一下官网对深刻响应式零碎的解释: 当你把一个一般的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性。并应用 Object.defineProperty 把这些属性全副转为 getter/setter。Object.defineProperty 是 ES5 中一个无奈 shim 的个性。这也就是为什么 Vue 不反对 IE8 以及更低版本浏览器的起因。 上图是Vue官网放出的一张图,而且提到外围概念Object.defineProperty,那么咱们间接看源码,咱们看到的Object.defineProperty在defineReactive函数的外部,而defineReactive函数在walk函数外部,顺次找到源头是Observer类 ./core/observer/indexexport class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data /** * 生成的Observer实例上挂载三个属性 * 1. value, 即观测数据对象自身 * 2. dep, 用于依赖收集的容器 * 3. vmCount, 间接写死为0 */ constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 在观测数据对象上增加__ob__属性, 是Observer实例的援用 // def相当于Object.defineProperty, 区别是dep里会把__ob__属性设置为不可枚举 // 须要留神的是, value.__ob__.value 显然就是 value 自身, 这里有一个循环援用 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } // 用于解决对象类型的观测值, 循环所有的key都调用一次defineReactive walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } // 对数组的每一项进行监听 observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}value是须要被察看的数据对象,在构造函数中,会给value减少ob属性,作为数据曾经被Observer察看的标记。如果value是数组,就应用observeArray遍历value,对value中每一个元素调用observe别离进行察看。如果value是对象,则应用walk遍历value上每个key,对每个key调用defineReactive来取得该key的set/get控制权。 ...

October 19, 2022 · 6 min · jiezi

关于vue.js:vue源码分析从new-Vue开始

初学vue,你得晓得咱们是从new Vue开始的: new Vue({ el: '#app', data: obj, ...})那你感觉是不是很有意思,咱们new Vue之后,就能够应用他那么多的性能,可见Vue是暴进去的一个一个性能类函数,咱们进入源码一探到底: import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'//判断是不是服务端渲染import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'/** * 增加全局的API */initGlobalAPI(Vue)/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})/** * 服务端渲染须要 */Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})/** * 服务端渲染须要 */Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})/** * vue版本号 这里的'__VERSION__'为占位符,公布版本时将被主动替换 */Vue.version = '__VERSION__'export default Vue那么咱们看到咱们的外围Vue来自'./instance/index'那咱们再去一探到底,可想而知外面必然有一个Vue函数类 ...

October 19, 2022 · 11 min · jiezi

关于vue.js:vue源码分析keepalive原理上

上一节最初略微提到了Vue内置组件的相干内容,从这一节开始,将会对某个具体的内置组件进行剖析。首先是keep-alive,它是咱们日常开发中常常应用的组件,咱们在不同组件间切换时,常常要求放弃组件的状态,以防止反复渲染组件造成的性能损耗,而keep-alive常常和上一节介绍的动静组件联合起来应用。因为内容过多,keep-alive的源码剖析将分为高低两局部,这一节次要围绕keep-alive的首次渲染开展。13.1 根本用法keep-alive的应用只须要在动静组件的最外层增加标签即可。 <div id="app"> <button @click="changeTabs('child1')">child1</button> <button @click="changeTabs('child2')">child2</button> <keep-alive> <component :is="chooseTabs"> </component> </keep-alive></div>var child1 = { template: '<div><button @click="add">add</button><p>{{num}}</p></div>', data() { return { num: 1 } }, methods: { add() { this.num++ } },}var child2 = { template: '<div>child2</div>'}var vm = new Vue({ el: '#app', components: { child1, child2, }, data() { return { chooseTabs: 'child1', } }, methods: { changeTabs(tab) { this.chooseTabs = tab; } }})简略的后果如下,动静组件在child1,child2之间来回切换,当第二次切到child1时,child1保留着原来的数据状态,num = 5。 13.2 从模板编译到生成vnode依照以往剖析的教训,咱们会从模板的解析开始说起,第一个疑难便是:内置组件和一般组件在编译过程有区别吗?答案是没有的,不论是内置的还是用户定义组件,实质上组件在模板编译成render函数的解决形式是统一的,这里的细节不开展剖析,有纳闷的能够参考前几节的原理剖析。最终针对keep-alive的render函数的后果如下: ...

October 19, 2022 · 4 min · jiezi

关于vue.js:vue源码分析动态组件

后面花了两节的内容介绍了组件,从组件的原理讲到组件的利用,包含异步组件和函数式组件的实现和应用场景。家喻户晓,组件是贯通整个Vue设计理念的货色,并且也是领导咱们开发的核心思想,所以接下来的几篇文章,将从新回到组件的内容去做源码剖析,首先会从罕用的动静组件开始,包含内联模板的原理,最初会简略的提到内置组件的概念,为之后的文章埋下伏笔。12.1 动静组件动静组件我置信大部分在开发的过程中都会用到,当咱们须要在不同的组件之间进行状态切换时,动静组件能够很好的满足咱们的需要,其中的外围是component标签和is属性的应用。 12.1.1 根本用法例子是一个动静组件的根本应用场景,当点击按钮时,视图依据this.chooseTabs值在组件child1,child2,child3间切换。 // vue<div id="app"> <button @click="changeTabs('child1')">child1</button> <button @click="changeTabs('child2')">child2</button> <button @click="changeTabs('child3')">child3</button> <component :is="chooseTabs"> </component></div>// jsvar child1 = { template: '<div>content1</div>',}var child2 = { template: '<div>content2</div>'}var child3 = { template: '<div>content3</div>'}var vm = new Vue({ el: '#app', components: { child1, child2, child3 }, methods: { changeTabs(tab) { this.chooseTabs = tab; } }})12.1.2 AST解析<component>的解读和后面几篇内容统一,会从AST解析阶段说起,过程也不会专一每一个细节,而是把和以往解决形式不同的中央特地阐明。针对动静组件解析的差别,集中在processComponent上,因为标签上is属性的存在,它会在最终的ast树上打上component属性的标记。 // 针对动静组件的解析function processComponent (el) { var binding; // 拿到is属性所对应的值 if ((binding = getBindingAttr(el, 'is'))) { // ast树上多了component的属性 el.component = binding; } if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true; }}最终的ast树如下: ...

October 19, 2022 · 3 min · jiezi

关于vue.js:vue为什么vfor的优先级比vif的高

前言有时候有些面试中常常会问到v-for与v-if谁的优先级高,这里就通过剖析源码去解答一下这个问题。 上面的内容是在 当咱们谈及v-model,咱们在探讨什么?的根底上剖析的,所以浏览上面内容之前可先看这篇文章。 持续从编译登程以上面的例子登程剖析: new Vue({ el:'#app', template:` <ul> <li v-for="(item,index) in items" v-if="index!==0"> {{item}} </li> </ul> `})从上篇文章能够晓得,编译有三个步骤 parse : 解析模板字符串生成 AST语法树optimize : 优化语法树,次要时标记动态节点,进步更新页面的性能codegen : 生成js代码,次要是render函数和staticRenderFns函数咱们再次顺着这三个步骤对上述例子进行剖析。 parseparse过程中,会对模板应用大量的正则表达式去进行解析。结尾的例子会被解析成以下AST节点: // 其实ast有很多属性,我这里只展现波及到剖析的属性ast = { 'type': 1, 'tag': 'ul', 'attrsList': [], attrsMap: {}, 'children': [{ 'type': 1, 'tag': 'li', 'attrsList': [], 'attrsMap': { 'v-for': '(item,index) in data', 'v-if': 'index!==0' }, // v-if解析进去的属性 'if': 'index!==0', 'ifConditions': [{ 'exp': 'index!==0', 'block': // 指向el本身 }], // v-for解析进去的属性 'for': 'items', 'alias': 'item', 'iterator1': 'index', 'parent': // 指向其父节点 'children': [ 'type': 2, 'expression': '_s(item)' 'text': '{{item}}', 'tokens': [ {'@binding':'item'}, ] ] }]}对于v-for指令,除了记录在attrsMap和attrsList,还会新增for(对应要遍历的对象或数组),alias,iterator1,iterator2对应v-for指令绑定内容中的第一,第二,第三个参数,结尾的例子没有第三个参数,因而没有iterator2属性。 ...

October 19, 2022 · 3 min · jiezi

关于vue.js:进阶vue面试题总结

过滤器的作用,如何实现一个过滤器依据过滤器的名称,过滤器是用来过滤数据的,在Vue中应用filters来过滤数据,filters不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed ,办法 methods 都是通过批改数据来解决数据格式的输入显示)。 应用场景: 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。比方后端返回一个 年月日的日期字符串,前端须要展现为 多少天前 的数据格式,此时就能够用fliters过滤器来解决数据。过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{ }} 和 v-bind 表达式 中,而后放在操作符“ | ”前面进行批示。 例如,在显示金额,给商品价格增加单位: <li>商品价格:{{item.price | filterPrice}}</li> filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }路由的hash和history模式的区别Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。 1. hash模式简介: hash模式是开发中默认的模式,它的URL带着一个# 特点:hash值会呈现在URL外面,然而不会呈现在HTTP申请中,对后端齐全没有影响。所以扭转hash值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的IE浏览器也反对这种模式。hash路由被称为是前端路由,曾经成为SPA(单页面利用)的标配。 原理: hash模式的次要原理就是onhashchange()事件: window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1);}应用onhashchange()事件的益处就是,在页面的hash值发生变化时,无需向后端发动申请,window就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash值变动对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的hash值和对应的URL关联起来了。 2. history模式简介: history模式的URL中没有#,它应用的是传统的路由散发模式,即用户在输出一个URL时,服务器会接管这个申请,并解析这个URL,而后做出相应的逻辑解决。 特点: 相比hash模式更加难看。然而,history模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回404。 API: history api能够分为两大部分,切换历史状态和批改历史状态: 批改历史状态:包含了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了url,但浏览器不会立刻向后端发送申请。如果要做到扭转url但又不刷新页面的成果,就须要前端用上这两个API。切换历史状态: 包含forward()、back()、go()三个办法,对应浏览器的后退,后退,跳转操作。尽管history模式抛弃了俊俏的#。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。 ...

October 19, 2022 · 9 min · jiezi

关于vue.js:vue组件通信6种方式总结常问知识点

前言在Vue组件库开发过程中,Vue组件之间的通信始终是一个重要的话题,尽管官网推出的 Vuex 状态治理计划能够很好的解决组件之间的通信问题,然而在组件库外部应用 Vuex 往往会比拟重,本文将零碎的列举出几种不应用 Vuex,比拟实用的组件间的通信形式,供大家参考。 组件之间通信的场景在进入咱们明天的主题之前,咱们先来总结下Vue组件之间通信的几种场景,个别能够分为如下几种场景: 父子组件之间的通信兄弟组件之间的通信隔代组件之间的通信父子组件之间的通信父子组件之间的通信应该是 Vue 组件通信中最简略也最常见的一种了,概括为两个局部:父组件通过prop向子组件传递数据,子组件通过自定义事件向父组件传递数据。 父组件通过 prop 向子组件传递数据Vue组件的数据流向都遵循单向数据流的准则,所有的 prop 都使得其父子 prop 之间造成了一个单向上行绑定:父级 prop 的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外变更父级组件的状态,从而导致你的利用的数据流向难以了解。 额定的,每次父级组件产生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转 prop。如果你这样做了,Vue 会在浏览器的控制台中收回正告。 父组件 ComponentA: <template> <div> <component-b title="welcome"></component-b> </div></template><script>import ComponentB from './ComponentB'export default { name: 'ComponentA', components: { ComponentB }}</script>子组件 ComponentB: <template> <div> <div>{{title}}</div> </div></template><script>export default { name: 'ComponentB', props: { title: { type: String, } }} </script>子组件通过自定义事件向父组件传递数据在子组件中能够通过 $emit 向父组件产生一个事件,在父组件中通过 v-on/@ 进行监听。 子组件 ComponentA: <template> <div> <component-b :title="title" @title-change="titleChange"></component-b> </div></template><script>import ComponentB from './ComponentB'export default { name: 'ComponentA', components: { ComponentB }, data: { title: 'Click me' }, methods: { titleChange(newTitle) { this.title = newTitle } }}</script>子组件 ComponentB: ...

October 19, 2022 · 5 min · jiezi

关于vue.js:VuenextTick核心原理

置信大家在写vue我的项目的时候,肯定会发现一个神奇的api,Vue.nextTick。为什么说它神奇呢,那是因为在你做某些操作不失效时,将操作写在Vue.nextTick内,就神奇的失效了。那这是什么起因呢? 让咱们一起来钻研一下。 简述vue 实现响应式并不是数据发生变化后 DOM 立刻变动,而是依照肯定策略异步执行 DOM 更新的vue 在批改数据后,视图不会立即进行更新,而是要等同一事件循环机制内所有数据变动实现后,再对立进行DOM更新nextTick 能够让咱们在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM。事件循环机制在探讨Vue.nextTick之前,须要先搞清楚事件循环机制,算是实现的基石了,那咱们来看一下。 在浏览器环境中,咱们能够将咱们的执行工作分为宏工作和微工作, 宏工作: 包含整体代码script,setTimeout,setInterval 、setImmediate、 I/O 操作、UI 渲染微工作: Promise.then、MuationObserver 事件循环的程序,决定js代码的执行程序。事件循环如下: 用代码解释,浏览器中事件循环的程序同如下代码: for (macroTask of macroTaskQueue) { // 1. 执行一个宏工作 handleMacroTask(); // 2. 执行所有的微工作 for (microTask of microTaskQueue) { handleMicroTask(microTask); } }vue数据驱动视图的解决(异步变动DOM)<template> <div> <div>{{count}}</div> <div @click="handleClick">click</div> </div></template>export default { data () { return { number: 0 }; }, methods: { handleClick () { for(let i = 0; i < 10000; i++) { this.count++; } } }}剖析上述代码: ...

October 18, 2022 · 3 min · jiezi

关于vue.js:vue中的几个高级概念

混入mixins官网解释混入 (mixin) 提供了一种非常灵活的形式,来散发 Vue 组件中的可复用性能。一个混入对象能够蕴含任意组件选项。当组件应用混入对象时,所有混入对象的选项将被“混合”进入该组件自身的选项。简略的来说就是 Mixins 是咱们能够重用的代码块,在理论开发中,如果有些代码重复性比拟高,这时候能够思考 Mixins 这个个性。 简略的mixin示例 export default { data() { return { name: '来自mixin的name', arr: [ 1, { arrName: '来自mixin', arrMixin: '来自mixin' }, 1233 ], obj: { name: '来自mixin', value: '来自mixin', mixin: '只有mixin才有的字段' } } }, created() { console.log('我是mixin的created---') }, mounted() { console.log('我是mixin的mounted---') this.getInfo() }, methods: { getInfo() { console.log('我是mixin, getInfo:', this.name) console.log('我是mixin的obj:', this.obj) console.log('我是mixin的arr:', this.arr) } }}组件中应用 import mixinDemo from './mixin.js'export default { name: '', mixins: [mixinDemo], components: {}, data() { return { name: '组件中的name', arr: [ 2, { arrName: '来自组件的arrName', title: '来自组件的独有字段title' } ], obj: { name: '来自组件name', value: '来自组件value', title: '只有组件才有的title字段' } } }, computed: {}, watch: {}, created() { console.log('---我是组件的created---') }, mounted() { console.log('---我是组件的mounted---') this.getInfo() }, methods: { getInfo() { console.log('我是组件的, getInfo:', this.name) console.log('我是组件的obj:', this.obj) console.log('我是组件的arr:', this.arr) } }}控制台后果 ...

October 18, 2022 · 3 min · jiezi

关于vue.js:vue实战深入响应式数据原理

本文将带大家疾速过一遍Vue数据响应式原理,解析源码,学习设计思路,循序渐进。 数据初始化_init在咱们执行new Vue创立实例时,会调用如下构造函数,在该函数外部调用this._init(options)。 import { initMixin } from "./init.js";// 先创立一个Vue类,Vue就是一个构造函数(类) 通过new关键字进行实例化function Vue(options) { // 这里开始进行Vue初始化工作 this._init(options);}// _init办法是挂载在Vue原型的办法,每一个new 实例能够调用, 由initMixin办法挂载// 将不同的操作拆分成不同的模块,导入后对Vue类做一些解决,此做法更利于保护initMixin(Vue); // 定义原型办法_initstateMixin(Vue) //定义 $set $get $delete $watch 等eventsMixin(Vue) // 定义事件 $on $once $off $emitlifecycleMixin(Vue) // 定义 _update $forceUpdate $destroyrenderMixin(Vue) // 定义 _render 返回虚构dom export default Vue;initMixin函数外面定义了原型办法_init,_init调用了initState(vm)等办法,_init里做了很多初始化工作,咱们重点关注initState import { initState } from "./state";export function initMixin(Vue) { Vue.prototype._init = function (options) { const vm = this; // 这里的this指向调用_init办法的对象(即 new的实例) // this.$options就是用户new Vue的时候传入的属性 vm.$options = options; ... initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props // 初始化状态,在beforeCreate之前,created之后 initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); ... };}initStateinitState函数按程序初始化$options的数据,程序为 prop>methods>data>computed>watch ...

October 18, 2022 · 5 min · jiezi

关于vue.js:vue3实战完全掌握refreactive

晓得大家应用 Vue3 的时候有没有这样的纳闷,“ref、rective 都能创立一个响应式对象,我该如何抉择?”,“为什么响应式对象解构之后就失去了响应式?应该如何解决?” 明天咱们就来全面盘点一下 ref、reactive,置信看完你肯定会有不一样的播种,一起学起来吧! reactive()根本用法在 Vue3 中咱们能够应用 reactive() 创立一个响应式对象或数组: import { reactive } from 'vue'const state = reactive({ count: 0 })这个响应式对象其实就是一个 Proxy, Vue 会在这个 Proxy 的属性被拜访时收集副作用,属性被批改时触发副作用。 要在组件模板中应用响应式状态,须要在 setup() 函数中定义并返回。 <script>import { reactive } from 'vue'export default { setup() { const state = reactive({ count: 0 }) return { state } }}</script><template> <div>{{ state.count }}</div></template>当然,也能够应用 <script setup> ,<script setup> 中顶层的导入和变量申明能够在模板中间接应用。 <script setup>import { reactive } from 'vue'const state = reactive({ count: 0 })</script><template> <div>{{ state.count }}</div></template>响应式代理 vs 原始对象reactive() 返回的是一个原始对象的 Proxy,他们是不相等的: ...

October 18, 2022 · 5 min · jiezi

关于vue.js:vue源码分析diff算法核心原理

这一节,仍然是深刻分析Vue源码系列,上几节内容介绍了Virtual DOM是Vue在渲染机制上做的优化,而渲染的外围在于数据变动时,如何高效的更新节点,这就是diff算法。因为源码中对于diff算法局部流程简单,间接分析每个流程不易于了解,所以这一节咱们换一个思路,参考源码来手动实现一个简易版的diff算法。之前讲到Vue在渲染机制的优化上,引入了Virtual DOM的概念,利用Virtual DOM形容一个实在的DOM,实质上是在JS和实在DOM之间架起了一层缓冲层。当咱们通过大量的JS运算,并将最终后果反馈到浏览器进行渲染时,Virtual DOM能够将多个改变合并成一个批量的操作,从而缩小 dom 重排的次数,进而缩短了生成渲染树和绘制节点所花的工夫,达到渲染优化的目标。之前的章节,咱们简略的介绍了Vue中Vnode的概念,以及创立Vnode到渲染Vnode再到实在DOM的过程。如果有遗记流程的,能够参考后面的章节剖析。 从render函数到创立虚构DOM,再到渲染实在节点,这一过程是残缺的,也是容易了解的。然而引入虚构DOM的外围不在这里,而在于当数据发生变化时,如何最优化数据变动到视图更新的过程。这一个过程才是Vnode更新视图的外围,也就是常说的diff算法。上面跟着我来实现一个简易版的diff算法 8.1 创立根底类代码编写过程会遇到很多根本类型的判断,第一步须要先将这些办法封装。 class Util { constructor() {} // 检测根底类型 _isPrimitive(value) { return (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean') } // 判断值不为空 _isDef(v) { return v !== undefined && v !== null }}// 工具类的应用const util = new Util()8.2 创立VnodeVnode这个类在之前章节曾经剖析过源码,实质上是用一个对象去形容一个实在的DOM元素,简易版关注点在于元素的tag标签,元素的属性汇合data,元素的子节点children,text为元素的文本节点,简略的形容类如下: class VNode { constructor(tag, data, children) { this.tag = tag; this.data = data; this.children = children; this.elm = '' // text属性用于标记Vnode节点没有其余子节点,只有纯文本 this.text = util._isPrimitive(this.children) ? this.children : '' }}8.3 模仿渲染过程接下来须要创立另一个类模仿将render函数转换为Vnode,并将Vnode渲染为实在DOM的过程,咱们将这个类定义为Vn,Vn具备两个根本的办法createVnode, createElement, 别离实现创立虚构Vnode,和创立实在DOM的过程。 ...

October 18, 2022 · 4 min · jiezi

关于vue.js:vue源码分析事件机制

这个系列讲到这里,Vue根本外围的货色曾经剖析完,然而Vue之所以弱小,离不开它提供给用户的一些实用功能,开发者能够更偏差于业务逻辑而非基本功能的实现。例如,在日常开发中,咱们将@click=***用得飞起,然而咱们是否思考,Vue如何在前面为咱们的模板做事件相干的解决,并且咱们常常利用组件的自定义事件去实现父子间的通信,那这个事件和和原生dom事件又有不同的中央吗,可能实现通信的原理又是什么,带着纳闷,咱们深刻源码开展剖析。9.1. 模板编译Vue在挂载实例前,有相当多的工作是进行模板的编译,将template模板进行编译,解析成AST树,再转换成render函数,而有了render函数后才会进入实例挂载过程。对于事件而言,咱们常常应用v-on或者@在模板上绑定事件。因而对事件的第一步解决,就是在编译阶段对事件指令做收集解决。 从一个简略的用法剖析编译阶段收集的信息: <div id="app"> <div v-on:click.stop="doThis">点击</div> <span>{{count}}</span></div><script>var vm = new Vue({ el: '#app', data() { return { count: 1 } }, methods: { doThis() { ++this.count } }})</script>咱们之前在将模板编译的时候大抵说过编译的流程,模板编译的入口是在var ast = parse(template.trim(), options);中,parse通过拆分模板字符串,将其解析为一个AST树,其中对于属性的解决,在processAttr中,因为分支较多,咱们只剖析例子中的流程。 var dirRE = /^v-|^@|^:/;function processAttrs (el) { var list = el.attrsList; var i, l, name, rawName, value, modifiers, syncGen, isDynamic; for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name; // v-on:click value = list[i].value; // doThis if (dirRE.test(name)) { // 匹配v-或者@结尾的指令 el.hasBindings = true; modifiers = parseModifiers(name.replace(dirRE, ''));// parseModifiers('on:click') if (modifiers) { name = name.replace(modifierRE, ''); } if (bindRE.test(name)) { // v-bind分支 // ...留到v-bind指令时剖析 } else if (onRE.test(name)) { // v-on分支 name = name.replace(onRE, ''); // 拿到真正的事件click isDynamic = dynamicArgRE.test(name);// 动静事件绑定 if (isDynamic) { name = name.slice(1, -1); } addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic); } else { // normal directives // 其余指令相干逻辑 } else {} } }processAttrs的逻辑尽管较多,然而了解起来较为简单,var dirRE = /^v-|^@|^:/;是匹配事件相干的正则,命中匹配的记功会失去事件指令相干内容,包含事件自身,事件回调以及事件修饰符。最终通过addHandler办法,为AST树增加事件相干的属性。而addHandler还有一个重要性能是对事件修饰符进行非凡解决。 ...

October 18, 2022 · 10 min · jiezi

关于vue.js:vue源码分析插槽原理

Vue组件的另一个重要概念是插槽,它容许你以一种不同于严格的父子关系的形式组合组件。插槽为你提供了一个将内容搁置到新地位或使组件更通用的进口。这一节将围绕官网对插槽内容的介绍思路,依照一般插槽,具名插槽,再到作用域插槽的思路,逐渐深刻外在的实现原理,有对插槽应用不相熟的,能够先参考官网对插槽的介绍。10.1 一般插槽插槽将<slot></slot>作为子组件承载散发的载体,简略的用法如下 10.1.1 根底用法var child = { template: `<div class="child"><slot></slot></div>`}var vm = new Vue({ el: '#app', components: { child }, template: `<div id="app"><child>test</child></div>`})// 最终渲染后果<div class="child">test</div>10.1.2 组件挂载原理插槽的原理,贯通了整个组件零碎编译到渲染的过程,所以首先须要回顾一下对组件相干编译渲染流程,简略总结一下几点: 从根实例动手进行实例的挂载,如果有手写的render函数,则间接进入$mount挂载流程。只有template模板则须要对模板进行解析,这里分为两个阶段,一个是将模板解析为AST树,另一个是依据不同平台生成执行代码,例如render函数。$mount流程也分为两步,第一步是将render函数生成Vnode树,子组件会以vue-componet-为tag标记,另一步是把Vnode渲染成真正的DOM节点。创立实在节点过程中,如果遇到子的占位符组件会进行子组件的实例化过程,这个过程又将回到流程的第一步。接下来咱们对slot的剖析将围绕这四个具体的流程开展,对组件流程的详细分析,能够参考深刻分析Vue源码 - 组件根底大节。 10.1.3 父组件解决回到组件实例流程中,父组件会优先于子组件进行实例的挂载,模板的解析和render函数的生成阶段在解决上没有非凡的差别,这里就不开展剖析。接下来是render函数生成Vnode的过程,在这个阶段会遇到子的占位符节点(即:child),因而会为子组件创立子的Vnode。createComponent执行了创立子占位节点Vnode的过程。咱们把重点放在最终Vnode代码的生成。 // 创立子Vnode过程 function createComponent ( Ctor, // 子类结构器 data, context, // vm实例 children, // 父组件须要散发的内容 tag // 子组件占位符 ){ ··· // 创立子vnode,其中父保留的children属性会以选项的模式传递给Vnode var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory ); }// Vnode结构器var VNode = function VNode (tag,data,children,text,elm,context,componentOptions,asyncFactory) { ··· this.componentOptions = componentOptions; // 子组件的选项相干}createComponent函数接管的第四个参数children就是父组件须要散发的内容。在创立子Vnode过程中,会以会componentOptions配置传入Vnode结构器中。最终Vnode中父组件须要散发的内容以componentOptions属性的模式存在,这是插槽剖析的第一步。 ...

October 18, 2022 · 5 min · jiezi

关于vue.js:vue源码分析vmodel的本质

双向数据绑定这个概念或者大家并不生疏,视图影响数据,数据同样影响视图,两者间有双向依赖的关系。在响应式零碎构建的上,中,下篇我曾经对数据影响视图的原理具体论述分明了。而如何实现视图影响数据这一关联?这就是本节探讨的重点:指令v-model。因为v-model和后面介绍的插槽,事件统一,都属于vue提供的指令,所以咱们对v-model的剖析形式和以往大同小异。剖析会围绕模板的编译,render函数的生成,到最初实在节点的挂载程序执行。最终咱们仍然会失去一个论断,v-model无论什么应用场景,实质上都是一个语法糖。 11.1 表单绑定11.1.1 根底应用v-model和表单脱离不了关系,之所以视图能影响数据,实质上这个视图须要可交互的,因而表单是实现这一交互的前提。表单的应用以<input > <textarea> <select>为外围,更细的划分联合v-model的应用如下: // 一般输入框<input type="text" v-model="value1">// 多行文本框<textarea v-model="value2" cols="30" rows="10"></textarea>// 单选框<div class="group"> <input type="radio" value="one" v-model="value3"> one <input type="radio" value="two" v-model="value3"> two</div> // 原生单选框的写法 注:原生单选框的写法须要通过name绑定一组单选,两个radio的name属性雷同,能力体现为互斥<div class="group"> <input type="radio" name="number" value="one">one <input type="radio" name="number" value="two">two</div>// 多选框 (原始值: value4: [])<div class="group"> <input type="checkbox" value="jack" v-model="value4">jack <input type="checkbox" value="lili" v-model="value4">lili</div>// 下拉选项<select name="" id="" v-model="value5"> <option value="apple">apple</option> <option value="banana">banana</option> <option value="bear">bear</option></select>接下来的剖析,咱们以一般输入框为例 <div id="app"> <input type="text" v-model="value1"></div>new Vue({ el: '#app', data() { return { value1: '' } }})进入注释前先回顾一下模板到实在节点的过程。 模板解析成AST树;AST树生成可执行的render函数;render函数转换为Vnode对象;依据Vnode对象生成实在的Dom节点。接下来,咱们先看看模板解析为AST树的过程。 11.1.2 AST树的解析模板的编译阶段,会调用var ast = parse(template.trim(), options)生成AST树,parse函数的其余细节这里不开展剖析,后面的文章或多或少都波及过,咱们还是把关注点放在模板属性上的解析,也就是processAttrs函数上。 应用过vue写模板的都晓得,vue模板属性由两局部组成,一部分是指令,另一部分是一般html标签属性。z这也是属性解决的两大分支。而在指令的细分畛域,又将v-on,v-bind做非凡的解决,其余的一般分支会执行addDirective过程。 // 解决模板属性function processAttrs(el) { var list = el.attrsList; var i, l, name, rawName, value, modifiers, syncGen, isDynamic; for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name; // v-on:click value = list[i].value; // doThis if (dirRE.test(name)) { // 1.针对指令的属性解决 ··· if (bindRE.test(name)) { // v-bind分支 ··· } else if(onRE.test(name)) { // v-on分支 ··· } else { // 除了v-bind,v-on之外的一般指令 ··· // 一般指令会在AST树上增加directives属性 addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]); if (name === 'model') { checkForAliasModel(el, value); } } } else { // 2. 一般html标签属性 } }}在深刻分析Vue源码 - 揭秘Vue的事件机制这一节,咱们介绍了AST产生阶段对事件指令v-on的解决是为AST树增加events属性。相似的,一般指令会在AST树上增加directives属性,具体看addDirective函数。 ...

October 18, 2022 · 13 min · jiezi

关于vue.js:Vue的computed和watch的区别是什么

一、computed介绍computed 用来监控本人定义的变量,该变量在 data 内没有申明,间接在 computed 外面定义,页面上可间接应用。 //根底应用{{msg}}<input v-model="name" /> //计算属性 computed:{ msg:function(){ return this.name }}在输入框中,扭转 name 值得时候,msg 也会跟着扭转。这是因为 computed 监听本人的属性 msg,发现 name 一旦变动,msg 立马会更新。 留神:msg 不可在 data 中定义,否则会报错。 1.1、get 和 set 用法 <input v-model="full" ><br><input v-model="first" > <br><input v-model="second" > data(){ return{ first:'美女', second:'姐姐' }},computed:{ full:{ get(){ //回调函数 当须要读取以后属性值是执行,依据相干数据计算并返回以后属性的值 return this.first + ' ' + this.second }, set(val){ //监督以后属性值的变动,当属性值发生变化时执行,更新相干的属性数据 let names = val.split(' ') this.first = names[0] this.second = names[1] } }}get 办法:first 和 second 扭转时,会调用 get 办法,更新 full 的值。 ...

October 18, 2022 · 2 min · jiezi

关于vue.js:滴滴前端二面vue相关面试题

computed 的实现原理computed 实质是一个惰性求值的观察者。 computed 外部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立即求值,同时持有一个 dep 实例。 其外部通过 this.dirty 属性标记计算属性是否须要从新求值。 当 computed 的依赖状态产生扭转时,就会告诉这个惰性的 watcher, computed watcher 通过 this.dep.subs.length 判断有没有订阅者, 有的话,会从新计算,而后比照新旧值,如果变动了,会从新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。) 没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。) Vue-router跳转和location.href有什么区别应用 location.href= /url 来跳转,简略不便,然而刷新了页面;应用 history.pushState( /url ) ,无刷新页面,动态跳转;引进 router ,而后应用 router.push( /url ) 来跳转,应用了 diff 算法,实现了按需加载,缩小了 dom 的耗费。其实应用 router 跳转和应用 history.pushState() 没什么差异的,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。$nextTick 原理及作用Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种利用。 nextTick 的外围是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 办法来模仿对应的微/宏工作的实现,实质是为了利用 JavaScript 的这些异步回调工作队列来实现 Vue 框架中本人的异步回调队列。 ...

October 18, 2022 · 42 min · jiezi

关于vue.js:new-Vue的时候到底做了什么

Vue加载流程1.初始化的第一阶段是Vue实例也就是vm对象创立前后:首先Vue进行生命周期,事件初始化产生在beforeCreate生命周期函数前,而后进行数据监测和数据代理的初始化,也就是创立vm对象的过程,当vm对象创立实现就能够通过vm对象拜访到劫持的数据,比方data中的数据,methods中的办法等。而后Vue调用外部的render函数开始解析模板将其解析为一个JS对象也即在内存中生成虚构DOM也就是Vnode对象。第二阶段是vm对象挂载前后:挂载实现前页面出现的是未通过Vue编译的DOM构造,所有对DOM的操作最终都不会失效。挂载前首先将内存中的Vnode转换为实在DOM插入页面,此时实现挂载。页面中出现的就是通过Vue编译的DOM构造,至此初始化过程完结。 2.开启订阅音讯也就是数据劫持代理监听,其实就是写了一个watcher函数去监听数据的扭转,发送网络申请,绑定自定义事件等初始化操作。当数据发生变化当前即状态变更的时候,会从新结构新的Vnode对象。而后用新的Vnode对象和旧的Vnode对象进行差别比拟也就是DIFF算法,而后把差别利用到旧的Vnode对象所构建的真正的DOM树上这个过程就是patch,视图就更新了 每一个组件在加载时都会调用Vue外部的render函数把该组件的tamplate选项的模板解析为一个JS对象,这个对象和DOM节点对象构造一样,而后是数据劫持代理监听,当数据发生变化当前,将旧Vnode对象和生成的新Vnode对象比拟差别而后更新DOM Vnode: {tag:"", id:, name:"Box", $el:实在页面上的DOM的援用, //等等属性 chiren:[ { tag:"", id:, name:"Box2",$el:实在页面上的DOM的援用, //等等属性 }, { tag:"", id:, name:"Box3",$el:实在页面上的DOM的援用,//等等属性 }] } 什么是DIFFdiff算法是一种比照算法。比照两者是旧虚构DOM和新虚构DOM,比照出是哪个虚构节点更改了,找出这个虚构节点,并只更新这个虚构节点所对应的实在节点,而不必更新其余数据没产生扭转的节点,实现精准地更新实在DOM,进而提高效率 其有两个特点: 比拟只会在同层级进行, 不会跨层级比拟在diff比拟的过程中,循环从两边向两头比拟参考:前端vue面试题具体解答 DIFF算法的过程: 当数据产生扭转时,订阅者watcher就会调用patch给实在的DOM打补丁通过isSameVnode进行判断,雷同则调用patchVnode办法patchVnode做了以下操作: 找到对应的实在dom,称为el如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点如果oldVnode有子节点而VNode没有,则删除el子节点如果oldVnode没有子节点而VNode有,则将VNode的子节点实在化后增加到el如果两者都有子节点,则执行updateChildren函数比拟子节点updateChildren次要做了以下操作: 设置新旧VNode的头尾指针新旧头尾指针进行比拟,循环向两头聚拢,依据状况调用patchVnode进行patch反复流程、调用createElem创立一个新节点,从哈希表寻找 key统一的VNode 节点再分状况操作 对于Vue中el,template,render,$mount的渲染渲染根节点: 先判断有无el属性,有的话间接获取el根节点,没有的话调用$mount去获取根节点。渲染模板: 有render:这时候优先执行render函数,render优先级 > template。无render:有template时拿template去解析成render函数的所需的格局,并应用调用render函数渲染。无template时拿el根节点的outerHTML去解析成render函数的所需的格局,并应用调用render函数渲染渲染的形式:无论什么状况,最初都对立是要应用render函数渲染

October 18, 2022 · 1 min · jiezi

关于vue.js:vue实战完全掌握Vue自定义指令

筹备:自定义指令介绍除了外围性能默认内置的指令 (v-model 和 v-show等),Vue 也容许注册自定义指令。留神,在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。 ———Vue官网 作为应用Vue的开发者,咱们对Vue指令肯定不生疏,诸如v-model、v-on、v-for、v-if等,同时Vue也为开发者提供了自定义指令的api,纯熟的应用自定义指令能够极大的进步了咱们编写代码的效率,让咱们能够节省时间开心的摸鱼~ 对于Vue的自定义指令置信很多同学曾经有所理解,自定义指令的具体写法这里就不细讲了,官网文档很具体。 然而不晓得各位同学有没有这种感觉,就是这个技术感觉很不便,也不难,我也感觉学会了,就是不晓得如何去利用。这篇文档就是为了解决一些同学的这些问题才写进去的。 PS:这次要讲的自定义指令咱们次要应用的是vue2.x的写法,不过vue3.x不过是几个钩子函数有所扭转,只有了解每个钩子函数的含意,两者的用法差异并不大。 试炼:实现v-mymodel我的上篇文章说到要本人实现一个v-model指令,这里应用v-myodel模仿一个简易版的,顺便再领不相熟的同学相熟一下自定义指令的步骤和注意事项。 定义指令首先梳理思路:原生input控件与组件的实现形式须要辨别,input的实现较为简单,咱们先实现一下input的解决。首先咱们先定义一个不做任何操作的指令 Vue.directive('mymodel', { //只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。 bind(el, binding, vnode, oldVnode) { }, //被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中),须要父节点dom时应用这个钩子 inserted(el, binding, vnode, oldVnode) { }, //所在组件的 VNode 更新时调用,**然而可能产生在其子 VNode 更新之前**。指令的值可能产生了扭转,也可能没有。然而你能够通过比拟更新前后的值来疏忽不必要的模板更新 (具体的钩子函数参数见下)。 update(el, binding, vnode, oldVnode) { }, //指令所在组件的 VNode **及其子 VNode** 全副更新后调用。 componentUpdated(el, binding, vnode, oldVnode) { }, 只调用一次,指令与元素解绑时调用。 unbind(el, binding, vnode, oldVnode) { },})下面的正文中具体的阐明了各个钩子函数的调用机会,因为咱们是给组件上增加input事件和value绑定,因而咱们在bind这个钩子函数中定义即可。所以咱们把其余的先去掉,代码变成这样。 Vue.directive('mymodel', { //只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。 bind(el, binding, vnode, oldVnode) { }})简略说一下bind函数的几个回调参数,el是指令绑定组件对应的dom,binding是咱们的指令自身,蕴含name、value、expression、arg等,vnode就是以后绑定组件对应的vnode结点,oldVnode就是vnode更新前的状态。 ...

October 17, 2022 · 4 min · jiezi

关于vue.js:vue实战中的一些小技巧

能让你首次加载更快的路由懒加载,怎么能忘?路由懒加载能够让咱们的包不须要一次把所有的页面的加载进来,只加载以后页面的路由组件就行。 举个,如果这样写,加载的时候会全副都加载进来。 const router = new VueRouter({ routes:[ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ]})所以,应该防止下面的写法,尽量应用懒加载 懒加载写法,联合webpack的import食用 const router = new VueRouter({ routes:[ { path: '/', name: 'Home', component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue') }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ]})你是否还记得有一个叫Object.freeze的办法?应该所有同学都晓得,vue初始化的时候会将data外面的数据都搞成响应式数据吧。然而,咱们在写业务逻辑的时候会有些数据一初始化就永远不会扭转,它基本就不须要被vue做成响应式数据,因而咱们应该将这些不必扭转的数据通过Object.freeze办法解冻它,防止vue初始化的时候,做一些无用的操作。 export default { data:()=>({ list:Object.freeze([{title:'我永远不须要扭转,我不须要响应式'}]) })}异步组件那么强,你是不是没用过?异步组件能够让咱们在须要一些组件时才将它加载进来,而不是一初始化就加载进来,这跟路由懒加载时一个概念。 ...

October 17, 2022 · 2 min · jiezi

关于vue.js:彻底搞懂Vue虚拟Dom和diff算法

前言应用过Vue和React的小伙伴必定对虚构Dom和diff算法很相熟,它扮演着很重要的角色。因为小编接触Vue比拟多,React只是浅学,所以本篇次要针对Vue来开展介绍,带你一步一步搞懂它。 虚构DOM什么是虚构DOM?虚构DOM(Virtual Dom),也就是咱们常说的虚构节点,是用JS对象来模仿实在DOM中的节点,该对象蕴含了实在DOM的构造及其属性,用于比照虚构DOM和实在DOM的差别,从而进行部分渲染来达到优化性能的目标。 实在的元素节点: <div id="wrap"> <p class="title">Hello world!</p></div>VNode: { tag:'div', attrs:{ id:'wrap' }, children:[ { tag:'p', text:'Hello world!', attrs:{ class:'title', } } ]}为什么应用虚构DOM?简略理解虚构DOM后,是不是有小伙伴会问:Vue和React框架中为什么会用到它呢?好问题!那来解决下小伙伴的疑难。 起初咱们在应用JS/JQuery时,不可避免的会大量操作DOM,而DOM的变动又会引发回流或重绘,从而升高页面渲染性能。那么怎么来缩小对DOM的操作呢?此时虚构DOM利用而生,所以虚构DOM呈现的次要目标就是为了缩小频繁操作DOM而引起回流重绘所引发的性能问题的! 虚构DOM的作用是什么?兼容性好。因为Vnode实质是JS对象,所以不论Node还是浏览器环境,都能够操作;缩小了对Dom的操作。页面中的数据和状态变动,都通过Vnode比照,只须要在比对完之后更新DOM,不须要频繁操作,进步了页面性能;虚构DOM和实在DOM的区别?说到这里,那么虚构DOM和实在DOM的区别是什么呢?总结大略如下: 虚构DOM不会进行回流和重绘;实在DOM在频繁操作时引发的回流重绘导致性能很低;虚构DOM频繁批改,而后一次性比照差别并批改实在DOM,最初进行顺次回流重绘,缩小了实在DOM中屡次回流重绘引起的性能损耗;虚构DOM无效升高大面积的重绘与排版,因为是和实在DOM比照,更新差别局部,所以只渲染部分;总损耗 = 实在DOM增删改 + (多节点)回流/重绘; //计算应用实在DOM的损耗总损耗 = 虚构DOM增删改 + (diff比照)实在DOM差异化增删改 + (较少节点)回流/重绘; //计算应用虚构DOM的损耗能够发现,都是围绕频繁操作实在DOM引起回流重绘,导致页面性能损耗来说的。不过框架也不肯定非要应用虚构DOM,关键在于看是否频繁操作会引起大面积的DOM操作。 那么虚构DOM到底通过什么形式来缩小了页面中频繁操作DOM呢?这就不得不去理解DOM Diff算法了。 DIFF算法当数据变动时,vue如何来更新视图的?其实很简略,一开始会依据实在DOM生成虚构DOM,当虚构DOM某个节点的数据扭转后会生成一个新的Vnode,而后VNode和oldVnode比照,把不同的中央批改在实在DOM上,最初再使得oldVnode的值为Vnode。 diff过程就是调用patch函数,比拟新老节点,一边比拟一边给实在DOM打补丁(patch);对照vue源码来解析一下,贴出外围代码,旨在简单明了讲述分明,不然小编本人看着都头大了O(∩_∩)O patch那么patch是怎么打补丁的? //patch函数 oldVnode:老节点 vnode:新节点function patch (oldVnode, vnode) { ... if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode) //如果新老节点是同一节点,那么进一步通过patchVnode来比拟子节点 } else { /* -----否则新节点间接替换老节点----- */ const oEl = oldVnode.el // 以后oldVnode对应的实在元素节点 let parentEle = api.parentNode(oEl) // 父元素 createEle(vnode) // 依据Vnode生成新元素 if (parentEle !== null) { api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素增加进父元素 api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点 oldVnode = null } } ... return vnode}//判断两节点是否为同一节点function sameVnode (a, b) { return ( a.key === b.key && // key值 a.tag === b.tag && // 标签名 a.isComment === b.isComment && // 是否为正文节点 // 是否都定义了data,data蕴含一些具体信息,例如onclick , style isDef(a.data) === isDef(b.data) && sameInputType(a, b) // 当标签是<input>的时候,type必须雷同 )}从下面能够看出,patch函数是通过判断新老节点是否为同一节点: ...

October 17, 2022 · 2 min · jiezi

关于vue.js:vue源码分析响应式系统三

上一节,咱们深入分析了以data,computed为数据创立响应式零碎的过程,并对其中依赖收集和派发更新的过程进行了具体的剖析。然而在应用和剖析过程中仍然存在或多或少的问题,这一节咱们将针对这些问题开展剖析,最初咱们也会剖析一下watch的响应式过程。这篇文章将作为响应式系统分析的完结篇。7.12 数组检测在之前介绍数据代理章节,咱们曾经具体介绍过Vue数据代理的技术是利用了Object.defineProperty,Object.defineProperty让咱们能够不便的利用存取描述符中的getter/setter来进行数据的监听,在get,set钩子中别离做不同的操作,达到数据拦挡的目标。然而Object.defineProperty的get,set办法只能检测到对象属性的变动,对于数组的变动(例如插入删除数组元素等操作),Object.defineProperty却无奈达到目标,这也是利用Object.defineProperty进行数据监控的缺点,尽管es6中的proxy能够完满解决这一问题,但毕竟有兼容性问题,所以咱们还须要钻研Vue在Object.defineProperty的根底上如何对数组进行监听检测。 7.12.1 数组办法的重写既然数组曾经不能再通过数据的getter,setter办法去监听变动了,Vue的做法是对数组办法进行重写,在保留原数组性能的前提下,对数组进行额定的操作解决。也就是从新定义了数组办法。 var arrayProto = Array.prototype;// 新建一个继承于Array的对象var arrayMethods = Object.create(arrayProto);// 数组领有的办法var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];arrayMethods是基于原始Array类为原型继承的一个对象类,因为原型链的继承,arrayMethod领有数组的所有办法,接下来对这个新的数组类的办法进行改写。 methodsToPatch.forEach(function (method) { // 缓冲原始数组的办法 var original = arrayProto[method]; // 利用Object.defineProperty对办法的执行进行改写 def(arrayMethods, method, function mutator () {});});function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }这里对数组办法设置了代理,当执行arrayMethods的数组办法时,会代理执行mutator函数,这个函数的具体实现,咱们放到数组的派发更新中介绍。 仅仅创立一个新的数组办法合集是不够的,咱们在拜访数组时,如何不调用原生的数组办法,而是将过程指向这个新的类,这是下一步的重点。 回到数据初始化过程,也就是执行initData阶段,上一篇内容花了大篇幅介绍过数据初始化会为data数据创立一个Observer类,过后咱们只讲述了Observer类会为每个非数组的属性进行数据拦挡,从新定义getter,setter办法,除此之外对于数组类型的数据,咱们无意跳过剖析了。这里,咱们重点看看对于数组拦挡的解决。 var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 将__ob__属性设置成不可枚举属性。内部无奈通过遍历获取。 def(value, '__ob__', this); // 数组解决 if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { // 对象解决 this.walk(value); }}数组解决的分支分为两个,hasProto的判断条件,hasProto用来判断以后环境下是否反对__proto__属性。而数组的解决会依据是否反对这一属性来决定执行protoAugment, copyAugment过程, ...

October 17, 2022 · 5 min · jiezi

关于vue.js:vue源码分析响应式系统二

为了深刻介绍响应式零碎的外部实现原理,咱们花了一整节的篇幅介绍了数据(包含data, computed,props)如何初始化成为响应式对象的过程。有了响应式数据对象的常识,上一节的后半局部咱们还在保留源码构造的根底上构建了一个以data为数据的响应式零碎,而这一节,咱们持续深刻响应式零碎外部构建的细节,详细分析Vue在响应式零碎中对data,computed的解决。7.8 相干概念在构建繁难式响应式零碎的时候,咱们引出了几个重要的概念,他们都是响应式原理设计的外围,咱们先简略回顾一下: Observer类,实例化一个Observer类会通过Object.defineProperty对数据的getter,setter办法进行改写,在getter阶段进行依赖的收集,在数据产生更新阶段,触发setter办法进行依赖的更新watcher类,实例化watcher类相当于创立一个依赖,简略的了解是数据在哪里被应用就须要产生了一个依赖。当数据产生扭转时,会告诉到每个依赖进行更新,后面提到的渲染wathcer便是渲染dom时应用数据产生的依赖。Dep类,既然watcher了解为每个数据须要监听的依赖,那么对这些依赖的收集和告诉则须要另一个类来治理,这个类便是Dep,Dep须要做的只有两件事,收集依赖和派发更新依赖。这是响应式零碎构建的三个根本外围概念,也是这一节的根底,如果还没有印象,请先回顾上一节对极简风响应式零碎的构建。 7.9 data7.9.1 问题思考在开始剖析data之前,咱们先抛出几个问题让读者思考,而答案都蕴含在接下来内容分析中。 后面曾经晓得,Dep是作为治理依赖的容器,那么这个容器在什么时候产生?也就是实例化Dep产生在什么时候?Dep收集了什么类型的依赖?即watcher作为依赖的分类有哪些,别离是什么场景,以及区别在哪里?Observer这个类具体对getter,setter办法做了哪些事件?手写的watcher和页面数据渲染监听的watch如果同时监听到数据的变动,优先级怎么排?有了依赖的收集是不是还有依赖的解除,依赖解除的意义在哪里?带着这几个问题,咱们开始对data的响应式细节开展剖析。 7.9.2 依赖收集data在初始化阶段会实例化一个Observer类,这个类的定义如下(疏忽数组类型的data): // initData function initData(data) { ··· observe(data, true)}// observefunction observe(value, asRootData) { ··· ob = new Observer(value); return ob}// 观察者类,对象只有设置成领有察看属性,则对象下的所有属性都会重写getter和setter办法,而getter,setting办法会进行依赖的收集和派发更新var Observer = function Observer (value) { ··· // 将__ob__属性设置成不可枚举属性。内部无奈通过遍历获取。 def(value, '__ob__', this); // 数组解决 if (Array.isArray(value)) { ··· } else { // 对象解决 this.walk(value); } };function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, // 是否可枚举 writable: true, configurable: true });}Observer会为data增加一个__ob__属性, __ob__属性是作为响应式对象的标记,同时def办法确保了该属性是不可枚举属性,即外界无奈通过遍历获取该属性值。除了标记响应式对象外,Observer类还调用了原型上的walk办法,遍历对象上每个属性进行getter,setter的改写。 ...

October 17, 2022 · 6 min · jiezi

关于vue.js:vue源码分析响应式系统一

从这一大节开始,正式进入Vue源码的外围,也是难点之一,响应式零碎的构建。这一节将作为剖析响应式构建过程源码的入门,次要分为两大块,第一块是针对响应式数据props,methods,data,computed,wather初始化过程的剖析,另一块则是在保留源码设计理念的前提下,尝试手动构建一个根底的响应式零碎。有了这两个根底内容的铺垫,下一篇进行源码具体细节的剖析会更加得心应手。7.1 数据初始化回顾一下之前的内容,咱们对Vue源码的剖析是从初始化开始,初始化_init会执行一系列的过程,这个过程包含了配置选项的合并,数据的监测代理,最初才是实例的挂载。而在实例挂载前还有意疏忽了一个重要的过程,数据的初始化(即initState(vm))。initState的过程,是对数据进行响应式设计的过程,过程会针对props,methods,data,computed和watch做数据的初始化解决,并将他们转换为响应式对象,接下来咱们会逐渐剖析每一个过程。 function initState (vm) { vm._watchers = []; var opts = vm.$options; // 初始化props if (opts.props) { initProps(vm, opts.props); } // 初始化methods if (opts.methods) { initMethods(vm, opts.methods); } // 初始化data if (opts.data) { initData(vm); } else { // 如果没有定义data,则创立一个空对象,并设置为响应式 observe(vm._data = {}, true /* asRootData */); } // 初始化computed if (opts.computed) { initComputed(vm, opts.computed); } // 初始化watch if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); }}7.2 initProps简略回顾一下props的用法,父组件通过属性的模式将数据传递给子组件,子组件通过props属性接管父组件传递的值。 ...

October 17, 2022 · 5 min · jiezi

关于vue.js:vue源码分析组件

咱们晓得,组件是Vue体系的外围,纯熟应用组件是把握Vue进行开发的根底。上一节中,咱们深刻理解了Vue组件注册到应用渲染的残缺流程。这一节咱们会在上一节的根底上介绍组件的两个高级用法:异步组件和函数式组件。6.1 异步组件6.1.1 应用场景Vue作为单页面利用遇到最辣手的问题是首屏加载工夫的问题,单页面利用会把页面脚本打包成一个文件,这个文件蕴含着所有业务和非业务的代码,而脚本文件过大也是造成首页渲染速度迟缓的起因。因而作为首屏性能优化的课题,最罕用的解决办法是对文件的拆分和代码的拆散。按需加载的概念也是在这个前提下引入的。咱们往往会把一些非首屏的组件设计成异步组件,局部不影响首次视觉体验的组件也能够设计为异步组件。这个思维就是按需加载。艰深点了解,按需加载的思维让利用在须要应用某个组件时才去申请加载组件代码。咱们借助webpack打包后的后果会更加直观。 webpack遇到异步组件,会将其从主脚本中拆散,缩小脚本体积,放慢首屏加载工夫。当遇到场景须要应用该组件时,才会去加载组件脚本。 6.1.2 工厂函数Vue中容许用户通过工厂函数的模式定义组件,这个工厂函数会异步解析组件定义,组件须要渲染的时候才会触发该工厂函数,加载后果会进行缓存,以供下一次调用组件时应用。具体应用: // 全局注册:Vue.component('asyncComponent', function(resolve, reject) { require(['./test.vue'], resolve)})// 部分注册:var vm = new Vue({ el: '#app', template: '<div id="app"><asyncComponent></asyncComponent></div>', components: { asyncComponent: (resolve, reject) => require(['./test.vue'], resolve), // 另外写法 asyncComponent: () => import('./test.vue'), }})6.1.3 流程剖析有了上一节组件注册的根底,咱们来剖析异步组件的实现逻辑。简略回顾一下上一节的流程,实例的挂载流程分为依据渲染函数创立Vnode和依据Vnode产生实在节点的过程。期间创立Vnode过程,如果遇到子的占位符节点会调用creatComponent,这里会为子组件做选项合并和钩子挂载的操作,并创立一个以vue-component-为标记的子Vnode,而异步组件的解决逻辑也是在这个阶段解决。 // 创立子组件过程 function createComponent ( Ctor, // 子类结构器 data, context, // vm实例 children, // 子节点 tag // 子组件占位符 ) { ··· // 针对部分注册组件创立子类结构器 if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } // 异步组件分支 var asyncFactory; if (isUndef(Ctor.cid)) { // 异步工厂函数 asyncFactory = Ctor; // 创立异步组件函数 Ctor = resolveAsyncComponent(asyncFactory, baseCtor); if (Ctor === undefined) { return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } ··· // 创立子组件vnode var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory ); return vnode }工厂函数的用法使得Vue.component(name, options)的第二个参数不是一个对象,因而不论是全局注册还是部分注册,都不会执行Vue.extend生成一个子组件的结构器,所以Ctor.cid不会存在,代码会进入异步组件的分支。 ...

October 17, 2022 · 4 min · jiezi

关于vue.js:VuenextTick的原理是什么vue面试进阶

原理性的货色就会文字较多,请耐下心来,细细品味 Vue中DOM更新机制当你威风凛凛地应用Vue大展宏图的时候,忽然发现,咦,我明明对这个数据进行更改了,然而当我获取它的时候怎么是上一次的值(自己比拟懒,就不具体举例了) 此时,Vue就会说:“小样,这你就不懂了吧,我的DOM是异步更新的呀!!!” 简略的说,Vue的响应式并不是只数据发生变化之后,DOM就立即发生变化,而是依照肯定的策略进行DOM的更新。这样的益处是能够防止一些对DOM不必要的操作,进步渲染性能。 在Vue官网文档中是这样阐明的: 可能你还没有留神到,Vue异步执行DOM更新。只有察看到数据变动,Vue将开启一个队列,并缓冲在同一事件循环中产生的所有数据扭转。如果同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和DOM操作上十分重要。而后,在下一个的事件循环“tick”中,Vue刷新队列并执行理论 (已去重的) 工作。文言一点就是说,其实这是和JS当中的事件循环是非亲非故的,就是Vue不可能对每一个数据变动都做一次渲染,它会把这些变动先放在一个异步的队列当中,同时它还会对这个队列外面的操作进行去重,比方你批改了这个数据三次,它只会保留最初一次。这些变动是都能够通过队列的模式保存起来,那当初的问题就来到了,那vue是在事件循环的哪个机会来对DOM进行批改呢? Vue有两种抉择,一个是在本次事件循环的最初进行一次DOM更新,另一种是把DOM更新放在下一轮的事件循环当中。z这时,尤雨溪拍了拍胸脯说:“这两种办法,我都有!” 然而因为本轮事件循环最初执行会比放在下一轮事件循环要快很多,所以Vue优先选择第一种,只有当环境不反对的时候才触发第二种机制。(结尾的链接让你懂事件循环) 尽管性能上进步了很多,但这个时候问题就呈现了,咱们都晓得在一轮事件循环中,同步执行栈中代码执行实现之后,才会执行异步队列当中的内容,那咱们获取DOM的操作是一个同步的呀!!那岂不是尽管我曾经把数据改掉了,然而它的更新异步的,而我在获取的时候,它还没有来得及改,所以会呈现文章结尾的那个问题。 这。。。我的确须要进行这样操作,那这么办呢?? 没关系啦,尤大很贴心的为咱们提供了Vue.$nextTick() Vue.$nextTick()其实一句话就能够把$nextTick这个货色讲明确:就是你放在$nextTick 当中的操作不会立刻执行,而是等数据更新、DOM更新实现之后再执行,这样咱们拿到的必定就是最新的了。 再精确一点来讲就是$nextTick办法将回调提早到下次DOM更新循环之后执行。(看不懂这句人话的,能够看下面[狗头]) 意思咱们都懂了,那$nextTick是怎么实现这个神奇的性能的呢? 外围如下: Vue在外部对异步队列尝试应用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不反对,则会采纳 setTimeout(fn, 0)代替。认真地看这句话,你就能够发现这不就是利用 JavaScript 的这些异步回调工作队列,来实现 Vue 框架中本人的异步回调队列。这其实就是一个典型的将底层 JavaScript 执行原理利用到具体案例中的示例。 我在这里略微总结一下:就是$nextTick将回调函数放到微工作或者宏工作当中以提早它地执行程序;(总结的也比拟懒) 重要的是了解源码中它的三个参数的意思: callback:咱们要执行的操作,能够放在这个函数当中,咱们没执行一次$nextTick就会把回调函数放到一个异步队列当中;pending:标识,用以判断在某个事件循环中是否为第一次退出,第一次退出的时候才触发异步执行的队列挂载timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程了解之后,在看整个$nextTick外面的执行过程,其实就是把一个个$nextTick中的回调函数压入到callback队列当中,而后依据事件的性质期待执行,轮到它执行的时候,就执行一下,而后去掉callback队列中相应的事件。 参考:前端vue面试题具体解答 应用说了这么多,怎么用它呢? 很简略很简略 mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered })}应用场景created中获取DOM的操作须要应用它就是咱们下面的例子,你如果想要获取最新值,就用它还有一些第三方插件应用过程中,应用到的状况,具体问题具体分析补充之前我始终搞不懂一个的问题,$nextTick既然把它传入的办法变成微工作了,那它和其它微工作的执行程序是怎么的呢? 这简略来说就是谁先挂载Promise对象的问题,在调用$nextTick办法时就会将其闭包外部保护的执行队列挂载到Promise对象,在数据更新时Vue外部首先就会执行$nextTick办法,之后便将执行队列挂载到了Promise对象上,其实在明确Js的Event Loop模型后,将数据更新也看做一个$nextTick办法的调用,并且明确$nextTick办法会一次性执行所有推入的回调,就能够明确执行程序的问题了 还有$nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的实质是一样的,$nextTick是实例办法,nextTick是类的静态方法而已;实例办法的一个益处就是,主动给你绑定为调用实例的this罢了。

October 17, 2022 · 1 min · jiezi

关于vue.js:美团前端vue面试题边面边更

Vue 修饰符有哪些vue中修饰符分为以下五种表单修饰符事件修饰符鼠标按键修饰符键值修饰符v-bind修饰符1. 表单修饰符 在咱们填写表单的时候用得最多的是input标签,指令用得最多的是v-model 对于表单的修饰符有如下: lazy在咱们填完信息,光标来到标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步 <input type="text" v-model.lazy="value"><p>{{value}}</p>trim主动过滤用户输出的首空格字符,而两头的空格不会过滤 <input type="text" v-model.trim="value">number主动将用户的输出值转为数值类型,但如果这个值无奈被parseFloat解析,则会返回原来的值 <input v-model.number="age" type="number">2. 事件修饰符 事件修饰符是对事件捕捉以及指标进行了解决,有如下修饰符 .stop 阻止了事件冒泡,相当于调用了event.stopPropagation办法<div @click="shout(2)"> <button @click.stop="shout(1)">ok</button></div>//只输入1.prevent 阻止了事件的默认行为,相当于调用了event.preventDefault办法<form v-on:submit.prevent="onSubmit"></form>.capture 应用事件捕捉模式,使事件触发从蕴含这个元素的顶层开始往下触发<div @click.capture="shout(1)"> obj1<div @click.capture="shout(2)"> obj2<div @click="shout(3)"> obj3<div @click="shout(4)"> obj4</div></div></div></div>// 输入构造: 1 2 4 3 .self 只当在 event.target 是以后元素本身时触发处理函数<div v-on:click.self="doThat">...</div>应用修饰符时,程序很重要;相应的代码会以同样的程序产生。因而,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素本身的点击.once 绑定了事件当前只能触发一次,第二次就不会触发<button @click.once="shout(1)">ok</button>.passive 通知浏览器你不想阻止事件的默认行为在挪动端,当咱们在监听元素滚动事件的时候,会始终触发onscroll事件会让咱们的网页变卡,因而咱们应用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符 <!-- 滚动事件的默认行为 (即滚动行为) 将会立刻触发 --><!-- 而不会期待 `onScroll` 实现 --><!-- 这其中蕴含 `event.preventDefault()` 的状况 --><div v-on:scroll.passive="onScroll">...</div>不要把 .passive 和 .prevent 一起应用,因为 .prevent 将会被疏忽,同时浏览器可能会向你展现一个正告。passive 会通知浏览器你不想阻止事件的默认行为native 让组件变成像html内置标签那样监听根元素的原生事件,否则组件上应用 v-on 只会监听自定义事件<my-component v-on:click.native="doSomething"></my-component><!-- 应用.native修饰符来操作一般HTML标签是会令事件生效的 -->3. 鼠标按钮修饰符 ...

October 17, 2022 · 8 min · jiezi

关于vue.js:Vue是怎样监听数组的变化的

上周五跟着一个师姐面试一个三年工作教训的前端开发,我在一边审慎的观摩。想着已经我也被他人面试过,现在面试他人,感觉其实情绪是一样的。前言 工作三年的Vue使用者应该懂什么?为何工作几年的根底越来越弱?工作如何挤出工夫学习?一道面试题其实咱们并不是要你把答案都记下来,而是把其中的思维学习到。就像你接触一个新的畛域react,你也一样能够把根本思维提炼进去。 面试题: Vue是如何对数据进行监听的? 这其实是陈词滥调的问题,凡是你有一点基础知识,你也能答出一二。师姐跟我说,其实问题不只是问题自身,而是跟这个常识顺带进去的体系。 01 对象数据是怎么被监听的 在vue2.x版本中,数据监听是用过Object.defineProperty这个API来实现的,咱们能够来看一个例子 var text = 'vue';const data = {};Object.defineProperty(data, 'text', { get() { return text; }, set(newVal) { text = newVal; }});data.text // 'vue'data.text = 'react' // 'react'当咱们拜访或设置对象的属性的时候,都会触发绝对应的函数,而后在这个函数里返回或设置属性的值。咱们当然能够在触发函数的时候做咱们本人想做的事件,这也就是“劫持”操作。 在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并创立一个监听器,当数据发生变化的时候发出通知。 var data = { name:'hello', age:18}Object.keys(data).forEach(function(key){ Object.defineProperty(data,key,{ enumerable:true, // 是否能在for...in循环中遍历进去或在Object.keys中列举进去。 configurable:true, // false,不可批改、删除指标属性或批改属性性以下个性 get:function(){ console.log('获取数据'); }, set:function(){ console.log('监听到数据产生了变动'); } })});data.name //控制台会打印出 “获取数据”data.name = 'world' //控制台会打印出 "监听到数据产生了变动"02 数组数据是怎么被监听的 咱们晓得,下面是对对象的数据进行监听的,咱们不能对数组进行数据的“劫持”。那么Vue是怎么做的呢? import { def } from '../util/index'const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']methodsToPatch.forEach(function (method) { // 缓存原来的办法 const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result })})看来Vue能对数组进行监听的起因是,把数组的办法重写了。总结起来就是这几步: ...

October 17, 2022 · 2 min · jiezi

关于vue.js:P3vue3-模版语法

AttributeMustache 语法不能作用在 HTML attribute 上,遇到这种状况应该应用 v-bind 指令:<div v-bind:id="dynamicId"></div>对于布尔 attribute (它们只有存在就意味着值为 true),v-bind 工作起来略有不同,在这个例子中: <button v-bind:disabled="isButtonDisabled">Button</button>如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被蕴含在渲染进去的 <button> 元素中。 JavaScript 表达式迄今为止,在咱们的模板中,咱们始终都只绑定简略的 property 键值。但实际上,对于所有的数据绑定,Vue.js 都提供了齐全的 JavaScript 表达式反对。 {{ number + 1 }}{{ ok ? 'YES' : 'NO' }}{{ message.split('').reverse().join('') }}<div v-bind:id="'list-' + id"></div>这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限度就是,每个绑定都只能蕴含单个表达式,所以上面的例子都不会失效。<!-- 这是语句,不是表达式 -->{{ var a = 1 }}<!-- 流控制也不会失效,请应用三元表达式 -->{{ if (ok) { return message } }}v-bind实例<template> <div> <p>{{test57}}</p> <p>{{test58}}</p> <p v-once>{{test57}}</p> <button @click="changeUname">changename</button> <p>{{msg}}</p> <p v-html="msg"> </p> <p v-bind:id="id">v-bind绑定</p> <img v-bind:src="url"> </div></template><script> export default{ data(){ return{ test57: '57-6666', test58: '58-jjjj', msg:"<h2>题目56</h2>>", id: "d1", url: "https://www.xiaohongshu.com/favicon.ico" } }, methods:{ changeUname: function (){ this.test57='66666666666666' }, }, };</script><style> #d1{ color: red; }</style> ...

October 14, 2022 · 1 min · jiezi

关于vue.js:vue这些原理你都知道吗面试版

前言在之前面试的时候我本人也常常会遇到一些vue原理的问题, 我也总结了下本人的常常的用到的,不便本人学习,明天也给大家分享进去, 欢送大家一起学习交换, 有更好的办法欢送评论区指出, 后序我也将继续整顿总结~ 形容 Vue 与 React 区别阐明概念:vue:是一套用于构建用户界面的渐进式框架,Vue 的外围库只关注视图层react:用于构建用户界面的 JavaScript 库 申明式, 组件化 定位vue 渐进式 响应式React 单向数据流写法 vue:template,jsx react: jsxHooks:vue3 和 react16 反对 hookUI 更新文化vue 官网提供React 第三方提供,本人抉择整个 new Vue 阶段做了什么?vue.prototype._init(option)initState(vm)Observer(vm.data)new Observer(data)调用 walk 办法,遍历 data 中的每个属性,监听数据的变动执行 defineProperty 监听数据读取和设置数据描述符绑定实现后,咱们就能失去以下的流程图 [外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-BIMbPCTn-1665722715253)(https://p3-juejin.byteimg.com...)] 图中咱们能够看出,vue 初始化的时候,进行了数据的 get\set 绑定,并创立了一个dep 对象就是用来依赖收集, 他实现了一个公布订阅模式,完后了数据 data 的渲染视图 watcher 的订阅class Dep { // 依据 ts 类型提醒,咱们能够得出 Dep.target 是一个 Watcher 类型。 static target: ?Watcher; // subs 寄存收集到的 Watcher 对象汇合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 收集所有应用到这个 data 的 Watcher 对象。 this.subs.push(sub); } depend() { if (Dep.target) { // 收集依赖,最终会调用下面的 addSub 办法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 调用对应的 Watcher,更新视图 subs[i].update(); } }}形容 vue 的响应式原理[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-HGn1QpmK-1665722715256)(https://p1-juejin.byteimg.com...)] ...

October 14, 2022 · 6 min · jiezi

关于vue.js:阿里前端高频vue面试题边面边更

Vue 中 computed 和 watch 有什么区别?计算属性 computed: (1)**反对缓存**,只有依赖数据发生变化时,才会从新进行计算函数; (2)计算属性内**不反对异步操作**; (3)计算属性的函数中**都有一个 get**(默认具备,获取计算属性)**和 set**(手动增加,设置计算属性)办法; (4)计算属性是主动监听依赖值的变动,从而动静返回内容。侦听属性 watch: (1)**不反对缓存**,只有数据发生变化,就会执行侦听函数; (2)侦听属性内**反对异步操作**; (3)侦听属性的值**能够是一个对象,接管 handler 回调,deep,immediate 三个属性**; (3)监听是一个过程,在监听的值变动时,能够触发一个回调,并**做一些其余事件**。vue是如何实现响应式数据的呢?(响应式数据原理)Vue2: Object.defineProperty 从新定义 data 中所有的属性, Object.defineProperty 能够使数据的获取与设置减少一个拦挡的性能,拦挡属性的获取,进行依赖收集。拦挡属性的更新操作,进行告诉。 具体的过程:首先Vue应用 initData 初始化用户传入的参数,而后应用 new Observer 对数据进行观测,如果数据是一个对象类型就会调用 this.walk(value) 对对象进行解决,外部应用 defineeReactive 循环对象属性定义响应式变动,外围就是应用 Object.defineProperty 从新定义数据。 写过自定义指令吗 原理是什么指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。 自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind 1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。原理 1.在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性 2.通过 genDirectives 生成指令代码 ...

October 14, 2022 · 7 min · jiezi

关于vue.js:vue面试之CompositionAPI响应式包装对象原理

本文次要分以下两个局部对 Composition API 的原理进行解读: reactive API 原理ref API 原理reactive API 原理关上源码能够找到reactive的入口,在composition-api/src/reactivity/reactive.ts,咱们先从函数入口开始剖析reactive产生了什么事件,通过之前的学习咱们晓得,reactive用于创立响应式对象,须要传递一个一般对象作为参数。 export function reactive<T = any>(obj: T): UnwrapRef<T> { if (process.env.NODE_ENV !== 'production' && !obj) { warn('"reactive()" is called without provide an "object".'); // @ts-ignore return; } if (!isPlainObject(obj) || isReactive(obj) || isNonReactive(obj) || !Object.isExtensible(obj)) { return obj as any; } // 创立一个响应式对象 const observed = observe(obj); // 标记一个对象为响应式对象 def(observed, ReactiveIdentifierKey, ReactiveIdentifier); // 初始化对象的访问控制,便于拜访ref属性时主动解包装 setupAccessControl(observed); return observed as UnwrapRef<T>;}首先,在开发环境下,会进行传参测验,如果没有传递对应的obj参数,开发环境下会给予开发者一个正告,在这种状况,为了不影响生产环境,生产环境下会将正告放过。 ...

October 14, 2022 · 4 min · jiezi

关于vue.js:VuenextTick的原理是什么vue面试进阶

原理性的货色就会文字较多,请耐下心来,细细品味 Vue中DOM更新机制当你威风凛凛地应用Vue大展宏图的时候,忽然发现,咦,我明明对这个数据进行更改了,然而当我获取它的时候怎么是上一次的值(自己比拟懒,就不具体举例了) 此时,Vue就会说:“小样,这你就不懂了吧,我的DOM是异步更新的呀!!!” 简略的说,Vue的响应式并不是只数据发生变化之后,DOM就立即发生变化,而是依照肯定的策略进行DOM的更新。这样的益处是能够防止一些对DOM不必要的操作,进步渲染性能。 在Vue官网文档中是这样阐明的: 可能你还没有留神到,Vue异步执行DOM更新。只有察看到数据变动,Vue将开启一个队列,并缓冲在同一事件循环中产生的所有数据扭转。如果同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和DOM操作上十分重要。而后,在下一个的事件循环“tick”中,Vue刷新队列并执行理论 (已去重的) 工作。文言一点就是说,其实这是和JS当中的事件循环是非亲非故的,就是Vue不可能对每一个数据变动都做一次渲染,它会把这些变动先放在一个异步的队列当中,同时它还会对这个队列外面的操作进行去重,比方你批改了这个数据三次,它只会保留最初一次。这些变动是都能够通过队列的模式保存起来,那当初的问题就来到了,那vue是在事件循环的哪个机会来对DOM进行批改呢? Vue有两种抉择,一个是在本次事件循环的最初进行一次DOM更新,另一种是把DOM更新放在下一轮的事件循环当中。z这时,尤雨溪拍了拍胸脯说:“这两种办法,我都有!” 然而因为本轮事件循环最初执行会比放在下一轮事件循环要快很多,所以Vue优先选择第一种,只有当环境不反对的时候才触发第二种机制。(结尾的链接让你懂事件循环) 尽管性能上进步了很多,但这个时候问题就呈现了,咱们都晓得在一轮事件循环中,同步执行栈中代码执行实现之后,才会执行异步队列当中的内容,那咱们获取DOM的操作是一个同步的呀!!那岂不是尽管我曾经把数据改掉了,然而它的更新异步的,而我在获取的时候,它还没有来得及改,所以会呈现文章结尾的那个问题。 这。。。我的确须要进行这样操作,那这么办呢?? 没关系啦,尤大很贴心的为咱们提供了Vue.$nextTick() Vue.$nextTick()其实一句话就能够把$nextTick这个货色讲明确:就是你放在$nextTick 当中的操作不会立刻执行,而是等数据更新、DOM更新实现之后再执行,这样咱们拿到的必定就是最新的了。 再精确一点来讲就是$nextTick办法将回调提早到下次DOM更新循环之后执行。(看不懂这句人话的,能够看下面[狗头]) 意思咱们都懂了,那$nextTick是怎么实现这个神奇的性能的呢? 外围如下: Vue在外部对异步队列尝试应用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不反对,则会采纳 setTimeout(fn, 0)代替。认真地看这句话,你就能够发现这不就是利用 JavaScript 的这些异步回调工作队列,来实现 Vue 框架中本人的异步回调队列。这其实就是一个典型的将底层 JavaScript 执行原理利用到具体案例中的示例。 我在这里略微总结一下:就是$nextTick将回调函数放到微工作或者宏工作当中以提早它地执行程序;(总结的也比拟懒) 重要的是了解源码中它的三个参数的意思: callback:咱们要执行的操作,能够放在这个函数当中,咱们没执行一次$nextTick就会把回调函数放到一个异步队列当中;pending:标识,用以判断在某个事件循环中是否为第一次退出,第一次退出的时候才触发异步执行的队列挂载timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程了解之后,在看整个$nextTick外面的执行过程,其实就是把一个个$nextTick中的回调函数压入到callback队列当中,而后依据事件的性质期待执行,轮到它执行的时候,就执行一下,而后去掉callback队列中相应的事件。 参考:前端vue面试题具体解答 应用说了这么多,怎么用它呢? 很简略很简略 mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered })}应用场景created中获取DOM的操作须要应用它就是咱们下面的例子,你如果想要获取最新值,就用它还有一些第三方插件应用过程中,应用到的状况,具体问题具体分析补充之前我始终搞不懂一个的问题,$nextTick既然把它传入的办法变成微工作了,那它和其它微工作的执行程序是怎么的呢? 这简略来说就是谁先挂载Promise对象的问题,在调用$nextTick办法时就会将其闭包外部保护的执行队列挂载到Promise对象,在数据更新时Vue外部首先就会执行$nextTick办法,之后便将执行队列挂载到了Promise对象上,其实在明确Js的Event Loop模型后,将数据更新也看做一个$nextTick办法的调用,并且明确$nextTick办法会一次性执行所有推入的回调,就能够明确执行程序的问题了 还有$nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的实质是一样的,$nextTick是实例办法,nextTick是类的静态方法而已;实例办法的一个益处就是,主动给你绑定为调用实例的this罢了。

October 13, 2022 · 1 min · jiezi

关于vue.js:vue面试常见考察点总结

Vue 的生命周期办法有哪些 个别在哪一步发申请beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访 created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。 mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点 beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程 updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。 beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。 destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。 activated keep-alive 专属,组件被激活时调用 deactivated keep-alive 专属,组件被销毁时调用 异步申请在哪一步发动?能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。 如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处: 能更快获取到服务端数据,缩小页面 loading 工夫;ssr 不反对 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;Vue中diff算法原理DOM操作是十分低廉的,因而咱们须要尽量地缩小DOM操作。这就须要找出本次DOM必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用diff算法 ...

October 13, 2022 · 13 min · jiezi

关于vue.js:Vue是怎样监听数组的变化的

上周五跟着一个师姐面试一个三年工作教训的前端开发,我在一边审慎的观摩。想着已经我也被他人面试过,现在面试他人,感觉其实情绪是一样的。前言 工作三年的Vue使用者应该懂什么?为何工作几年的根底越来越弱?工作如何挤出工夫学习?一道面试题其实咱们并不是要你把答案都记下来,而是把其中的思维学习到。就像你接触一个新的畛域react,你也一样能够把根本思维提炼进去。 面试题: Vue是如何对数据进行监听的? 这其实是陈词滥调的问题,凡是你有一点基础知识,你也能答出一二。师姐跟我说,其实问题不只是问题自身,而是跟这个常识顺带进去的体系。 01 对象数据是怎么被监听的 在vue2.x版本中,数据监听是用过Object.defineProperty这个API来实现的,咱们能够来看一个例子 var text = 'vue';const data = {};Object.defineProperty(data, 'text', { get() { return text; }, set(newVal) { text = newVal; }});data.text // 'vue'data.text = 'react' // 'react'当咱们拜访或设置对象的属性的时候,都会触发绝对应的函数,而后在这个函数里返回或设置属性的值。咱们当然能够在触发函数的时候做咱们本人想做的事件,这也就是“劫持”操作。 在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并创立一个监听器,当数据发生变化的时候发出通知。 var data = { name:'hello', age:18}Object.keys(data).forEach(function(key){ Object.defineProperty(data,key,{ enumerable:true, // 是否能在for...in循环中遍历进去或在Object.keys中列举进去。 configurable:true, // false,不可批改、删除指标属性或批改属性性以下个性 get:function(){ console.log('获取数据'); }, set:function(){ console.log('监听到数据产生了变动'); } })});data.name //控制台会打印出 “获取数据”data.name = 'world' //控制台会打印出 "监听到数据产生了变动"02 数组数据是怎么被监听的 咱们晓得,下面是对对象的数据进行监听的,咱们不能对数组进行数据的“劫持”。那么Vue是怎么做的呢? import { def } from '../util/index'const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']methodsToPatch.forEach(function (method) { // 缓存原来的办法 const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result })})看来Vue能对数组进行监听的起因是,把数组的办法重写了。总结起来就是这几步: ...

October 13, 2022 · 2 min · jiezi

关于vue.js:vue组件通信6种方式总结常问知识点

前言在Vue组件库开发过程中,Vue组件之间的通信始终是一个重要的话题,尽管官网推出的 Vuex 状态治理计划能够很好的解决组件之间的通信问题,然而在组件库外部应用 Vuex 往往会比拟重,本文将零碎的列举出几种不应用 Vuex,比拟实用的组件间的通信形式,供大家参考。 组件之间通信的场景在进入咱们明天的主题之前,咱们先来总结下Vue组件之间通信的几种场景,个别能够分为如下几种场景: 父子组件之间的通信兄弟组件之间的通信隔代组件之间的通信父子组件之间的通信父子组件之间的通信应该是 Vue 组件通信中最简略也最常见的一种了,概括为两个局部:父组件通过prop向子组件传递数据,子组件通过自定义事件向父组件传递数据。 父组件通过 prop 向子组件传递数据Vue组件的数据流向都遵循单向数据流的准则,所有的 prop 都使得其父子 prop 之间造成了一个单向上行绑定:父级 prop 的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外变更父级组件的状态,从而导致你的利用的数据流向难以了解。 额定的,每次父级组件产生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转 prop。如果你这样做了,Vue 会在浏览器的控制台中收回正告。 父组件 ComponentA: <template> <div> <component-b title="welcome"></component-b> </div></template><script>import ComponentB from './ComponentB'export default { name: 'ComponentA', components: { ComponentB }}</script>子组件 ComponentB: <template> <div> <div>{{title}}</div> </div></template><script>export default { name: 'ComponentB', props: { title: { type: String, } }} </script>子组件通过自定义事件向父组件传递数据在子组件中能够通过 $emit 向父组件产生一个事件,在父组件中通过 v-on/@ 进行监听。 子组件 ComponentA: <template> <div> <component-b :title="title" @title-change="titleChange"></component-b> </div></template><script>import ComponentB from './ComponentB'export default { name: 'ComponentA', components: { ComponentB }, data: { title: 'Click me' }, methods: { titleChange(newTitle) { this.title = newTitle } }}</script>子组件 ComponentB: ...

October 12, 2022 · 5 min · jiezi