首页
写过的文章
Search
1
mock的基本用法
719 阅读
2
js的数组常用方法
694 阅读
3
cnpm安装教程
566 阅读
4
能有效解决问题的提问方法2.0
555 阅读
5
Vue、git指令
491 阅读
默认分类
JavaScript
Vue
TypeScript
登录
Search
标签搜索
底部导航
tabbar
底部导航栏
vue3隐藏底部导航栏
vue3隐藏tabbar
网页
黑白
网页黑白
cnpm安装
cnpm
Z的故事
累计撰写
23
篇文章
累计收到
82
条评论
首页
栏目
默认分类
JavaScript
Vue
TypeScript
页面
写过的文章
搜索到
23
篇与
Zuiet
的结果
2022-11-03
Vue、git指令
GitGit一个开源的分布式版本控制系统,项目管理工具,git安装请输入链接描述下面是平常在项目中常用的一些指令// 初始化:创建一个git仓库 git init // 添加文件到暂存区 .全部文件添加到暂存区 git add . // 删除文件 git rm filename // 提交文件 git commit -m "提交的说明" // 切换分支 git checkout name // 查看分支 git branch // 创建分支 git branch 分支名 // 查看状态 git status // 合并分支到当前分支 git merge 分支名 // 删除分支 git branch -d 分支名 // 推送分支到远端仓库 git push // 把所有未提交的修改(包括暂存的和非暂存的)都保存起来 git stash // 恢复之前缓存的工作目录 git stash pop // 查看现有stash git stash list // 移除stash git stash drop // 拉取远端仓库分支 git pull // 克隆 git clone 还有就是vue相关的npmnpm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具。通过 npm 可以安装、共享、分发代码,管理项目依赖关系。安装完成以后可以通过node -v查看node版本,npm -v查看npmnpm的一些指令// 下载依赖 npm install // 启动项目 npm run serve // 打包 npm run build // 下载包 npm install name // 删除包 npm uninstall name 最后就是Vue的脚手架安装vue-cli(全局安装vue-cli) 命令:cnpm install vue-cli -g检查环境是否安装上:vue -V创建vue项目:在命令行里输入命令:vue init vue_demo,如果不想使用命令,vue 脚手架还可以可视化配置在命令行中输入vue ui最后运行程序:npm run serve
2022年11月03日
491 阅读
10 评论
0 点赞
2022-10-06
斐波那契数列
什么是斐波那契数列?前两个数字的之和等于第三个数字,比如:0 1 1 2 3 5 8 13 21 ...代码如下:方法一:package a02_fbo; public class Main { public static int fib1(int n) { if (n <= 1) return n; return fib1(n - 1) + fib1(n - 2) } public static void main(String[] args) { System.out.print(fib1(0)); // 结果 0 System.out.print(fib1(1)); // 结果 1 System.out.print(fib1(2)); // 结果 1 System.out.print(fib1(3)); // 结果 2 System.out.print(fib1(4)); // 结果 3 } } 上面方法的话在计算结果上不会出现问题,但逻辑过于简单,如果出现再大一些的数字,会出现卡顿!这是因为栈的空间不够支撑那么大的计算量,溢出了!方法二:package a02_fbo; public class Main { public static int fib1(int n) { if(n <= 1) return n; return fib1(n - 1) + fib1(n - 2); } public static int fib2(int n) { if(n <= 1) return n; int frist = 0; int second = 1; for (int i = 0; i < n - 1; i++) { int sum = frist + second; frist = second; second = sum; } return second; } public static void main(String[] args) { System.out.print(fib2(64)); // 结果 1640636603 } } 这样写不管多大的数字都能快速给出打印结果
2022年10月06日
416 阅读
0 评论
0 点赞
2022-10-06
防抖节流的基本原理
防抖(Debounce)和节流(Throttle)都是用来控制某个函数在一定时间内触发次数,两者都是为了减少触发频率,以便提高性能或者是避免资源浪费.毕竟JS操作DOM对象的代价还是十分昂贵的.防抖防抖是指在事件触发n秒后在执行回调,如果在n秒内再次触发,则重新计算时间.(就是再触发某个事件后,在下一次触发之前,中间的间隔事件如果超过设置的时间才会发送请求,一直触发就不会发送请求)代码实现:function debounce(fn, delay) { let timeId = null return function() { const context = this // 如果在n秒内从新计算 if(timeId) { window.clearTimeout(timeId) } // 如果在n秒后调用 timeId = setTimeout(() => { fn.apply(context, arguments) timeId = null }, delay) } } 应用场景有:scroll事件滚动触发搜索框输入查询表单验证按钮提交事件浏览器窗口缩放,resize事件节流节流是指如果持续触发某个事件, 则每隔n秒执行一次代码实现:function throttle(fn, delay) { //设置一个触发开关 let canUse = true return function() { //如果为true, 就触发,否则就不触发 if(canUse) { fn.apply(this, arguments) //触发开关后,关闭开关 casUse = false //每隔delay秒执行一次 setTimeout(() => canUse = true, delay) } } } 应用场景有:DOM元素的拖拽功能实现射击游戏类计算鼠标移动的距离监听scroll事件
2022年10月06日
376 阅读
0 评论
0 点赞
2022-09-18
mock的基本用法
什么是mock增加测试的真实性,可以模拟各种数据mock的用法比较简单而且不会涉及跨域问题方便扩展支持扩展更多数据类型,支持自定义函数和正则。mock在使用之前,通常前端要和后端沟通数据接口结构和数据类型不需要修改既有代码,就可以拦截Ajax请求,返回模拟的响应数据数据类型丰富有随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等目前基本都是前后端分离开发,前端和后端并行同时开发,可以通过mock数据来解决后端接口没有写好的问题mock的使用(vue)第一步: 引入mockjs插件npm install mockjs -S // 或者 cnpm yarn都可以 第二步: 在scr下创建一个文件夹,用于存放模拟的数据的文件(src/mock/index.js)const Mock = require("mockjs"); let data = Mock.mock({ "data|6": [{ //生成6条数据 数组 "shopId|+1": 1, //生成商品id,自增1 shopMsg: "@ctitle(10)", //生成商品信息,长度为10个汉字 shopName: "@cname", //生成商品名 , 都是中国人的名字 shopTel: /^1(5|3|7|8)[0-9]{9}$/, //生成随机电话号 shopAddress: "@county(true)", //随机生成地址 "shopStar|1-5": "★", //随机生成1-5个星星 "salesVolume|30-1000": 30, //随机生成商品价格 在30-1000之间 //生成随机图片,大小/背景色/字体颜色/文字信息 shopLogo: "@Image('100x40','#c33', '#ffffff','小北鼻')", "food|2": [{ //每个商品中再随机生成2个food foodName: "@cname", //food的名字 //生成随机图片,大小/背景色/字体颜色/文字信息 foodPic: "@Image('100x40','#c33', '#ffffff','小可爱')", "foodPrice|1-100": 20, //生成1-100的随机数 "aname|2": [{ aname: "@cname", "aprice|30-60": 20, }, ], }, ], }, ], }); Mock.mock(/goods\/goodAll/, "post", () => { //三个参数。第一个:路径,第二个:请求方式post/get,第三个:回调,返回值 return data; }); 第三步: 需要在入口主文件 main.js中,引入这个模拟数据的文件import "./mock/index.js" 第四步: 在页面中,我们直接可以进行axios请求(正常是封装在api中,便于后期维护)<template> <div id="app"> <button @click="getTeachFn">请求mock</button> <router-view /> </div> </template> <script> import axios from "axios"; export default { components: {}, data() { return {}; }, created() {}, mounted() {}, methods: { //点击事件 getTeachFn() { axios.post("http://localhost:8080/goods/goodAll").then((res) => { console.log(res); }); }, }, }; </script> 第五步: 直接在页面中点击按钮获取请求过来的数据看这里mock.js仓库非mock规则示例
2022年09月18日
719 阅读
15 评论
0 点赞
2022-09-17
keep-alive
keep-alive在vue-router中使用,保活一个路由组件。一般写法vue3中,对于这个问题,写法有点不一样。<router-view>、<keep-alive> 和 <transition> transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用,下面是一个案例:<router-view v-slot="{ Component,route }"> <transition> <keep-alive> <component :is="Component" v-if="route.meta.keepalive==true" :key="route.path" /> </keep-alive> <component :is="Component" v-if="route.meta.keepalive==false" :key="route.path" /> </transition> </router-view>\ 原因: 这是一个必要的变化。详见 related RFC.所以说这里还有其他的信息,transition过度效果,现在也要用这种方式来写了。回顾一下插槽:其中v-slot="{ Component }"这种写法,是解构插槽 Prop,用来解构作用域插槽的参数,作用域插槽是用来向组件提供插槽属性的:绑定在 元素上的 attribute 被称为插槽 prop。现在,在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:// 一个todo-list组件,有一个默认的插槽 <ul> <li v-for="( item, index ) in items"> <slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot> </li> </ul> // 作用域插槽 <todo-list> <template v-slot:default="slotProps"> <i class="fas fa-check"></i> <span class="green">{{ slotProps.item }}</span> </template> </todo-list> 推荐写法keep-aliveProps: include - string | RegExp | Array。只有名称匹配的组件会被缓存。 exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。 max - number | string。最多可以缓存多少组件实例。 keepAlive本身具有的include去匹配。比如我有一个routes: onst routes = [{ path:"/", component:layout, redirect:"/AccessibleMap", children:[{ path:"page404", name:"page404", meta:{title:"page404",ismenu:false,keepalive:false}, component:()=> import("../pages/page404.vue") }, { path:"AccessibleMap", name:"AccessibleMap", meta:{title:"第一个例子",ismenu:true,keepalive:true}, component:()=> import("../pages/AccessibleMap.vue") }, { path:"tianditu", name:"tianditu", meta:{title:"加载天地图",ismenu:true,keepalive:true}, component:()=> import("../pages/tianditu.vue") }, { path:"baohuo", name:"baohuo", meta:{title:"保活组件",ismenu:true,keepalive:true}, component:()=> import("../pages/baohuo.vue") } ] }]; 在组件里面去引用,只需要过滤出一个需要保活的name数据就可以,比如说“baohuo”这个组件,组件要定义name属性(并不是指上面路由表routes里面那个name,而是baohuo.vue的name)匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称(父组件 components选项的键值)。匿名组件不能被匹配。<template> <div class="catalog"> <router-link class="mylink" active-class="my-active" v-for="item in link" :to="`/${item.path}`"> <span v-if="item.meta.ismenu == true">{{ item.meta.title }}</span> </router-link> </div> <router-view v-slot="{ Component, route }"> <keep-alive :include="keepaliveRoutes"> <component :is="Component" :key="route.path" /> </keep-alive> </router-view> </template> <script lang="ts"> import { defineComponent } from "vue"; import { routes } from "../router" interface idata { link: { path: string; meta: { title: string, ismenu: boolean, keepalive: boolean } }[]; keepaliveRoutes: Array<string>, } export default defineComponent({ data(): idata { return { link: [], keepaliveRoutes:[], } }, components: {}, created() { this.link = routes[0].children.map(e => { return { path: e.path, meta: e.meta } }) routes[0].children.forEach(e=>{ if(e.meta.keepalive == true){ this.keepaliveRoutes.push(e.path) } }) }, }); </script> 普通组件的保活跟上面说的类似。源码分析日常开发中,如果需要在组件切换时,保存组件的状态,防止它多次销毁,多次渲染,我们通常采用 <keep-alive> 组件处理,因为它能够缓存不活动的组件,而不是销毁它们。同时, <keep-alive> 组件不会渲染自己的 DOM 元素,也不会出现在组件父链中,属于一个抽象组件。当组件在 <keep-alive>内被切换时,它的 activated 和 deactivated 这两个钩子函数将会被对应执行。基础用法以下是 <keep-alive> 组件的示例用法<keep-alive :include="['a', 'b']" :max="10"> <component :is="view"></component> </keep-alive> 属性 Propsinclude 字符串或表达式。只有名称匹配的组件会被缓存。exclude 字符串或正则表达式。任务名称匹配的组件都不会被缓存。max 数字。最多可以缓存多少组件实例。注意的是,<keep-alive>组件是用在直属的子组件被开关的情况,若存在多条件性的子元素,则要求同时只能有一个元素被渲染组件源码实现上面我们了解了 <keep-alive>组件的定义、属性以及用法,下面就看下源码是如何对应实现的。抽象组件我们去掉多余的代码,看看KeepAlive组件是如何定义的const KeepAliveImpl = { __isKeepAlive: true, inheritRef: true, props: { include: [String, RegExp, Array], exclude: [String, RegExp, Array], max: [String, Number] }, setup(props: KeepAliveProps, { slots }: SetupContext){ // 省略其他代码... return()=>{ if (!slots.default) { return null } // 拿到组件的子节点 const children = slots.default() // 取第一个子节点 let vnode = children[0] // 存在多个子节点的时候,keepAlive组件不生效了,直接返回 if (children.length > 1) { current = null return children } else if ( !isVNode(vnode) || !(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) ) { current = null return vnode } // 省略其他代码... // 返回第一个子节点 return vnode } } } 从源码可以看出 KeepAlive 组件是通过 Composition API 实现的,setup 返回的是组件的渲染函数。在渲染函数内,取组件的子节点,当存在多个子节点,则直接返回所有节点,也就 KeepAlive 组件不生效了。当仅存在一个子节点,则渲染第一个子节点的内容,也就验证了 KeepAlive 是抽象组件,不渲染本身的 DOM 元素。缓存机制了解 KeepAlive 组件缓存机制前,我们先了解下 LRU 算法概念,它正是通过该算法来处理缓存机制。LRU 算法我们常用缓存来提升数据查询的数据,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,让新数据添加进来。因此需要制定一些策略对加入缓存的数据进行管理。常见的策略有:LUR 最近最久未使用FIFO 先进先出NRU Clock 置换算法LFU 最少使用置换算法PBA 页面缓冲算法KeepAlive 缓存机制使用的是 LRU 算法(Least Recently Used),当数据在最近一段时间被访问,那么它在以后也会被经常访问。这就意味着,如果经常访问的数据,我们需要能够快速命中,而不常访问的数据,我们在容量超出限制,要将其淘汰。我们这里只讲概念,如果想深入理解 LRU 算法,可自行查找。缓存实现简化下代码,抽离出核心代码,看看缓存机制const KeepAliveImpl = { setup(props){ // 缓存KeepAlive子节点的数据结构{key:vNode} const cache: Cache = new Map() // 保存KeepAlive子节点唯一标识的数据结构 const keys: Keys = new Set() let current: VNode | null = null let pendingCacheKey: CacheKey | null = null // 在beforeMount/Update 缓存子树 const cacheSubtree = () => { if (pendingCacheKey != null) { cache.set(pendingCacheKey, instance.subTree) } } onBeforeMount(cacheSubtree) onBeforeUpdate(cacheSubtree) return ()=>{ pendingCacheKey = null const children = slots.default() let vnode = children[0] const comp = vnode.type as Component const name = getName(comp) // 解构出属性值 const { include, exclude, max } = props // key值是KeepAlive子节点创建时添加的,作为缓存节点的唯一标识 const key = vnode.key == null ? comp : vnode.key // 通过key值获取缓存节点 const cachedVNode = cache.get(key) if (cachedVNode) { // 缓存存在,则使用缓存装载数据 vnode.el = cachedVNode.el vnode.component = cachedVNode.component if (vnode.transition) { // 递归更新子树上的 transition hooks setTransitionHooks(vnode, vnode.transition!) } // 阻止vNode节点作为新节点被挂载 vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE // 让key始终新鲜 keys.delete(key) keys.add(key) } else { keys.add(key) // 属性配置max值,删除最久不用的key,这很符合LRU的思想 if (max && keys.size > parseInt(max as string, 10)) { pruneCacheEntry(keys.values().next().value) } } // 避免vNode被卸载 vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE current = vnode return vnode; } } } 从源码中可以看出 KeepAlive 声明了了个 cache 变量来缓存节点数据,它是 Map 结构。并采用 LRU 缓存算法来处理子节点存储机制,具体说明如下:声明有序集合 keys 作为缓存容器,容器内缓存组件的唯一标识 keykeys 缓存容器中的数据,越靠前的 key 值越少被访问越旧,往后的值越新鲜渲染函数执行时,若命中缓存时,则从 keys 中删除当前命中的 key,并往 keys 末尾追加 key 值,保存新鲜未命中缓存时,则 keys 追加缓存数据 key 值,若此时缓存数据长度大于 max 最大值,则删除最旧的数据,这里的值是 keys中第一个值,很符合 LRU 思想。当触发 beforeMount/update 生命周期,缓存当前激活的子树的数据挂载区别通常组件挂载、卸载都会触发各自生命周期,那 KeepAlive 子树有无缓存在挂载阶段是否存在区别呢?以下抽离下 patch 阶段中ShapeFlags.COMPONENT 类型相关核心代码看看。 const processComponent = (n1: VNode | null,n2: VNode,container: RendererElement,anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null,parentSuspense: SuspenseBoundary | null, isSVG: boolean,optimized: boolean ) => { if (n1 == null) { // 存在COMPONENT_KEPT_ALIVE ,激活n2 if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ;(parentComponent!.ctx as KeepAliveContext).activate(n2,container,anchor,isSVG,optimized) } else { // 否则,挂载组件 mountComponent(n2,container,anchor,parentComponent,parentSuspense,isSVG,optimized) } } else { // 更新组件 updateComponent(n1, n2, optimized) } } `KeepAlive 组件在渲染函数执行时,若存在缓存,会给 vNode 赋予 `vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE `状态,因此再次渲染该子树时,会执行`parentComponent!.ctx.activate` 函数 激活子树的状态。那这里的 `activate` 函数是什么呢?看下代码` const instance = getCurrentInstance() const sharedContext = instance.ctx as KeepAliveContext sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { const instance = vnode.component! // 挂载节点 move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // 更新组件,可能存在props发生变化 patch(instance.vnode,vnode,container,anchor,instance,parentSuspense,isSVG,optimized) queuePostRenderEffect(() => { // 组件渲染完成后,执行子节点组件定义的actived钩子函数 instance.isDeactivated = false if (instance.a) {invokeArrayFns(instance.a)} const vnodeHook = vnode.props && vnode.props.onVnodeMounted if (vnodeHook) { invokeVNodeHook(vnodeHook, instance.parent, vnode) } }, parentSuspense) } 再次激活子树时,因为上次渲染已经缓存了vNode,能够从 vNode 直接获取缓存的 DOM 了,也就无需再次转次 vNode。因此可以直接执行move挂载子树,然后再执行 patch 更新组件,最后再通过queuePostRenderEffect,在组件渲染完成后,执行子节点组件定义的activate钩子函数。再看下激活/失效的实现思路,通过将渲染器传入 KeepAlive 实例的 ctx 属性内部,实现 KeepAlive 与渲染器实例的通信,并且通过 KeepAlive 暴露 activate/deactivate 两个实现。这样做的目的是,避免在渲染器直接导入 KeepAlive 产生 tree-shaking。属性实现KeepAlive 支持 3 个属性 include,exclude,max。其中 max 在上面已经讲过了,这里看下另外 2 个属性的实现setup(){ watch(() => [props.include, props.exclude], ([include, exclude]) => { include && pruneCache(name => matches(include, name)) exclude && pruneCache(name => matches(exclude, name)) }) return ()=>{ if ( (include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name)) ) { return (current = vnode) } } } 这里很好理解,当子组件名称不匹配 include 的配置值,或者子组件名称匹配了 exclude 的值,都不该被缓存,而是直接返回。而 watch 函数是监听 include、exclude 值变化时做出对应反应,即去删除对应的缓存数据。卸载过程卸载分为子组件切换时产生的子组件卸载流程,以及 KeepAlive 组件卸载导致的卸载流程子组件卸载流程组件卸载过程,会执行 unmount 方法,然后执行 parentComponent.ctx.deactivate(vnode)函数,在函数里通过 move 函数移除节点,然后通过 queuePostRenderEffect 的方式执行定义的 deactivated 钩子函数。此过程跟挂载过程类似,不过多描述。KeepAlive 组件卸载当 KeepAlive 组件卸载时,会触发onBeforeUnmount函数,现在看看该函数的实现: onBeforeUnmount(() => { cache.forEach(cached => { const { subTree, suspense } = instance if (cached.type === subTree.type) { resetShapeFlag(subTree) const da = subTree.component!.da da && queuePostRenderEffect(da, suspense) return } unmount(cached) }) }) 当缓存的 vnode 为当前 KeepAlive 组件渲染的 vnode 时,重置 vnode 的 ShapeFlag,让它不被当做是 KeepAlive 的 vNode,然后通过 queuePostRenderEffect 执行子组件的 deactivated 函数,这样就完成了卸载逻辑。否则,则执行 unmount 方法执行 vnode 的整套卸载路程。附:LRU 算法class LRUCache{ constructor(capacity){ this.capacity = capacity || 2 this.cache = new Map() } // 存值,超出最大则默认删除第一个:最近最少被用元素 put(key,val){ if(this.cache.has(key)){ this.cache.delete(key) } if(this.cache.size>=this.capacity){ this.cache.delete(this.cache.keys().next().value) } this.cache.set(key,val) } // 取值,同时刷新缓存新鲜度 get(key){ if(this.cache.has(key)){ const temp = this.cache.get(key) this.cache.delete(key) this.cache.set(key,temp) return temp } return -1 } }
2022年09月17日
416 阅读
0 评论
0 点赞
1
2
3
4
5