和女朋友争论了1个小时,在vue用throttle居然这么黑盒?
首先我们都知道 throttle(节流) 和 debounce(防抖) 是性能优化的利器。
本文会简单介绍一下这两个的概念 但是并不会对这两个函数再进行老生常谈地说原理了 而是会说它和 vue 之间的爱恨情仇~ 但是在步入正题以前 我们得先知道它的一些简介。
函数节流(throttle) 是指一定时间内 js 方法只运行一次。
节流节流就是节省水流的意思 就像水龙头在流水 我们可以手动让水流 在一定时间内 小一点 但是他会一直在流。
函数节流的情况下 函数将每隔 n 秒执行一次 常见的场景为
函数防抖(debounce) 只当有足够的空闲时间 才运行代码一次。
比如生活中的坐公交 就是一定时间内 如果有人陆续刷卡上车 司机就不会开车。只有别人没刷卡了 司机才开车。 其实只要记住了节流的思想就能通过排除法判断节流和防抖了
函数防抖的情况下 函数将一直推迟执行 造成不会被执行的效果 常见的场景为
那么它们和 vue 结合会擦除怎么样的火花呢 你有了以上的基础知识后 下面正片就正式开始了~ 最近和女朋友谈了下 vue throttle 相关的问题 一开始以为是简单的的东西 没想到真的讨论了1个小时.... 前方高能硬核 层层递进涉及到 vue 源码。
问题形态一:
input input download / methods: { download: () { this.throttle(xxx) ...
我们来分析为什么这样是不行 首先我们来看看正常情况下 throttle 是怎么写的 再来拆分拆分 throttle 。
window.addEventListener( mousemove , throttle(xxx));
进一步拆分
const handleMove throttle(xxx) window.addEventListener( mousemove , handleMove);
我们一直调用的是 handleMove 方法 而 throttle 的原理是依赖于 JS 的闭包原理 依赖于handleMove 中的闭包变量。而如果你在 handleMove 外层再套一层 download 函数 贼无法让 handleMove 中的闭包内的变量进行了缓存 因此也失去了throttle 的效果。
那我们来改造一下 看起来是正确地形态。
input input throttle(download(xxx)) methods: { download: (xxx) { throttle: ... ...
开始一顿疑惑 没错呀 这的确就是 throttle 正确写法的样子 为什么这样就不行呢 再加上好久没有写 vue 的黑魔法了 一时不知道如何解释。
赶紧偷偷查资料 默默地在谷歌输入下了 vue debounce ...
搜到了一些正确的打开方式。
发现它这样是可以使用的 而我将他写到模板中不行。
emm。查不到 那开始思考 为什么这个写法不行 等等 我刚刚说了什么 把时间倒退 3.3 秒前... 为什么是3.3秒 因为人类平均说话语速是200字/分钟
写法 对啊 是写法 这个只是 vue 的模板语法 真实浏览器运行的并不是这个样子啊。
感觉有思路了 快快快 快找 vue 模板编译完后的样子
在浏览器输入下下了vue 模板 在线这几个关键词。
很快我们就查到了这个地址 https://template-explorer.vuejs.org/
我们将我们的模板输入到左侧的输入框。
我们得到了这样的一个解析后的 render 函数。
function render() { with(this) { return _c( input , { on: { input : function ($event) { throttle(download(xxx)); }
在这里我们看到 我们能大概知道 通过解析后 input 监听方法已经被包裹了一层函数。也很容猜出 最终解析成真正的绑定的函数会变成以下这个样子。
xxxx.addEventListener( input , function ($event) { throttle(download(xxx)); })
如果是这个样子的 throttle 我相信有了解 throttle 的朋友们一眼就能看出来 这样子的 throttle 是完全不起效果的。
而我们刚才资料中查询到的方式呢
template input input click / /template script click: _.throttle(() { /script
function render() { with(this) { return _c( input , { on: { input : click }
这种方式下 vue 是直接传递绑定的实践方法的 并不会有任何包装。
所以真相只有一个
果然是 vue 模板的黑魔法
那我们通过 vue 的源码来探索一下 vue 的模板解析的原理 来加深一些我们的印象。
由于这里部分是 vue 事件编译相关的代码 我们很容易地找到了 vue 源码 目前看的是 v2.6.12版本 的位置。
https://github.com/vuejs/vue/blob/v2.6.12/src/compiler/codegen/events.js#L96
我们看到 vue 源码中含关于事件生成是以下代码。
const fnExpRE /^([\w$_] |\([^)]*?\))\s* |^function(?:\s [\w$] )?\s*\(/ const fnInvokeRE /\([^)]*?\);*$/ const simplePathRE /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[ [^ ]*? ]|\[ [^ ]*? ]|\[\d ]|\[[A-Za-z_$][\w$]*])*$/ const isMethodPath simplePathRE.test(handler.value) const isFunctionExpression fnExpRE.test(handler.value) const isFunctionInvocation simplePathRE.test(handler.value.replace(fnInvokeRE, )) if (!handler.modifiers) { // 判断如果是个方法或者是函数表达式 就返回 value if (isMethodPath || isFunctionExpression) { return handler.value /* istanbul ignore if */ if (__WEEX__ handler.params) { return genWeexHandler(handler.params, handler.value) // 如果不满足以上的情况就会包一层方法 return function($event){${ isFunctionInvocation ? return ${handler.value} : handler.value }} // inline statement } else { }
由于我们的是没有 修饰符(modifiers)的 因此我们关于含有修饰符的代码注释了 防止不必要的干扰。
为了能更好地梳理情况 我们将 isMethodPath 称作方法路径 而将 isFunctionExpression称作函数表达式,isFunctionInvocation称为函数调用 虽然英文就是这个意思 但是为了大家都能看明白吧
通过以上代码我们能明白 如果这个事件的写法 满足 isMethodPath 或者满足isFunctionExpression。那么我们在事件中的写法会被直接返回 否则的话 会被包一层 function。
我们一一来看看关于事件的情景。isMethodPath 的判断方法是const simplePathRE /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[ [^ ]*? ]|\[ [^ ]*? ]|\[\d ]|\[[A-Za-z_$][\w$]*])*$/ 乍一看有点长 我们通过可视化工具分析分析。
通过可视化可以看出 我们的事件方式如果是以上形态就会通过正则的检验 例如 handle, handle[ xx ], handle[ xx ],handle[xxx], handle[0], console.log 这些情况都是不会被包裹一层函数。
还有一种情况就是 正则 const fnExpRE /^([\w$_] |\([^)]*?\))\s* |^function(?:\s [\w$] )?\s*\(/。
简单来讲就是写一个匿名函数 (xx) {} 或者 funciton(){}。
除了以上两种情况之外的所有情况都会被包含一层方法。
还记得 vue 的官方教程中 我们写模板语法的时候 以下两种方式是等价的。
1. div click handler /div
2. div click handler() /div
因为在编译的时候 他们会分别被编译成以下形态。
xxx.onclick handle xxx.onclick function($event) { return handler(); }
通过包一层函数来达到相同的目的 现在你能明白了吧 在 vue 中写 怎么写都不会出问题 有时候可能是你偶然手误 它都讲这些情况考虑在内了 就像是吃饭一样 饭已经喂到我们嘴边了。
而在被函数包裹的情况又分了两种情况。
isFunctionInvocation ? return ${handler.value} : handler.value
isFunctionInvocation的检测就是将函数调用的部分去掉 如果去掉后 满足方法路径的情况 那么就会多一个 return。
我们来画个图总结一下。
而我们的情况是怎么样的呢
throttle(download(xxx))
显然我们既不满足方法路径、也不满足函数表达式 因此就会出现我们上述的 bug 让我们的 throttle 失效了。
至此 我们已经清楚了关于 vue 中的黑魔法了 vue 给我们带来便利的同时 我们运用的不好 或者说不理解它的一些思想原理 就会发生一些神奇的事情。
所以上述说了这么多 我们需要有个最佳的实践方案。
template input click download(xxx) / /template script import {debounce} from lodash methods: { download: debounce((xxx) { /script
那么我们再来解释一个问题 外部导入和内部 methods 的差异性
template input click debounce(download(xxx)) / /template script import {debounce} from lodash /script
先说以上写法是会出错的。
因为在我们模板中写的方法 必须是 methods 中的方法 否则就会找不到。
也许这样我们直接像在模板中写 throttle 就必须将这个函数定义在 methods 中 这样是非常不友好的 因为会反直觉 对于太久没写的我(T T忘记了)。
那为什么不可以直接写在模板上面呢 其实这也和 vue 的编译相关的 因为 vue 模板中的方法都会被编译成 _vm.xxx 举个例子。
template input click debounce(download(xxx)) / /template
以上模板代码会被编译成这个样子。
/* template */ var __vue_render__ function() { var _vm this; var _h _vm.$createElement; var _c _vm._self._c || _h; return _c( input , { on: { click: function($event) { _vm.debounce(_vm.download(_vm.xxx)); };
以上才是真正在浏览器执行的代码 所以我们可以很清楚地看到 _vm 中是不存在 debounce 这也是 template 只能访问 vue 中定义的方法与变量。
我们再来探究一下 vue 3.0 是否对这个有改动。
答案是: 没有。
我特地去找了 vue/compiler-sfc 进行了测试。
const sfc require( vue/compiler-sfc const template sfc.compileTemplate({ filename: example.vue , source: input input throttle(download(xxx)) / , id: });
// output import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from vue export function render(_ctx, _cache) { return (_openBlock(), _createBlock( input , { onInput: _cache[1] || (_cache[1] $event (_ctx.throttle(_ctx.download(_ctx.download(_ctx.xxx))));
从这一次的探索来看 vue 自身模板语言需要很多心智模型 而在本实例中 vue给了我们很多语法糖 让我们沉醉其中 不得不说这样的方式很舒服 但是总有一天我们独自承受这些苦楚。
这就不得不讨论到 React 的 JSX 虽然它麻烦 对我们很残酷 但是我们对自身的行为更加可控 虽然 vue 也可以用 JSX,但是 Templates 依旧是是官方推荐的方法 我也能理解 vue 上述的这些表现 因为它帮我们做了很多处理 对于某些情况它需要给我们注入 $event, 也就是我们常用的事件对象 但是别人帮我们手把手处理了这些事情 也使得我们慢慢忘记了它原本的形态 一旦出现问题 会让我们举手无措。而 JSX 中则要求我们写出完整的代码 这样的方式使得我们写什么都需要付出额外的劳动 也许像 vue 官方文档中所说 谈论 JSX 和 vue 的 Templates 是肤浅的的 但是不管怎么样 每个人都会对它有不一样的理解 不一样的喜好 所以自己总结了一下。
都学就完si儿了 :)
1小时入手vue Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了 batteries-included 的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本
vue3 效率提升主要表现在哪些方面? `vue3.0`的各种表现还是非常棒的,相比`vue2.0`确实上了一个台阶,据说在客户端渲染效率比vue2提升了`1.3~2`倍,SSR渲染效率比vue2提升了`2 ~3`倍。在面试的过程中可能也会被问到。
相关文章
- 【转】[总结]vue开发常见知识点及问题资料整理(持续更新)
- vue项目打包发布
- vue3.0使用ant-design-vue进行按需加载原来这么简单
- MUI使用vue示例
- vue element-ui表格里时间戳转换成时间显示
- 灵活的Vue组件——原来这么简单
- 基于Vue的动态通用table表格及dialog对话框处理技巧总结
- Vue知识点整理(面试)
- vue.js 使用技巧总结
- Vue 项目里戳中你痛点的问题及解决办法
- vue项目中对axios的封装
- vue-仿手机qq的demo
- 基于vue.js重写Cnodejs.org社区的webapp
- vue-router beforeEach钩子
- vue——同网络下让别人访问本地项目