vue
谈谈你对 MVVM 的理解
-
MVC 指的是用户操作会请求服务端路由,路由调用对应的控制器来处理,控制器会获取数据,将结果返回前端,页面重新渲染。 MVVM 是 Model-View-ViewModel 模块-视图-双向绑定
-
传统的前端会将数据手动渲染到页面上, MVVM 模式不需要用户操作 dom 元素,将数据绑定在 ViewModel 层,会自动将数据渲染到页面上,视图变化时,会通知 ViewModel 层更新数据, ViewModel 是连接 View 和 Model 的桥梁。
1、说说你对 vue 双向绑定的理解
vue.js 利用数据劫持结合发布者-订阅者模式的方式,通过 Object.definedProperty 劫持各个属性的 getter,setter,dep.addsub 收集订阅依赖,watcher 监听数据的变化,数据变化时发消息给订阅者,触发相应的监听回调。
- new vue() 初始化,对 data 响应化处理,这个过程发生在 Observe 中。
- 同时对模板进行编译,找到动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 compile 中。
- 同时定义一个更新函数和 watcher,数据变化时 watcher 更新函数。
- data 的某个 key 在一个视图中可能会出现多次,所以每个 key 都需要一个管家 dep 来管理多个 watcher。
- data 数据发生变化,找到对应的 dep 通知 watcher 更新函数。
2、为什么 data 是一个函数,不是对象
- JavaScript 中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行了操作,其他实例中的数据也会发生变化。
- Vue 更多的是想要复用组件,就需要每个组件都有自己的数据,组件间才不会相互干扰。
- 组件不能写成对象的形式,要写成对象的形式,数据以函数返回值的形式定义,每次复用组件的时候,就会返回一个新的 data。
- 每个组件都有了自己的数据,不影响其他组件的数据。
2、你做过vue的哪些性能优化
路由懒加载:使用 import 引用,以函数的方式进行引入,把各自的路由组件分别打包,只要给定解析的路由时,才会下载路由组件。
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{path:'/login',component: ()=> import(@/views/login/index.vue) }
{path:'/home', component: ()=> import(@/views/home/index.vue) }
]
export default router
第三方插件的按需引入:Element-ui的按需引入
import { Buttom } from 'element-ui'
Vue.use(Buttom)
v-if v-show computed watch 区别使用应用场景v-for 必须加 key,key使用id,而且避免同时使用 v-if使用 防抖和节流使用 keep-alive 缓存组件对象的层级不写很深
3、v-if 和 v-show 有什么区别
- 控制手段:v-show 使用 css 的 display:none,;控制。v-if 是将 dom 元素整个添加删除。
- 编译过程:v-show 只是 css 的简单切换,v-if 是局部编译/卸载事件和组件的过程
- v-show 为 false ,true 的时候不会涉及到 vue 生命周期
- v-if 为 true 的时候会涉及到 vue 的前面四个周期 beforeCreate、create、beforeMount、mounted,为 false 的时候涉及到 vue 后面两个生命周期 beforeDestory、destoryed
4、computed 和 watch 的区别
- computed: 支持缓存,不支持异步操作,因为无法监听数据的变化,依赖数据改变,重新进行计算,如果一个属性依赖其他属性,多对一,一般用 computed。
- watch: 不支持缓存,支持异步监听,数据变,直接触发相应操作,监听的数据必须是 data 中声明或者是父级传过来的 props 数据
- immediate:会立即执行一次
- deep:深度监听。
- 操作比较多的时候用 watch
- 性能消耗:v-show 消耗比较小,v-if 消耗比较大
5、Computed 和 Methods 的区别?
- Computed 是计算属性,可以进行缓存数据,依赖的数据发生变化的才会执行。
- Methods:只要调用都会执行。
7、防抖和节流
就像网站荣耀的回城和技能的冷却时间。
防抖:回城,电梯,点击跳转页的时候,点1还没响应,手速很快点到了2,最后执行肯定是2页的内容啊节流:技能冷却,下拉加载。
<button id="debounce">点我防抖!</button>;
// 防抖
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
function debounce(fn) {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.call(this, arguments);
}, 1000);
};
}
function sayDebounce() {
console.log("防抖成功!");
}
<button id="throttle">点我节流!</button>;
// 节流
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
function throttle(fn) {
let timer = true;
return function () {
if (!timer) {
return;
}
timer = false;
setTimeout(() => {
fn.call(this, arguments);
timer = true;
}, 500);
};
}
function sayThrottle() {
console.log("节流成功!");
}
8、对 keep-alive 的理解?它是怎么实现的?具体缓存了什么?
- 组件切换的时候,为了保存组件的状态,防止多次渲染。就使用 ·
keep-alive包裹需要保存的组件。 keep-alive会触发组件的activateddeactivated两个生命周期keep-alive是通过catch 数组实现缓存组件的vnode 实例。keep-alive通常搭配动态组件和router-viewkeep-alive有三个属性:
include:接收参数是字符串或者正则表达式,名称匹配的被缓存。exclude:接收参数是字符串或者正则表达式,名称匹配的不被缓存。max:接收数字,最多可以包裹组件。 它的过程是:
- 判断 name ,是否在 include,exclude中,直接返回 vnode,该组件需不需要被缓存。
- 当缓存的数量超过最大 max 时,清除 keys 数组里第一个组件
12、在 Vue 中给 data 中的对象增加属性时会发生什么?如何解决?
- 对象改变了但是页面并没有被刷新,因为没有被 vue 做响应化处理
- 需要通过 set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了。
13、单页面应用和多页面应用的区别?
- 单页面应用:一个主界面和多个组件,页面切换快,资源文件只加载一次,组件切换是局部更新,路由模式有 hash,history模式,数据传递有 vuex,组件通信,前期开发成本高,后期维护成本低。
- 多页面应用:单独完整页面,页面切换慢,每个页面都需要加载资源,内容更新的话会整体刷新,跳转方式是普通连接跳转,数据传递是 cookie、localStorage,前期开发简单,后期修改功能会修改多个界面。
filters 有了解吗?
filters 是一个过滤器,不会改变数据,是过滤数据,变成自己子想要展示的数据。
应用场景有: 展示时间,展示价格等...
常见的修饰符及其作用
- .stop 停止事件的,相当于 event.stopPropagation() 防止事件冒泡
- .prevent 相当于 evnt.preventDefault() 阻止默认行为
- .self 只触发自己的事件,不包括子元素
- .once 只触发一次事件
说说 vue 内置指令
- v-bind:绑定属性,动态更新 HTML 上的属性,可以动态传值,传方法,动态绑定 class 名等等
- v-on:用于监听 dom 事件
v-on:click v-on:change - v-hmtl:渲染 HTML 代码,防止被 XSS 攻击。
- v-text:更新元素的
- v-model:处理 value 和 input 的语法糖
- v-if/v-show:判断元素/组件的消失出现的。
- v-for:循环指令,不要用 index 当做 key,可以用
+new Date()当做 key 不能和 v-if 一起使用,因为 v-for 优先级高,如果非要用就写成计算属性的方式。
Vue 中封装的数组有哪些?如何实现页面的更新?
- 封装的数组有:
- push 在数组的没添加一个元素并返回数组的长度 [1, 2, 3, 1] 4
- pop 用于删除数组最后的一个元素,并返回最后一个元素 [1,2] 3
- shift 删除数组的第一个长度并返回第一个元素的值 [2,3] 1
- unshift 向数组头部添加一个长度并返回数组的长度 [1,1,2,3] 4
- splice 删除,添加,替换
- sort 排列数组
- reverse 倒叙数组
- 使用函数劫持的方式,重写了数组的方法。
- vue 将 data 中的数组进行了原型链的重写,指向了自己定义的数组原型方法中,当调用数组的 api 时,通知依赖更新。
- 如果数组中包含引用类型数据,会再次监听引用类型数据。
$route 和 $router 的区别
$route是路由信息对象,包含了 path、params、hash、query、name、fullPath、matched.$router是路由实例对象,包含了路由的跳转方法,钩子函数等
Vue 组件间的通信方式
props/$emit(父组件向子组件传参,子组件向父组件传参,$emit:父组件通过v-on监听并接收参数)- 响应式的
eventBus(非父子间,$emit/$on)project/inject(父子间通信/或者是祖孙通信)ref/$refs(ref在子组件上定义,在父组件中使用this.$refs来用变量和方法)- 响应式的
$parent/$chidren($chidren是个无需数组,$parent是个对象)- 响应式的
- 父组件获取子组件是个无序的数组
$attrs/$listeners(组件间的跨代通信)- 响应式的
- 以上的这些方法层次感都太多了,建议使用 vuex
组件和插件的区别
- 组件(Component):组件是用于构成内容、业务模块功能的,目标是 App.vue
- 插件(Plugin):是用于增强技术栈功能模块的,目标是 vue 本身。
插件和依赖的区别
依赖:运行时开发时都需要用到的包,比如项目中需要一个包,就要添加一个依赖,这个依赖在项目运行时也需要,因此在项目打包时需要把这些依赖也打包进项目里;
插件:在项目开的发时需要,但是在项目运行时不需要,因此在项目开发完成后不需要把插件打包进项目中,比如有个可以自动生成 getter 和 setter 的插件,嗯对这就是插件了,因为这玩意在编译时生成 getter 和 setter,编译结束后就没用了,所以项目打包时并不需要把插件放进去
npm 安装时 --save --dev 和 --save 区别
--save:将保存配置信息到 pacjage.json 的 dependencies 节点中。
--save-dev:将保存配置信息到 pacjage.json 的 devDependencies 节点中。
dependencies:运行时的依赖,发布后,即生产环境下还需要用的模块
devDependencies:开发时的依赖。里面的模块是开发时用的,发布时用不到它。
跨域
跨域的本质是浏览器基于同源策略的一种安全方式,前端通过一些方式避开浏览器安全的限制。同源指的是协议``端口``域名一一致,不一致就是跨域。
有三种方式:JSONP,CORS,Proxy
- JSONP:
<script>标签的 src 属性不会被同源策略所约束,可以获取任意服务器上的脚本并执行。jsonp 通过插入script标签的方式来实现跨域,参数只能通过url传入,仅能支持get请求。在 get 请求的时候不太安全,携带的数据比较小。 - CORS:CORS 是一个系统,是由一系列传输的 HTTP 头组成, HTTP 头决定是否阻止前端 Js 代码
获取跨域请求的响应。 只需要增加一些 HTTP 头,让服务器声明允许访问来源,就实现了跨域。 - Proxy:Proxy 是网络代理,网关,路由器等设备具有网络代理功能,客户端通过网络代理与服务器进行非直接的连接。网路代理可以保护隐私安全,防止攻击。 我是通过 Vue-clii 脚手架搭建项目的,用 node 起一个本地服务器作为请求的代理对象。 通过本地服务器发请求给目标服务器,得到结果再转发给前端
我的毕业设计就是一个 IT 信息平台。
当时自己不想写很多的数据,就从 Vue 中文社区里面找了一个 API 接口。通过跨域拿到了里面的同步数据。
下载 axios 进行封装,通过发送请求,配置请求的的根路径。
新增一个 vue.config.js和 axios.js 文件
// axios
import axios from "axios";
axios.defaults.baceURL = "/api";
const $axios = axios;
export default $axios;
// vue.config.js
module.exports = {
devServer: {
proxy: {
"/api": {
target: "目标路径",
ws: true, //是否缓存
changeOrigin: true, //是否跨域
pathRewrite: {
"^/api": "", //路径重写
},
},
},
},
};
vue3
Vue3 和 Vue2
- 响应式原理的改变: vue3 使用的是 proxy,vue2 使用的是 object.definedProperty()
- Object,definedProperty() 只是劫持对象的属性,(监听数据还需要通过 set()方法)
- proxy 可以直接代理整个对象,包括增加属性和删除属性,可以监听数组的变化
- 组件选项声明方式:vue3 使用的是 Composition API,setup 是 vue3 新增的的一个选项,他是组件内使用 Composition API 的入口
-
- setup 在 beforeCreate 之前执行,不能访问 this,显示 undefined,有 props content 两个参数,props 是响应式的,不能使用解构赋值,可以使用 toRef,content 包含三个属性 attrs,slot,emit
-
- vue2 定义数据在 data 中,vue3 定义数据在 ref 和 reactive 中。
-
- watch 可以监听多个属性
-
<template>可以接收多个节点
-
- 模板语法的变化:slot 具名插槽,自定义指令 v-model 升级
-
- vue2 的具名插槽和作用域插槽用 slot 和 slot-scope 实现,vue3 使用 v-slot 实现
-
- v-model 和 .sync 结合
-
13 Vue3.0 和 2.0 的响应式原理区别
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
Object.definedProperty() 和 proxy 的区别
- proxy 可以监听数据的变化,是 ES6 新增的属性,可以直接代理整个对象,包括新增属性和删除属性,可以监听数组的变化。
- vue 对象添加删除属性时,监听不到变化,因为初始化的时候没有进行响应化处理,只能通过 $set 方法调用 Object.definedProperty()处理
做过SSR吗?说一说
在vue项目中没有做过,但是有了解过。
SSR是服务器端渲染,主要是将Vue在客户端把标签渲染成HTML页面的工作放在服务器端完成,然后把HTML返回客户端。
优点
SSR有着更好的SEO,并且首屏加载速度更快。
缺点
- 但是开发条件会受到限制,服务器端只支持两个生命周期 beforeCrate,Created。
- 还要对
外部扩展库做特殊的处理,服务器端渲染应用程序还要处于node.js运行环境中。 - 这就意味着服务器端有着很大的负载需求。
Vue 事件绑定原理了解吗
原生事件是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的 $on 方法实现的。
如果想在组件上绑定原生事件,需要加 .native 修饰符,这样就相当于父组件把子组件当做普通的HTML标签,然后加上原生事件。
28 对 React 和 Vue 的理解,他们的异同
相同点:
- 都有
路由和vuex和其他组件库。 - 都有
props的概念,允许组件间通信。 - 都鼓励
组件化,提高复用性
不同之处:
- Vue 支持数据双向绑定,React 支持单项数据流。
- Vue 的模板编写接近 HTML, React 的模板编写是使用
JSX。 - Vue 使用 getter ,setter 劫持数据的方式,React 通过引入的方式。
- Vue 强调数据可变, React 强调数据不可变。
- Vue 通过 mixins 进行扩展,React 通过 HOC 高阶组件(高阶函数)扩展。
- Vue 有自己的 Vue Cli, React 有 Create React App
- 跨平台 Vue 有 weex,React 有 React Native
Vue 的优点
- 简单易学
- 组件化
- 双向数据绑定
- 轻量级框架
- 视图、数据、结构分离
- 虚拟 dom
- 运行速度快
Vue 的生命周期
八个
- beforeCreate(初始化界面前)
- created(初始化界面后)
- beforeMount(渲染 dom 前)
- mounted(渲染 dom 后)
- beforeUpdate(更新数据前)
- updated(更新数据后)
- beforeDestroy(卸载组件前)
- destroyed(卸载组件后)
还有两个是
keep-alive的生命周期activateddeactivated
每个周期适合的场景
- beforCreate: 写一个 loading 事件,在加载时触发
- created: 写一个结束 loading 事件,写异步请求
- mounted:挂载元素,获取到 DOM 节点
- updated:写一个处理数据的函数
- beforeDestroyed:写一个确认停止事件的确认框
crated 和 mounted 的区别
created:在模板渲染成 HTML 前调用,此时 data 已经准备完毕,el 仍是 undefined,因为模板还没有渲染成 HTML ,所以不能操作 dom 节点,通常用来初始化数据。如果非要想与 Dom 进行交互,可以通过 vm.$nextTick 来访问 Dommounted:模板渲染成 HTML 后调用,此时 data,el 已经准备完毕,可以操作 dom 节点,可以通过 id 查找页面的元素,也可以加载一些组件。
异步请求放在哪里最合适
- 放在
createdbeforeMountmounted这三个周期内,因为这三个周期 data 已经初始化完毕,可以操作数据。 - 如果不操作 dom 节点的话最好放在
created里面,因为他能最早拿到数据,而且SSR不支持beforeMountmounted这两个生命周期。
vue 父子组件生命周期执行过程
- 加载渲染过程 父 beforeCreate -> 父 created -> 父 beforeMount-> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
- 子组件更新过程 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
- 父组件更过程 父 beforeUpdate -> 父 updated
- 子组件销毁过程 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
做过 SSR 吗?说说 SSR
- SSR 是服务器端渲染,将 Vue 在客户端把标签渲染 HTML 页面的工作放到服务器端去渲染,然后再把 HTML 返回客户端。
- SSR 有着更好的 SEO,并且首屏加载速度更快。但是开发条件会受到限制,服务器端只支持两个生命周期 beforeCreate,Created,还要对外部扩展库进行特殊的处理,服务器端渲染应用程序也需要处于 node.js 的运行环境。
- 服务器有很大的负载需求
组件的通信方式有哪些?
props/$emit:props接收父组件传过来的参数,子组件通过$emit提交父组件的方法,并携带参数改变数据。$emit的第一个参数是父组件的方法,第二个参数是传参。
- 父组件 parent
<template>
<div>
<span>父组件数据:{{ num }}</span>
<hr />
<chidren :num="num" @ChangeNum="ChangeNum" />
</div>
</template>
<script>
import chidren from "./chidren.vue";
export default {
name: "parent",
components: {
chidren,
},
data() {
return {
num: 666,
};
},
methods: {
ChangeNum(newNum) {
this.num = newNum;
},
},
};
</script>
- 子组件 chidren
<template>
<div>
<span>子组件数据:{{ num }}</span>
<button @click="Demo">点击改变数据</button>
</div>
</template>
<script>
export default {
name: "chidren",
props: {
num: {
type: Number,
default: 0,
},
},
methods: {
Demo() {
this.$emit("ChangeNum", 999);
},
},
};
</script>
-
$parent/$children -
project/inject依赖注入所提供的属性是非响应式的。
-
$attrs/$listeners -
eventBus事件总线($emit / $on)
通过 $parent/$refs 来获取到兄弟组件,也可以进行通信。
history 和 hash 模式的区别
vue-router 有几种模式?有什么区别?history 模式下 404 后台应该配置什么? 路由有两种模式:hash模式,history模式
- hash模式是路由的默认模式,它的URL里面带
#。 - hash 值在 URL 里面,不在 HTTP 请求中,不会对后端产生影响。修改hash值时不会加载页面。hash 模式在浏览器中的兼容性比较好。
- hash值变化对应的URL会被浏览器记录下来,浏览器可以实现页面的前进后退。
- hash值的主要原理是
onhashchange()事件
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
- history 模式比较美观,URL里面并没有
#,只有/ - 采用的是路由分发模式。如果后台没有正确配置 history 模式,会返回404。
- history api 有两种状态:修改历史状态和切换历史状态。
- 修改历史状态有两个方法,pushState()、replaceState、记录浏览器历史记录,可以改变URL,不刷新页面。
- 切换历史状态有三个方法
forward()back()go,对应浏览器的前进,后退,跳转操作。
如何获取页面的hash变化
watch监听$route的和变化。window.location.hash读取。
39. vue初始化页面闪动问题
使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。
首先:在css里加上以下代码:
[v-cloak] {
display: none;
}
如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display: 'block'}"
Vuex
不用 Vuex 会带来什么问题
- 可维护性会下降,你要想修改数据,你得维护三个地方
- 可读性会下降,因为一个组件里的数据,你根本就看不出来是从哪来的
- 增加耦合,大量的上传派发,会让耦合性大大的增加,本来 Vue 用 Component 就是为了减少耦合,现在这么用,和组件化的初衷相背。
- 兄弟组件有大量通信的,建议一定要用,不管大项目和小项目,因为这样会省很多事
Vuex 有哪几种属性?
State mutation action getter module
mutation 和 action 有什么区别?
- action 提交的是 mutation,不可以直接修改数据状态,mutation 可以直接修改数据状态。
- action 可以是异步操作,mutation 不可以使异步操作。
- 提交的方式不同,action 是
this.$store.dispatch()mutation 是this.$store.commit() - 接收的参数不同,action 的第一个参数是 context,包含了 state,commit等属性,mutation 的第一个参数是 state ,他们的第二个参数都是接收参数,如果接受多个参数的话可以写成数组或对象的形式。
vuex 和单独的全局对象有什么区别?
- vuex 数据是响应式的、不可直接修改。需要 Mutation 提交 commit 修改
- 全局对象不是响应式的,可以直接修改
如何在组件中批量使用Vuex的getter属性
使用mapGetters辅助函数, 利用对象展开运算符将getter混入computed 对象中
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['total','discountTotal'])
}
}
如何在组件中重复使用Vuex的mutation
使用mapMutations辅助函数,在组件中这么使用
import { mapMutations } from 'vuex'
methods:{
...mapMutations({
setNumber:'SET_NUMBER',
})
}
然后调用this.setNumber(10)相当调用this.$store.commit('SET_NUMBER',10)
vuex 的原理
- vuex 的存储于状态时响应式的,store 发生变化,组件的数据也会发生变化。
- 通过 action commit 提交 mutation ,再通过 mutation 提交 commit 提交 store 改变数据。
Vue 里用 vuex做组件之间的数据双向绑定
监听 Vuex 里的数据变化 mapState
import { mapState } from 'vuex'
computed: {
...mapState({
count: state => state.number.count,
})
},
怎么解决 vuex 里的数据在页面刷新后丢失的问题?
- 需要做 vuex 数据持久化,一般使用本地存储的方案解决
- 有可以使用第三方插件 vuex-persist 插件,它是为 vuex 数据持久化存储而生的插件,不需要手动存取 Storage, 直接将数据存储到 cookie 或者 locolstorage 中。
路由
1、 parmas 和 query 的区别?
- params 传参必须使用命名路由的方式传参,不会显示在地址栏上,会保存在内存中,刷新会丢失。可以配合本地存储进行使用。
- query 参数会显示在地址栏上,刷新不会丢失。
router 跳转和 locaotion.herf 跳转有什么区别?
一个是会刷新,一个不会刷新
路由守卫
- 全局路由钩子:
- 全局前置守卫
beforeEach判断是否登录,没登录就跳转登录页,三个参数 (to,from,next) - 全局解析钩子
beforeResolve - 全局后置钩子
afterEach跳转之后滚动条回到顶部 (to,from)
- 全局前置守卫
- 路由独享守卫:
beforeEnter即将进入登录首页 - 组件内守卫:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
key 在 vue 项目中的作用。
- 在 diff 算法执行的过程中快速的找到对应的节点,提高效率,高效的更新渲染虚拟 dom。
- 在数据变化的时候强制更新组件,避免 '就地复用' 带来的副作用。
意思是 v-for 更新已渲染的元素列表时,就默认使用 '就地复用' 策略,数据项的顺序发生变化时,vue 不会移动 dom 元素去匹配数据项的顺序,而是简单的复用此处的每个元素,从而通过为每个列表项提供一个 key 值,方便 vue 跟踪元素的身份,高效的实现复用。
- 不能用 index 作为 key,可以使用时间戳 new Date() 作为 key,或者 id 作为key,因为用 index 作为 key 和没用基本没区别,数组不管再怎么颠倒,index 的排列顺序还是 0,1,2, 导致 vue 复用作物的旧子节点,做很多额外的工作。
为什么 v-for 和 v-if 不能在一起使用?
- 因为
v-for的优先级比较高,每次渲染都会先循环再进行判断,带来性能的浪费。 - 如果要是用
v-if可以在外层套<template> - v-if也可以写在 computed 内。
computed: {
items: function() {
return this.list.filter(function (item) {
return item.isShow
})
}
}
虚拟 dom 是什么?有什么优缺点?
- 浏览器操作 dom 的代价很昂贵,频繁的操作 dom 会产生性能的问题。
- 虚拟dom的本质是利用原生JS对象描述一个 dom 节点,是对真实 dom 节点的一层抽象。
- 将
template(真实DOM)先转成ast,ast树通过codegen生成render函数,render函数里的_c方法将它转为虚拟dom - 数据变化前,虚拟 dom 缓存一份,变化时,通过 diff 算法比较现在的虚拟 dom 和缓存的虚拟 dom,然后渲染改变的部分,没有改变的部分用之前的数据渲染。 优点:
- 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
- 跨平台:可以在服务器渲染。
缺点:
- 对于一些业务性能要求比较高的逻辑无法极致的优化。
- 首屏加载的时候会比较慢。
4、另外一种说法 为什么虚拟dom会提高性能?
参考答案
### 为什么虚拟dom会提高性能?
- 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。 具体实现步骤如下:
- 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
- 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
diff 有了解吗?说一说
diff 是通过同层树节点进行比较的高效算法。它有两个特点:
深度优先、同层比较、不会跨级。diff 比较过程中,循环从两边向中间比较。应用场景有: 虚拟 dom 节点渲染成真实 dom 节点的新旧vnode节点比较。
- 当数据变化时,
set方法调用dep.notify()通知所有的订阅者Watch,订阅者调用patch函数给真实的dom打补丁,然后更新视图。 patch函数有两个参数oldvnodenewvnode,然后点用isSamevnode方法进行判断。- diff 算法的核心是判断如何对这些新老节点的子节点进行操作。
Vue 的 $nextTick 原理和作用
我们可以理解成 vue 更新 dom 是异步执行的,数据变化时,vue 开启一个异步更新队列,视图等队列上所有的数据变化完成之后,再统一进行更新。主要思路是采用微任务优先的方式,调用异步任务去执行,nextTick 包装好的方法。
this.$nextTick(()=>{
// 操作具体的数据
})
vue 中应用了哪些设计模式
- 工厂模式(传入参数就可以创建实例)
- 单例模式(整个程序只有一个实例)
- 发布订阅模式(Vue事件机制)
- 观察者模式(响应式数据原理)
- 装饰模式(@装饰器的用法)
mixin 和 mixins 的区别
- mixin 是全局混入,会影响每个组件的实例,通常会初始化一个插件。
- mixin 优先于 created 执行
- 缺点是定义的变量会重名,冲突,不清楚暴露的变量是干嘛的
// 在 main.js 里面插入
Vue.mixin({
created() {
// 总共有 APP.vue MixinDemo.vue 还有根组件呢,所以执行了三次
// console.log(this);
console.log("全局 mixin 加入成功");
},
});
- mixins 是扩展组件的方式。如果组件有很多相同的业务逻辑就把它剥离出来,通过 mixins 混入代码
- mixins 优先于 created 执行
// 组件代码
<script>
import helloMixin from "../mixins/hello";
export default {
name: "MixinDemo",
data() {
return {
number: 10000,
};
},
mixins: [helloMixin],
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
};
</script>
// js 代码
const helloMixin = {
// 该对象内的格式和组件导出对象格式一样
// 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行 "合并"
// 可以拿得到组件的 this
// 恰当的方式
// 1. data 以组件为主 (如果是不同名的话就都展示)
// 2. 生命周期函数 自动合并(mixin混入的先执行)
// 3. methods components 和 directives 以组件本身为主
data() {
return {
num: 100,
};
},
created() {
console.log("mixins 内的 hello");
// console.log("判断用户是否登录了");
// console.log(this);
// const loading = true;
// if (loading) {
// console.log("用户已经登陆");
// } else {
// console.log("用户还没有登录");
// }
},
};
export default helloMixin;
Vue 自定义指令
directive 方法的第二个参数会接收一个对象,对象内有一些钩子函数,用来设置指令的功能,钩子函数会在特定的情况下自动触发
常用的两个钩子函数
bind元素绑定时调用inserted元素插入 dom 时调用update元素更新时调用componentUpdate元素更新后调用unbind元素解绑时调用
自己写了两个自定义指令
<input v-focus="true" type="text" />
// main.js 全局绑定
Vue.diretive("focus",{
inserted: function(el,binding){
if(binding.value) {
el.focus()
}
}
})
<input v-focus="true" type="text" />
main.js 全局绑定
Vue.diretive("focus",{
inserted: function(el,binding){
if(binding.value) {
el.focus()
}
}
})
注册组件内的局部指令,只有该组件内使用
directives:{
focus:{
inserted(el,binding){
console.log(el,binding);
// 节点: "focus", rawName: "v-focus", modifiers: {…}, def: {…}}
el.focus()
}
}
}
Vue.directive("my-click", {
inserted(el, binding) {
console.log(binding);
if (binding.value) el.onclick = binding.value
},
});
vuex 的实现原理
- Vue.use(vuex)会调用 vuex 的 install 方法
- 在 beforeCreate 钩子前混入 vuexInit 方法,vuexInit 方法实现了 store 注入 vue 组件实例,并注册了 vuex store 的引用属性$store。
- Vuex 的 state 状态是响应式,是借助 vue 的 data 是响应式,将 state 存入 vue 实例组件的 data 中;
- Vuex 的 getters 则是借助 vue 的计算属性 computed 实现数据实时监听。
vue.component() vue.use()
- vue.component() 是注册全局组件的,有两个参数,第一个参数是自定义名称,第二个参数是要注册的组件。
- vue.use() 注册插件的,接收一个参数,这个参数必须由 install 方法,vue.use() 方法会自动调用 插件的 install 方法。
Vue 事件的绑定原理
- 原生事件是通过
addEventListener绑定的 - Vue 事件是通过
$on方法绑定的
v-model 的实现原理
- v-model 是 value 和 input 的语法糖,会根据标签的不同,生成不同的事件和标签。
- text 和 textarea 元素使用是用 value property 和 input 事件
- checkbox 元素使用的是 checked property 和change 事件。
- select 元素使用的是 prop 和 change 事件。
为什么vue采用异步渲染呢?
- 因为 vue 是组件级更新,不采用异步更新,每次数据改变都会对当前组件重新渲染。
- 为了性能消耗,vue 会在本轮数据更新后,异步更新视图。
描述组件渲染和更新过程
- 渲染组件时,会通过
vue.extend()方法构建子组件的构造函数,并进行实例化,最终手动调用$mount()进行挂载。 - 更新组件时会进行
patchVnode流程,核心就是diff算法。
Object.definedProperty() 的特点
- Object.definedProperty() 只能够劫持对象数组的属性,不能检测到对象数组添加删除属性,因为在初始化的时候没有进行相应化处理,可以通过 $set() 方法调用 Object.definedProperty() 手动进行相应化处理。
- vue3 使用 Proxy 监听数据的变化,Proxy 是ES6新增的属性,能够代理整个对象数组,包括添加删除属性。
extend 有什么作用?
extend 是扩展组件生成一个构造器,通常和 $mount 一起使用
vuex 和 LocalStorage 的区别
最重要的是 vuex 存储在内存中,LocalStorage 存储在本地
- LocalStorage 保存对象需要使用 JSON.stringfy 和 parse
- 读取内存的速度比硬盘的速度快
- vuex 的数据是响应式的,LocalStorage 不是
- vuex 是在 组件间通信的,LocalStorage 是在网页建通信的
- vuex 是刷新数据会丢失,LocalStorage 刷新数据不会丢失
vuex 和全局对象的区别
- vuex 数据是响应式, 全局对象不是响应式的
- vuex 不可以直接变更状态,全局对象可以直接变更状态
1. 懒加载的概念
懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。
如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
2. 懒加载的特点
- 减少无用资源的加载:使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。
- 提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验。
- 防止加载过多图片而影响其他资源文件的加载 :会影响网站应用的正常使用。
3. 懒加载的实现原理
图片的加载是由src引起的,当对src赋值时,浏览器就会请求图片资源。根据这个原理,我们使用HTML5 的data-xxx属性来储存图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。
注意:data-xxx 中的xxx可以自定义,这里我们使用data-src来定义。
懒加载的实现重点在于确定用户需要加载哪张图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。
使用原生JavaScript实现懒加载: 知识点:
(1)window.innerHeight 是浏览器可视区的高度
(2)document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离
(3)imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离)
(4)图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop;
<div class="container">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winHeight= window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winHeight ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
window.onscroll = lozyLoad();
</script>
4. 懒加载与预加载的区别
这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
- 懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。
- 预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。 通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。
如何学习前端技术?
看红宝书打基础,边看边复现案例,然后在掘金社区关注不同的博主对同一知识点的看法,然后看视频查漏补缺,还有就是在群聊里面互相讨论在学习和工作中遇到的一些坑和难点
JS 的基本数据类型、引用类型,二者存储的方式
有 number string undefined null boolean BigInt symbol object 队列:先进先出 原数数据 undefined null number string boolean 线性结构,存储在栈中,先进后出,后进先出 引用数据 object array function 存储在堆中,树形结构
- ES6 的模块化和 CommonJS 模块化的区别 CommonJs 模块是输出一个值的拷贝,ES6 模块是输出一个值的引用 CommonJs 模块是运行时加载,ES6 模块是编译时输出接口
- import 和 requeir 的最主要的区别,静态引用和动态引用 import 是 ES6 模块的,requere 是 AMD 模块的 import 是编译时调用,必须放在文件的开头,requere 是运行时调用,可以放在代码的任何地方,requere 的结果是对象,数组,字符串,函数,是把结果赋值给某个变量。
- promise 和 await async 之间的关系 async/await 是基于 promise 实现的,跟同步代码差不多 async/await 不需要写箭头函数处理 promise 的 resolve() 函数 async/await 让 try catch 可以同事处理同步和异步错误 async/await 的第一个方法返回值,用作第二个方法的参数,解决了嵌套问题。
- http 缓存类型有哪些?
https://www.jianshu.com/p/227cee9c8d15有强缓存和协商缓存 区别是: 强缓存不会向服务器发请求,状态码是 200 协会缓存通过 get 发请求,状态码是 304 - 依赖收集在哪一个方法中去完成的? 计算属性是在 beforeMounte 和 Mounted 之间完成 监听属性是在 beforeCreate 和 Created 之间完成
- 说一下 dep 和 watcher 之间的关系
- 双向绑定的功能是在哪一个生命周期中实现的? Mounted 之间实现的
- vue 模板编译的过程,每一个过程细说一下做了些什么
<template>转换成 ast 树,ast 树通过 ganerate 变成 reder 函数,render 函数再通过 _C 方法变成虚拟 dom 节点 - 你怎么做到降低代码的耦合度 少用类的继承,多用接口 避免使用全局变量 模块功能划分
- 图片懒加载怎么做到的 自己先写一个自定义属性,把这个属性的值写成列表数据,在可视化范围内的时候,就把自定义属性的值做展示,这就实现懒加载