路由的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模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。

vue和react的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件
2. 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
3. 数据流动单向,都支持服务器的渲染SSR
4. 都有支持native的方法,react有React native, vue有wexx

=> 不同点:

 1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的2.数据渲染:大规模的数据渲染,react更快3.使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目4.开发风格:react推荐做法jsx + inline style把html和css都写在js了vue是采用webpack + vue-loader单文件组件格式,html, js, css同一个文件

虚拟 DOM 实现原理?

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

Vue.js的template编译

简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点),详细步骤如下:

首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。

然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)

Vue是如何收集依赖的?

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑,如下所示∶

function defieneReactive (obj, key, val){const dep = new Dep();...Object.defineProperty(obj, key, {...get: function reactiveGetter () {if(Dep.target){dep.depend();...}return val}...})
}

以上只保留了关键代码,主要就是 const dep = new Dep()实例化一个 Dep 的实例,然后在 get 函数中通过 dep.depend() 进行依赖收集。 (1)Dep Dep是整个依赖收集的核心,其关键代码如下:

class Dep {static target;subs;constructor () {...this.subs = [];}addSub (sub) {this.subs.push(sub)}removeSub (sub) {remove(this.sub, sub)}depend () {if(Dep.target){Dep.target.addDep(this)}}notify () {const subs = this.subds.slice();for(let i = 0;i < subs.length; i++){subs[i].update()}}
}

Dep 是一个 class ,其中有一个关 键的静态属性 static,它指向了一个全局唯一 Watcher,保证了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的管理,再看看 Watcher 的相关代码∶

(2)Watcher

class Watcher {getter;...constructor (vm, expression){...this.getter = expression;this.get();}get () {pushTarget(this);value = this.getter.call(vm, vm)...return value}addDep (dep){...dep.addSub(this)}...
}
function pushTarget (_target) {Dep.target = _target
}

Watcher 是一个 class,它定义了一些方法,其中和依赖收集相关的主要有 get、addDep 等。

(3)过程

在实例化 Vue 时,依赖收集的相关过程如下∶
初 始 化 状 态 initState , 这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 部分便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher ,进入 Watcher 中,便会执行 this.get() 方法,

updateComponent = () => {vm._update(vm._render())
}
new Watcher(vm, updateComponent)

get 方法中的 pushTarget 实际上就是把 Dep.target 赋值为当前的 watcher。

this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 方法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this)。刚才 Dep.target 已经被赋值为 watcher,于是便会执行 addDep 方法,然后走到 dep.addSub() 方法,便将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便已经完成了一个依赖收集的过程。

MVVM、MVC、MVP的区别

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化开发效率。

在开发单页面应用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简单项目时,可能看不出什么问题,如果项目变得复杂,那么整个文件就会变得冗长、混乱,这样对项目开发和后期的项目维护是非常不利的。

(1)MVC

MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。

(2)MVVM

MVVM 分为 Model、View、ViewModel:

  • Model代表数据模型,数据和业务逻辑都在Model层中定义;
  • View代表UI视图,负责数据的展示;
  • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM。

(3)MVP

MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑。

vue 中使用了哪些设计模式

1.工厂模式 - 传入参数即可创建实例

虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode

2.单例模式 - 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉

3.发布-订阅模式 (vue 事件机制)

4.观察者模式 (响应式数据原理)

5.装饰模式: (@装饰器的用法)

6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略

参考:前端vue面试题详细解答

为什么vue组件中data必须是一个函数?

对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

Vue中封装的数组方法有哪些,其如何实现页面更新

在Vue中,对响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中的变化。 那Vue是如何实现让这些数组方法实现元素的实时更新的呢,下面是Vue中对这些方法的封装:

// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 需要进行功能拓展的方法
const methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse"
];/** * Intercept mutating methods and emit events */
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) {// push、unshift会新增索引,所以要手动observercase "push":case "unshift":inserted = args;break;// splice方法,如果传入了第三个参数,也会有索引加入,也要手动observer。case "splice":inserted = args.slice(2);break;}// if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听// notify changeob.dep.notify();// 通知依赖更新// 返回原生数组方法的执行结果return result;});
});

简单来说就是,重写了数组中的那些原生方法,首先获取到这个数组的__ob__,也就是它的Observer对象,如果有新的值,就调用observeArray继续对新的值观察变化(也就是通过target__proto__ == arrayMethods来改变了数组实例的型),然后手动调用notify,通知渲染watcher,执行update。

vue-cli 工程常用的 npm 命令有哪些

  • 下载 node_modules 资源包的命令:
npm install
  • 启动 vue-cli 开发环境的 npm命令:
npm run dev
  • vue-cli 生成 生产环境部署资源 的 npm命令:
npm run build
  • 用于查看 vue-cli 生产环境部署资源文件大小的 npm命令:
npm run build --report

在浏览器上自动弹出一个 展示 vue-cli 工程打包后 app.jsmanifest.jsvendor.js 文件里面所包含代码的页面。可以具此优化 vue-cli 生产环境部署的静态资源,提升 页面 的加载速度

简述 mixin、extends 的覆盖逻辑

(1)mixin 和 extends mixin 和 extends均是用于合并、拓展组件的,两者均通过 mergeOptions 方法实现合并。

  • mixins 接收一个混入对象的数组,其中混入对象可以像正常的实例对象一样包含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
  • extends 主要是为了便于扩展单文件组件,接收一个对象或构造函数。

(2)mergeOptions 的执行过程

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
  • 对未合并的选项,进行判断
if (!child._base) {if (child.extends) {parent = mergeOptions(parent, child.extends, vm);}if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm);}}
}
  • 合并处理。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里。
  • 返回合并结果 options。

Vue项目本地开发完成后部署到服务器后报404是什么原因呢

如何部署

前后端分离开发模式下,前后端是独立布署的,前端只需要将最后的构建物上传至目标服务器的web容器指定的静态目录下即可

我们知道vue项目在构建后,是生成一系列的静态文件

常规布署我们只需要将这个目录上传至目标服务器即可


web容器跑起来,以nginx为例

server {listen  80;server_name  www.xxx.com;location / {index  /data/dist/index.html;}
}

配置完成记得重启nginx

// 检查配置是否正确
nginx -t // 平滑重启
nginx -s reload

操作完后就可以在浏览器输入域名进行访问了

当然上面只是提到最简单也是最直接的一种布署方式

什么自动化,镜像,容器,流水线布署,本质也是将这套逻辑抽象,隔离,用程序来代替重复性的劳动,本文不展开

404问题

这是一个经典的问题,相信很多同学都有遇到过,那么你知道其真正的原因吗?

我们先还原一下场景:

  • vue项目在本地时运行正常,但部署到服务器中,刷新页面,出现了404错误

先定位一下,HTTP 404 错误意味着链接指向的资源不存在

问题在于为什么不存在?且为什么只有history模式下会出现这个问题?

为什么history模式下有问题

Vue是属于单页应用(single-page application)

SPA是一种网络应用程序或网站的模型,所有用户交互是通过动态重写当前页面,前面我们也看到了,不管我们应用有多少页面,构建物都只会产出一个index.html

现在,我们回头来看一下我们的nginx配置

server {listen  80;server_name  www.xxx.com;location / {index  /data/dist/index.html;}
}

可以根据 nginx 配置得出,当我们在地址栏输入 www.xxx.com 时,这时会打开我们 dist 目录下的 index.html 文件,然后我们在跳转路由进入到 www.xxx.com/login

关键在这里,当我们在 website.com/login 页执行刷新操作,nginx location 是没有相关配置的,所以就会出现 404 的情况

为什么hash模式下没有问题

router hash 模式我们都知道是用符号#表示的,如 website.com/#/login, hash 的值为 #/login

它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对服务端完全没有影响,因此改变 hash 不会重新加载页面

hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 website.com/#/login 只有 website.com 会被包含在请求中 ,因此对于服务端来说,即使没有配置location,也不会返回404错误

解决方案

看到这里我相信大部分同学都能想到怎么解决问题了,

产生问题的本质是因为我们的路由是通过JS来执行视图切换的,

当我们进入到子路由时刷新页面,web容器没有相对应的页面此时会出现404

所以我们只需要配置将任意页面都重定向到 index.html,把路由交由前端处理

nginx配置文件.conf修改,添加try_files $uri $uri/ /index.html;

server {listen  80;server_name  www.xxx.com;location / {index  /data/dist/index.html;try_files $uri $uri/ /index.html;}
}

修改完配置文件后记得配置的更新

nginx -s reload

这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件

为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面

const router = new VueRouter({mode: 'history',routes: [{ path: '*', component: NotFoundComponent }]
})

父子组件生命周期调用顺序(简单)

渲染顺序:先父后子,完成顺序:先子后父

更新顺序:父更新导致子更新,子更新完成后父

销毁顺序:先父后子,完成顺序:先子后父

SPA首屏加载速度慢的怎么解决

一、什么是首屏加载

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

首屏加载可以说是用户体验中最重要的环节

关于计算首屏时间

利用performance.timing提供的数据:

通过DOMContentLoad或者performance来计算出首屏时间

// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{name: "first-contentful-paint",entryType: "paint",startTime: 507.80000002123415,duration: 0,
};

二、加载慢的原因

在页面渲染的过程,导致加载速度慢的因素可能如下:

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

三、解决方案

常见的几种SPA首屏优化方式

  • 减小入口文件积
  • 静态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR

1. 减小入口文件体积

常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

vue-router配置路由的时候,采用动态加载路由的形式

routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')
]

以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件

2. 静态资源本地缓存

后端返回资源问题:

  • 采用HTTP缓存,设置Cache-ControlLast-ModifiedEtag等响应头
  • 采用Service Worker离线缓存

前端合理利用localStorage

3. UI框架按需加载

在日常使用UI框架,例如element-UI、或者antd,我们经常性直接引用整个UI

import ElementUI from 'element-ui'
Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)

4. 组件重复打包

假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

解决方案:在webpackconfig文件中,修改CommonsChunkPlugin的配置

minChunks: 3

minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

5. 图片资源的压缩

图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素

对于所有的图片资源,我们可以进行适当的压缩

对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。

6. 开启GZip压缩

拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js中引入并修改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')configureWebpack: (config) => {if (process.env.NODE_ENV === 'production') {// 为生产环境修改配置...config.mode = 'production'return {plugins: [new CompressionPlugin({test: /\.js$|\.html$|\.css/, //匹配文件名threshold: 10240, //对超过10k的数据进行压缩deleteOriginalAssets: false //是否删除原文件})]}}

在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用

const compression = require('compression')
app.use(compression())  // 在其他中间件使用之前调用

7. 使用SSR

SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器

从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染

四、小结

减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化页面渲染优化

下图是更为全面的首屏优化的方案

大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化

Vue项目性能优化-详细

Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM;但 Vue 项目中仍然存在项目首屏优化、Webpack 编译配置优化等问题,所以我们仍然需要去关注 Vue 项目性能方面的优化,使项目具有更高效的性能、更好的用户体验

代码层面的优化

1. v-if 和 v-show 区分使用场景

  • v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
  • v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS displaynone/block属性进行切换。
  • 所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景

2. computed 和 watch 区分使用场景

  • computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
  • watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作

运用场景:

  • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
  • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的

3. v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

  • v-for 遍历必须为 item 添加 key

    • 在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff
  • v-for 遍历避免同时使用 v-if
    • vue2.xv-forv-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性

推荐:

<ul><liv-for="user in activeUsers":key="user.id">{{ user.name }}</li>
</ul>
computed: {activeUsers: function () {return this.users.filter(function (user) {return user.isActive})}
}

不推荐:

<ul><liv-for="user in users"v-if="user.isActive":key="user.id">{{ user.name }}</li>
</ul>

4. 长列表性能优化

Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了

export default {data: () => ({users: {}}),async created() {const users = await axios.get("/api/users");this.users = Object.freeze(users);}
};

5. 事件的销毁

Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListener 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:

created() {addEventListener('click', this.click, false)
},
beforeDestroy() {removeEventListener('click', this.click, false)
}

6. 图片资源懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vuevue-lazyload 插件

npm install vue-lazyload --save-dev

在入口文件 man.js 中引入并使用

import VueLazyload from 'vue-lazyload'Vue.use(VueLazyload)// 或者添加自定义选项
Vue.use(VueLazyload, {preLoad: 1.3,error: 'dist/error.png',loading: 'dist/loading.gif',attempt: 1
})

vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示

<img v-lazy="/static/img/1.png">

以上为 vue-lazyload 插件的简单使用,如果要看插件的更多参数选项,可以查看 vue-lazyload 的 github 地址(opens new window)

7. 路由懒加载

Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来

路由懒加载:

const Foo = () => import('./Foo.vue')
const router = new VueRouter({routes: [{ path: '/foo', component: Foo }]
})

8. 第三方插件的按需引入

我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 babel-plugin-component ,然后可以只引入需要的组件,以达到减小项目体积的目的。以下为项目中引入 element-ui 组件库为例

npm install babel-plugin-component -D

.babelrc 修改为:

{"presets": [["es2015", { "modules": false }]],"plugins": [["component",{"libraryName": "element-ui","styleLibraryName": "theme-chalk"}]]
}

main.js 中引入部分组件:

import Vue from 'vue';
import { Button, Select } from 'element-ui';Vue.use(Button)
Vue.use(Select)

9. 优化无限列表性能

如果你的应用存在非常长或者无限滚动的列表,那么需要采用虚拟列表的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。 你可以参考以下开源项目 vue-virtual-scroll-list (opens new window) 和 vue-virtual-scroller (opens new window)来优化这种无限列表的场景的

10. 服务端渲染 SSR or 预渲染

服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。

  • 如果你的项目的 SEO首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO
  • 如果你的 Vue 项目只需改善少数营销页面(例如 //about/contact 等)的 SEO,那么你可能需要预渲染,在构建时简单地生成针对特定路由的静态 HTML 文件。 优点是设置预渲染更简单 ,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin (opens new window) 就可以轻松地添加预渲染

Webpack 层面的优化

1. Webpack 对图片进行压缩

对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片

npm install image-webpack-loader --save-dev
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,use:[{loader: 'url-loader',options: {limit: 10000,name: utils.assetsPath('img/[name].[hash:7].[ext]')}},{loader: 'image-webpack-loader',options: {bypassOnDebug: true,}}]
}

2. 减少 ES6 转为 ES5 的冗余代码

Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如下面的 ES6 代码

class HelloWebpack extends Component{...}

这段代码再被转换成能正常运行的 ES5 代码时需要以下两个辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法

在默认情况下, Babel 会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过 require('babel-runtime/helpers/createClass') 的方式导入,这样就能做到只让它们出现一次。babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减小 babel 编译出来的代码的文件大小

npm install babel-plugin-transform-runtime --save-dev

修改 .babelrc 配置文件为:

"plugins": ["transform-runtime"
]

3. 提取公共代码

如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

  • 相同的资源被重复加载,浪费用户的流量和服务器的成本。
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。

所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin,我们在项目中 CommonsChunkPlugin 的配置如下:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({name: 'vendor',minChunks: function(module, count) {return (module.resource &&/\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0);}
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({name: 'manifest',chunks: ['vendor']
})

4. 模板预编译

  • 当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。
  • 预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。
  • 如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader (opens new window),它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数

5. 提取组件的 CSS

当使用单文件组件时,组件内的 CSS 会以 style 标签的方式通过 JavaScript 动态注入。这有一些小小的运行时开销,如果你使用服务端渲染,这会导致一段 “无样式内容闪烁 (fouc) ” 。将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存

6. 优化 SourceMap

我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的

SourceMap 的可选值如下(+ 号越多,代表速度越快,- 号越多,代表速度越慢, o 代表中等速度)

  • 开发环境推荐: cheap-module-eval-source-map
  • 生产环境推荐: cheap-module-source-map

原因如下:

  • cheap: 源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息;
  • module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module配置;
  • soure-mapsource-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性;
  • eval-source-mapeval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。

7. 构建结果输出分析

Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。接下来讲解我们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer

if (config.build.bundleAnalyzerReport) {var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成分析报告如下

基础的 Web 技术优化

1. 开启 gzip 压缩

gzipGNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,zip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右

以下我们以服务端使用我们熟悉的 express 为例,开启 gzip 非常简单,相关步骤如下:

npm install compression --save
var compression = require('compression');
var app = express();
app.use(compression())

重启服务,观察网络面板里面的 response header,如果看到如下红圈里的字段则表明 gzip 开启成功

Nginx开启gzip压缩

#是否启动gzip压缩,on代表启动,off代表开启
gzip  on;#需要压缩的常见静态资源
gzip_types text/plain application/javascript   application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;#由于nginx的压缩发生在浏览器端而微软的ie6很坑爹,会导致压缩后图片看不见所以该选
项是禁止ie6发生压缩
gzip_disable "MSIE [1-6]\.";#如果文件大于1k就启动压缩
gzip_min_length 1k;#以16k为单位,按照原始数据的大小以4倍的方式申请内存空间,一般此项不要修改
gzip_buffers 4 16k;#压缩的等级,数字选择范围是1-9,数字越小压缩的速度越快,消耗cpu就越大
gzip_comp_level 2;

要想配置生效,记得重启nginx服务

nginx -t
nginx -s reload

2. 浏览器缓存

为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)

3. CDN 的使用

浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率

4. 使用 Chrome Performance 查找性能瓶颈

ChromePerformance 面板可以录制一段时间内的 js 执行细节及时间。使用 Chrome 开发者工具分析页面性能的步骤如下。

  • 打开 Chrome 开发者工具,切换到 Performance 面板
  • 点击 Record 开始录制
  • 刷新页面或展开某个节点
  • 点击 Stop 停止录制

Vue模版编译原理知道吗,能简单说一下吗?

简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

  • 生成AST树
  • 优化
  • codegen

首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的AST树转换为可执行的代码

SPA、SSR的区别是什么

我们现在编写的VueReactAngular应用大多数情况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,由于良好的用户体验逐渐成为主流的开发模式。但同时也会有首屏加载时间长,SEO不友好的问题,因此有了SSR,这也是为什么面试中会问到两者的区别

  1. SPA(Single Page Application)即单页面应用。一般也称为 客户端渲染(Client Side Render), 简称 CSRSSR(Server Side Render)即 服务端渲染。一般也称为 多页面应用(Mulpile Page Application),简称 MPA
  2. SPA应用只会首次请求html文件,后续只需要请求JSON数据即可,因此用户体验更好,节约流量,服务端压力也较小。但是首屏加载的时间会变长,而且SEO不友好。为了解决以上缺点,就有了SSR方案,由于HTML内容在服务器一次性生成出来,首屏加载快,搜索引擎也可以很方便的抓取页面信息。但同时SSR方案也会有性能,开发受限等问题
  3. 在选择上,如果我们的应用存在首屏加载优化需求,SEO需求时,就可以考虑SSR
  4. 但并不是只有这一种替代方案,比如对一些不常变化的静态网站,SSR反而浪费资源,我们可以考虑预渲染(prerender)方案。另外nuxt.js/next.js中给我们提供了SSG(Static Site Generate)静态网站生成方案也是很好的静态站点解决方案,结合一些CI手段,可以起到很好的优化效果,且能节约服务器资源

内容生成上的区别:

SSR

SPA

部署上的区别

Vue中常见性能优化

编码优化

  1. 使用v-show复用DOM:避免重复创建组件
<template><div class="cell"><!-- 这种情况用v-show复用DOM,比v-if效果好 --><div v-show="value" class="on"><Heavy :n="10000"/></div><section v-show="!value" class="off"><Heavy :n="10000"/></section></div>
</template>
  1. 合理使用路由懒加载、异步组件,有效拆分App尺寸,访问时才异步加载
const router = createRouter({routes: [// 借助webpack的import()实现异步组件{ path: '/foo', component: () => import('./Foo.vue') }]
})
  1. keep-alive缓存页面:避免重复创建组件实例,且能保留缓存组件状态
<router-view v-slot="{ Component }"><keep-alive><component :is="Component"></component></keep-alive>
</router-view>
  1. v-oncev-memo:不再变化的数据使用v-once
<!-- single element -->
<span v-once>This will never change: {{msg}}</span>
<!-- the element have children -->
<div v-once><h1>comment</h1><p>{{msg}}</p>
</div>
<!-- component -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` directive -->
<ul><li v-for="i in list" v-once>{{i}}</li>
</ul>

按条件跳过更新时使用v-momo:下面这个列表只会更新选中状态变化项

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"><p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p><p>...more child nodes</p>
</div>
  1. 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
<recycle-scrollerclass="items":items="items":item-size="24"
><template v-slot="{ item }"><FetchItemView:item="item"@vote="voteItem(item)"/></template>
</recycle-scroller>
  1. 防止内部泄漏,组件销毁后把全局变量和事件销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件
export default {created() {this.timer = setInterval(this.refresh, 2000)},beforeUnmount() {clearInterval(this.timer)}
}
  1. 图片懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载

<!-- 参考 https://github.com/hilongjw/vue-lazyload -->
<img v-lazy="/static/img/1.png">
  1. 滚动到可视区域动态加载

https://tangbc.github.io/vue-virtual-scroll-list(opens new window)

  1. 第三方插件按需引入:(babel-plugin-component

element-plus这样的第三方组件库可以按需引入避免体积太大

import { createApp } from 'vue';
import { Button, Select } from 'element-plus';
​
const app = createApp()
app.use(Button)
app.use(Select)
  1. 服务端渲染:SSR

如果SPA应用有首屏渲染慢的问题,可以考虑SSR

以及下面的其他方法

  • 不要将所有的数据都放在data中,data中的数据都会增加gettersetter,会收集对应的watcher
  • v-for 遍历为 item 添加 key
  • v-for 遍历避免同时使用 v-if
  • 区分 computedwatch 的使用
  • 拆分组件(提高复用性、增加代码的可维护性,减少不必要的渲染 )
  • 防抖、节流

用户体验

  • app-skeleton 骨架屏
  • pwa serviceworker

SEO优化

  • 预渲染插件 prerender-spa-plugin
  • 服务端渲染 ssr

打包优化

  • Webpack 对图片进行压缩
  • 使用 cdn 的方式加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • 优化 SourceMap
  • 构建结果输出分析,利用 webpack-bundle-analyzer 可视化分析工具

基础的 Web 技术的优化

  • 服务端 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈

Vue项目中你是如何解决跨域的呢

一、跨域是什么

跨域本质是浏览器基于同源策略的一种安全手段

同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能

所谓同源(即指在同一个域)具有以下三个相同点

  • 协议相同(protocol)
  • 主机相同(host)
  • 端口相同(port)

反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域

一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。

如果让你从零开始写一个vuex,说说你的思路

思路分析

这个题目很有难度,首先思考vuex解决的问题:存储用户全局状态并提供管理状态API。

  • vuex需求分析
  • 如何实现这些需求

回答范例

  1. 官方说vuex是一个状态管理模式和库,并确保这些状态以可预期的方式变更。可见要实现一个vuex
  • 要实现一个Store存储全局状态
  • 要提供修改状态所需API:commit(type, payload), dispatch(type, payload)
  1. 实现Store时,可以定义Store类,构造函数接收选项options,设置属性state对外暴露状态,提供commitdispatch修改属性state。这里需要设置state为响应式对象,同时将Store定义为一个Vue插件
  2. commit(type, payload)方法中可以获取用户传入mutations并执行它,这样可以按用户提供的方法修改状态。 dispatch(type, payload)类似,但需要注意它可能是异步的,需要返回一个Promise给用户以处理异步结果

实践

Store的实现:

class Store {constructor(options) {this.state = reactive(options.state)this.options = options}commit(type, payload) {this.options.mutations[type].call(this, this.state, payload)}
}

vuex简易版

/*** 1 实现插件,挂载$store* 2 实现store*/let Vue;class Store {constructor(options) {// state响应式处理// 外部访问: this.$store.state.***// 第一种写法// this.state = new Vue({//   data: options.state// })// 第二种写法:防止外界直接接触内部vue实例,防止外部强行变更this._vm = new Vue({data: {$$state: options.state}})this._mutations = options.mutationsthis._actions = options.actionsthis.getters = {}options.getters && this.handleGetters(options.getters)this.commit = this.commit.bind(this)this.dispatch = this.dispatch.bind(this)}get state () {return this._vm._data.$$state}set state (val) {return new Error('Please use replaceState to reset state')}handleGetters (getters) {Object.keys(getters).map(key => {Object.defineProperty(this.getters, key, {get: () => getters[key](this.state)})})}commit (type, payload) {let entry = this._mutations[type]if (!entry) {return new Error(`${type} is not defined`)}entry(this.state, payload)}dispatch (type, payload) {let entry = this._actions[type]if (!entry) {return new Error(`${type} is not defined`)}entry(this, payload)}
}const install = (_Vue) => {Vue = _VueVue.mixin({beforeCreate () {if (this.$options.store) {Vue.prototype.$store = this.$options.store}},})
}export default { Store, install }

验证方式

import Vue from 'vue'
import Vuex from './vuex'
// this.$store
Vue.use(Vuex)export default new Vuex.Store({state: {counter: 0},mutations: {// state从哪里来的add (state) {state.counter++}},getters: {doubleCounter (state) {return state.counter * 2}},actions: {add ({ commit }) {setTimeout(() => {commit('add')}, 1000)}},modules: {}
})

前端高频vue面试题合集相关推荐

  1. 2020中高级前端面试题合集

    2020中高级前端面试题合集 "全方位总结一下所遇到的面试题目,与大家共同学习,也是对自己的一次总结" 文章同步到我的公众号<前端小时>,欢迎大家关注! 01 前言 2 ...

  2. 2023年网络安全工程师面试题合集【首发】

    以下为信息安全各个方向涉及的面试题,星数越多代表问题出现的几率越大,祝各位都能找到满意的工作~ [一一帮助安全学习[点我]一一]①网络安全学习路线②20 份渗透测试电子书③安全攻防 357 页笔记④5 ...

  3. 2021金三最新自动化测试面试题合集(含答案)!

    金三已经过去了,相信很多小伙伴在为银四做准备,这里给大家整理了一些金三中有被问到的自动化测试面试题合集,希望会小伙伴们有所帮助! 需要面试题合集的小伙伴,可点击这里领取哦! web自动化测试: 1.S ...

  4. 9012年大厂面试题合集:Java技术栈为什么竞争越来越激烈?

    就今年大环境来看,跳槽成功的难度比往年高很多,一个明显的感受:今年的Java技术栈面试,无论一面还是二面,都特别考验Java程序员的技术功底. 最近有人搜集了93套腾讯.阿里.美团.百度.网易等公司9 ...

  5. Java面试核心知识点(283页)Java面试题合集最新版(485页)

    阿里.腾讯两大互联网企业传来裁员消息,很多人都陷入担心,不安情绪蔓延-- 其实大家应该更冷静和理性地看待大厂裁员.每年三四月都是大厂人员调整期,这个季节是各个公司战略调整.战略规划的一个关键期,肯定会 ...

  6. 大学“电路分析基础”试题合集第四章

    大学"电路分析基础"试题合集第一章 大学"电路分析基础"试题合集第二章 大学"电路分析基础"试题合集第三章 "电路分析基础&quo ...

  7. 大学“电路分析基础”试题合集第六章(文末附PDF文档与Word文档)

    大学"电路分析基础"试题合集第一章 大学"电路分析基础"试题合集第二章 大学"电路分析基础"试题合集第三章 大学"电路分析基础&q ...

  8. 2019年大厂面试题合集:Java架构师技术栈为什么竞争越来越激烈?程序员必看!

    2019年大厂面试题合集:Java架构师技术栈为什么竞争越来越激烈?程序员必看! 就今年大环境来看,跳槽成功的难度比往年高很多,一个明显的感受:今年的Java技术栈面试,无论一面还是二面,都特别考验J ...

  9. 中级java笔试题_Java中级面试题合集

    Java中级面试题合集:1.弹出式选择菜单(Choice)和列表(List)有什么区别 Choice是以一种紧凑的形式展示的,需要下拉才能看到所有的选项.Choice中一次只能选中一个选项.List同 ...

最新文章

  1. Proguard returned with error code 1. See console
  2. 洛谷——P1951 收费站_NOI导刊2009提高(2)
  3. 最新wingide6破解方法(支持Linux),亲测有效,支持python3.0
  4. 【工具】更新arduino最新esp8266库离线安装包3.0.1、ESP32库离线安装包1.0.6,库编译报错解决方法...
  5. 有参组装新转录本cufflinks_转录本组装软件StringTie的使用说明
  6. 把虚拟机装到内存里(打开PScs3只需要2秒)
  7. C++ std::thread 和 std::jthread 使用详解 (含C++20新特性)
  8. java红包雨_Java升职加薪课开发企业年会红包雨场景项目实战视频教程
  9. 扫目录过狗过waf方法
  10. 20140213-面向对象技术概论
  11. Spring Boot 2.2 正式发布,大幅性能提升 Java 13 支持 | CSDN 博文精选
  12. Apache Struts 修复 OGNL 技术中可能存在的 RCE 缺陷
  13. windows下客户端连接上马上会断开连接_zookeeper系列客户端的骚操作amp; Curator使用...
  14. 数据分析和挖掘常用方法
  15. STM32控制0.96寸OLED(4针/4Pin)汉字以及数字,英文显示
  16. Windows视频桌面壁纸实现(libvlc)(类似于wall paper engine效果)
  17. 拼音四线三格图片_一年级拼音总结,请查收
  18. java计算机毕业设计个性化穿搭推荐系统MyBatis+系统+LW文档+源码+调试部署
  19. cml sml区别_资本资产定价模型中cml与sml有什么区别?
  20. 构建云视频平台的七种武器分别包括长生剑、孔雀翎、碧玉刀、多情环、离别钩、霸王枪、拳头,这七种武器分别对应的是什么技术呢?该文将揭晓答案。

热门文章

  1. Docker容器恢复
  2. mysql间隙锁可重入_关于mysql 间隙锁
  3. Python绘图还在用Matplotlib?out了 !发现一款手绘可视化神器!
  4. 郑州轻工业大学(ZZULIOJ) 答案汇总(C)(更新中)
  5. 莫言750万不够买大房子
  6. 世界第一位半机械人出现
  7. MyBatis拦截器介绍
  8. 流行快餐 —— 破解快速时尚产品成功之道
  9. 经典开关控制【Java】语言实现
  10. 高并发访问请求合并学习笔记