这篇博客记录自己学习的感悟,也给一些学习vue3的同学一些面试技巧,实战经验。
技术是很纯粹的事情,并不是那些虚无缥缈的管理经验,技术是最简单的,你会就是会,不会就不会。技术也是分得清界限的,分别是了解,熟悉,创新。
时序图失效了!!!!
客户端->>客户端: 异步请求队列控制
客户端->>服务端: 发送操作数据
服务端-->>客户端: 响应成功
服务端->>MQ: 数据发送给MQ(异步,解耦,并发)
MQ-->>服务端: 订阅消息
服务端-)阿里云: 操作数据追加写入oss带着润物细无声的态度学习,享受清风徐来的名利
工作和娱乐是不一样的,一本小说我看也看了,情节也是记得一些,可是记不全了,只是记得其他一些片段,这些不影响我跟别人聊这个小说,自己也可以回味这个小说。但是工作可不是这么一回事,工作是很明显的结果导向。
看尤雨溪原版讲解远远比技术八股好 B站vue3新特性介绍
尤雨溪原版讲解vue实现 B站vue实现原理
千万不要一上来数据绑定,diff算法啊,什么什么的?我们不仅仅是在产品设计的时候需要同理心,我们在开发的时候更需要同理心,如果你一个vue的使用者你希望作者提供什么样的能,如果你是vue的作者你应该如果设计这个框架,上来就实现只能说面试准备得不错,但在真正的工作场景下面的设计,开发,决策能力肯定是欠缺的。业务复杂的时候是需要我们强大的设计能力,逻辑思维,决策方向,只有这些得到成长,才能把活干的漂亮。简单的业务程序员是肯定有职业生涯危机的,也许你在南京找个外包或者其他公司能拿到20K,但是如果不持续学习,正在去了解业务和技术的结合点,我相信一定是被淘汰的。
这和我之前看的《架构师修炼之道》的观点有些不谋而合。架构的过程其实就是权衡的过程,决策的过程,性能,维护性,扩展性,时间,业务等等,我们每天其实都是在决策中,权衡中。这是我向所有开发和产品,测试传达的最主要的一个观点,大家一定要沟通,了解技术,去寻找客户价值和当前开发的结合点。当前产品的通病不关注价值,沉醉于自嗨;技术的通病是一定要完美主义,技术不能有瑕疵;项目的通病是管理与节奏;测试通病是过于在意细节。最好的状态应该是最短时间可交付的最大客户价值,所以说UI不重要,接受技术缺陷和创新收益并存,不过分关心细节抓住核心重点。如果一个火箭能载入上天,它的安全最重要,它的厕所功能不重要,它的回收不重要,等真正意思安全,稳定之后才可以考虑这些。
指令式编程(英语:Imperative programming);是一种描述电脑所需作出的行为的编程典范。几乎所有电脑的硬件都是指令式工作;几乎所有电脑的硬件都是能执行机器代码,而机器代码是使用指令式的风格来写的。较高端的指令式编程语言使用变量和更复杂的语句,但仍依从相同的典范。菜谱和行动清单,虽非计算机程序,但与指令式编程有相似的风格:每步都是指令。因为指令式编程的基础观念,不但概念上比较熟悉,而且较容易具体表现于硬件,所以大部分的编程语言都是指令式的。
<!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>Document</title>
</head>
<body>
<div id="test"></div>
<script>
const test = document.getElementById("test")
test.innerHTML = "hello world!"
test.addEventListener("click", ()=>{
alert("hello world!");
})
</script>
</body>
</html>上面这段js是一种表现形式,使用js获取dom元素,设置dom元素的文本,绑定点击事件,从我的描述可以看出这是典型的面向执行过程的一种实现。
声明式编程(英语:Declarative programming)或译为声明式编程,是对与命令式编程不同的编程范型的一种合称。它们建造计算机程序的结构和元素,表达计算的逻辑而不用描述它的控制流程。
<div id="app" @click="hello">
{{ message }}
</div>var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
hello:()=> {alert('Hello Vue!')}
}
})从上面的代码可以看出,如果我将上面的代码直接copy到一个html是不能正常执行的,需要经过vue这样的框架可以帮我们实现,绑定元素的文本内容,绑定点击事件。不管怎么说其实vue的底层实现依然是调用js的原生能力,使用命令式的方式实现编程效果的。说白了vue底层还是调用命令式去实现的。经过vue转一道性能必然是变低的,但是我们为什么还是使用vue呢?如果一个复杂项目使用js进行数据渲染那肯定代码是杂乱无章的,但是如果使用vue的组件开发项目时清晰可见的。所有说使用vue无非是编程的体验和项目的可维护性项目是有提升的。这就是取舍权衡之道,说白了vue提高了代码可维护性的下限。
特性\范式 | 命令式 | 声明式 |
|---|---|---|
性能 | 高 | 低 |
可维护 | 差 | 高 |
表现 | 面向过程 | 面向结果 |
纯js操作消耗时间 = X
vue实现消耗时间 = X + Y
Y:vue中算法本身逻辑消耗的时间命令式项目复杂度增长 = logaX (a>b>1)
声明式项目复杂度增长 = logbX (a>b>1)
声明式增长更缓慢
虚拟dom使用js对象的方式以及diff算法实现的一种更新页面的方式
const VirtualDom = {
tag:"div",
children:[
{
tag:"span"
}
]
}原生的方式还是最直接的命令式的操作dom实现渲染
特性\实现方式 | 虚拟dom | 原生js |
|---|---|---|
可阅读性 | 高 | 低 |
可维护性 | 高 | 低 |
性能 | 低 | 高 |
运行时正如上面的虚拟dom的js对象,使用一个render函数将这个js对象实现渲染, 实现方式可以简单理解为一个递归循环的过程。
// 虚拟dom对象
const VirtualDom = {
tag:"div",
children:[
{
tag:"span"
}
]
}
// 渲染函数
function render(virtualDom,container){
// 创建元素
const el = document.creatElement(virtualDom.tag)
// 递归循环所有的子节点
virtualDom.children.forEach((item)=>{
render(item,el)
})
// 将创建的元素添加到容器当中
container.appendChild(el)
}编译时正如我们所见的vue和angular都是使用模板语言创建组件的,那就需要一个将模板转换为js对象的这样过程,我们需要实现一个编译器将模板对象转换为js对象,实现的核心原理还是基于ast相关的,如果有兴趣的可以看一下《编译原理》这本书籍。这是我使用golang去编译less的一个小demo大家可以阅读一下。
<template>
<div>
<span>
</span>
</div>
</template>里面有两个知识点一个是模板语言和jsx的场景分析,一个模板语言解析实践。
// 使用 "interpolate" 分隔符创建编译模板
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'
// 使用 HTML "escape" 转义数据的值
var compiled = _.template('<b><%- value %></b>');
compiled({ 'value': '<script>' });
// => '<b><script></b>'
// 使用 "evaluate" 分隔符执行 JavaScript 和 生成HTML代码
var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
compiled({ 'users': ['fred', 'barney'] });
// => '<li>fred</li><li>barney</li>'
// 在 "evaluate" 分隔符中使用内部的 `print` 函数
var compiled = _.template('<% print("hello " + user); %>!');
compiled({ 'user': 'barney' });
// => 'hello barney!'
// 使用 ES 分隔符代替默认的 "interpolate" 分隔符
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'
// 使用自定义的模板分隔符
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
var compiled = _.template('hello {{ user }}!');
compiled({ 'user': 'mustache' });
// => 'hello mustache!'
// 使用反斜杠符号作为纯文本处理
var compiled = _.template('<%= "\\<%- value %\\>" %>');
compiled({ 'value': 'ignored' });
// => '<%- value %>'
// 使用 `imports` 选项导入 `jq` 作为 `jQuery` 的别名
var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
compiled({ 'users': ['fred', 'barney'] });
// => '<li>fred</li><li>barney</li>'
// 使用 `sourceURL` 选项指定模板的来源URL
var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
compiled(data);
// => 在开发工具的 Sources 选项卡 或 Resources 面板中找到 "greeting.jst"
// 使用 `variable` 选项确保在编译模板中不声明变量
var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
compiled.source;
// => function(data) {
// var __t, __p = '';
// __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
// return __p;
// }
// 使用 `source` 特性内联编译模板
// 便以查看行号、错误信息、堆栈
fs.writeFileSync(path.join(cwd, 'jst.js'), '\
var JST = {\
"main": ' + _.template(mainText).source + '\
};\
');有这些前人的经验,我们的道路会更佳简单一点。编程设计最大的原则就是借鉴之前的设计与方案。由此可见我们使用编译时的方式,将模板语言转换为js对象,同时可以加入我们自己定义新的特性,在代码的表达力,理解力(模板理解简单)以及易用性(快捷的v-for等等方式)上面我们得到很好的提升。
世间万物之道其实就是权衡决策,我记得有一本说写就是随着经济的发展,越来越少的人不选择去当流氓了,因为流氓带来的收益和风险以及完全不成正比了。辛苦一点从事外卖行业也是能过万的,没啥法律风险,可进入门槛低。人们不仅仅是在生活中做权衡选择,工作也是,然后再到每个一个软件的架构设计,易用性和维护性跟代码执行的效率有冲突,需要我们做出选择,架构的高可用和迭代的时间是冲突的,我们不可能设计出来一个完美的架构,也不可能同时满足产品的需求以及中长期的技术规划。这就是要我们在趋于完美的道路不断抉择和权衡利弊。
最后回到vue3本身,vue3是一个声明式的,兼顾运行时和编译时的前端框架。
做一个框架也好,写一个js库也好,不仅仅是关注与功能本身。我写了一个js库提供了哪些api,使用者调用哪些api,就完事了。
比如说我调用next的方法,结果却是pre这显然是不符合预期的,比如说我调用neet方法,我根本不知道这是什么意思。
这就是我们常说的最少知识原则,比如我调用B的api报错,提供了方法我调用为啥报错,发现必须先调用A的api,在api设计的时候没有注意私有方法和公用方法,已经调用顺序的设计是让使用者十分痛苦的。
首先还是要区分状态,是开发状态还是运行状态,开发状态一定要有友好的代码提升,建议区分场景console.warn,对于开发者进行友好的开发辅助,一定要慎用报错error,在人们期望中一定是希望第三方库使用的友好性和简易性的。如果阻塞执行是一件我认为很糟糕的事情。
前端web的场景还是比较简单的,后端经常使用分布式和集群的两个方案管理资源和代码部署方式,前端不一样刚入门的时候前端性能优化里面的第一课就是请求合并资源压缩(其他的就不在赘述了),更少的网络资源请求必然是前端要考虑的。常见的cdn中经常都会有生产,压缩版这样区分。vue也一直标榜自己库小的特点。
更少的网络请求,需要更少的代码资源,在尽可能少的代码量下完成网站的正常运行。
关键字dead code
//a.js 中
export function foo(){
console.log("foo")
}
export function bar(){
console.log("bar")
}
//b.js 中
import {foo} from './a.js'
foo()由上面的代码我们可以清楚的看到a.js中的bar的方法从未执行过,也从未被引入过很显然这样的代码就没有必要打包在资源当中。
function foo(){
const a = 1
console.log("foo")
}从上面的代码我们也可以看到,const a = 1,但是在整个函数体根本没有使用,其实打包时也不希望加入构建产物中
function foo(){
console.log("foo")
return
console.log("我是大帅哥")
}从上面的代码我们可以看出console.log("我是大帅哥"),这打印是永远不会执行,其实也可以不加人构建产物中
关键词-副作用,js有两个特性运行时和全局变。
var a = 1
function foo(){
a = 10
}
foo()我们表面上执行了函数foo,可是它去改变了全局的变量,这个改变也是在函数执行时才能很好的发现,这个就比较痛苦,对于我们执行摇树优化的话会产生影响。js本身就是也是有对象引用的这样特性,如果操作不大,就会摇掉我们本来有用的树叶。当场景比较复杂的时候,约定就是一种比较交单解决问题的方式。使用这样的注释就是很好的解决方案。成长最好的方式就是看文档webpck
var Button$1 = /*#__PURE__*/ withAppProvider()(Button);设计原则 场景决定设计:js库的使用方式就决定了我们使用哪一种打包方式。
开发环境不需要压缩代码,保留开发友好提示。
生产环境需要压缩代码,去除无效代码,还需要根据引入的方式进行不同的打包形式。第一层的区分例如运行在nodejs中还是浏览器中,第二层在浏览器中是引入标签直接实现还是使用模块化的方式。因为新增的<script type = 'module'>方式可以将代码以模块的方式引入。
//常见的使用方式
//自执行函数这种方式,只要资源引入即可使用
(()=>{})()
// 浏览器模块化引入
// a.js
export function foo(){
console.log("foo")
}
// b.js
import { foo } from './a.js';里面有一些核心的概念还是要自己熟知的,模块的方式,commonjs,amd,esModule,这几个概念很重要也是大家在开发是需要注意的。
重要内容
export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。为什么重要?只有使用es6模块的方式才能进行摇树优化切记!!!
例如在vue3中建议和推荐使用composition API的方式构建应用,在组合api中其实vue2的很多option的能力已经迁入其他,如果不需要使用vue2的option的方式,那么就可以使用_VUE_OPTIONS_API_这个配置项进行配置,这样就又可以摇树优化一部分不会被使用的代码。
虽然很多功能很小很琐碎,但是我们依然可以看出一个开源项目对应前端性能优化的标准的不断要求,这是我们需要学习的,一定要有这样极致的工匠精神,每天所有业务也是不能因为自己的懒惰或者后端,产品,测试等等的理由去放弃自己对技术的苛求。永远是那一句话,工作是老板的,但技术是自己的,在公司使用最好,最极致的技术,是和公司双赢的局面,最怕是业务做得很差,每天要去支撑,然后自己也没有学到东西,混沌度日,最后压垮你的不是你的工作而是你自己产生的技术负债。总之是啥也没学到,自己还累。
一个良好的封装时减少代码量和阅读性的重要实现。在js运行和方法调用尤其是大型前端项目统一的错误处理,统一的接口情况,统一的日志打印都是必不可少的。这是在项目初期就要想好的规划,如果没有好好规划,将来突然想对前端的日志做上传,本地化等等个性化的需求时就是毁灭性的工作量。前期的模块封装大家一定要有注意,不仅仅是方便以后个性化的需求,在监控服务,各种全链路生态中都是依据之前的统一模块的。一定要有远瞻性,不管项目如何变化,稳坐钓鱼台。
错误封装的实现方式还是比较简单粗暴一点的。
let handleError = null
const utils = {
foo:(fu)=>{
callWithErrorHand(fu)
},
// 错误处理函数
registerErrorHandler(fu){
handleError = fu
}
}
function callWithErrorHand(fu){
if(typeOf fu == "Fuction"){
try{
fu()
}cacth (e){
handleError(e)
}
}else{
console.warn("not a function")
}
}
// 注册异常
utils.registerErrorHandler = (e)=>{
console.log(e)
}
// 调用方式
utils.foo(()=>{})在vue中我们常见的使用方式如下
app.config.errorHandler = ()=>{
}这样的统一处理方式也是我们场景的面向切片的一种处理方式,这种设计思想大家一定要具备。
其实我总结一下,其实是利用了ts本身类型特以及泛型能力,好像自己变成了一个粘贴怪,没关系我也没人家写的那么好。
大家一定要掌握摇树优化的机制,判断哪些场景下面是dead code,关注前端模块化的方式有哪些,一个好的框架一定是注重细节的框架,在对的事情上面要精益求精,统一封装是提高代码水平的第一阶段。
核心思路是很重要的,一个项目也是有核心思路的,我觉得可以分为两个部分,一个是业务的核心思路,一个是技术的核心思路。举两个例子,我之前就做一家公司叫做云帐房,业务的核心思路企业报税的云化支持,技术核心多业务模块切分与关联。现在就职于云学堂,都是带个云字,我个人的认知业务的核心认知应该是企业培训场景的云化支持,技术的核心思路高可用的直播服务。 回归到vue3本身,我认为vue是一个声明式的UI框架。
vue 本身最大的特点之一就是他的模板方式,我们且不去评论模板方式和jsx的方式哪一种是最佳的前端UI表现形式,我们稍微总结一下异同剩下的公道自在人心了。
模板语言特点:使用简单使用v-for就完成了一个数组的遍历渲染。静态编译优化空间大,毕竟已经静态模板编译器可以根据里面的内容做编译能力上面提升,这是vue3新增的一个加强。弊端也比较明显,ts支持是需要转换的,需要把模板转ts再支持。
jsx特点: 官方表述更符合js本来的思维方式,更好的代码支持。弊端虽然已经做了性能上面的优化,但是基于运行时的状态其实优化有限。
这是都是那些大佬的观点,都能理解,我不引战,我不偏向,我觉得都ok。到项目里面还是看你本身代码的可维护性怎么样,自己代码水平不行,写啥都不行。
vue本身不仅仅支持模板语法,其实还支持使用虚拟dom渲染UI,这是我面试时比较喜欢问的问题。
import {h} from "vue"
exprot default{
render(){
return h('h1',{onClick:()=>{}})
}
}其实就是h 这个方法之前是更底层的方式,现在作者将其暴露出来了。