目录
Vue相关
1.vue的路由有哪些模式?分别有什么异同(优缺点)
答:首先路由有三种模式;hash模式、history模式、abstract模式
hash模式:使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载,其显示的网路路径中会有 “#” 号,有一点点丑。这是最安全的模式,因为他兼容所有的浏览器和服务器。
缺点:只能改变#后面的来实现路由跳转。
history模式:美化后的hash模式,会去掉路径中的 “#”。依赖于Html5 的history,pushState API,所以要担心IE9以及一下的版本;包括back、forward、go三个方法,对应浏览器的前进,后退,跳转操作。就是浏览器左上角的前进、后退等按钮进行的操作。
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
缺点:如果后端没有设置对应路由,直接访问会出现404;怕刷新
abstract模式:适用于所有JavaScript环境,例如服务器端使用Node.js。如果没有浏览器API,路由器将自动被强制进入此模式。
2.vue中的route和router的区别;
答:route是路由信息对象,里面有name,path,params,query等信息参数
router是一个VueRouter的实例对象,里面含有很多属性和子对象(history),push方法等【直接通过path手写this.$router.push({path:`/user/${userId}`});也可以用params传递(通过name);也可以用query传递(通过path)】
3.vue-router的原理
更新视图但不重新请求页面,是前端路由原理的核心之一
(1)利用URL中的hash("#"); hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页;
(2)利用History interface在HTML5中新增的方法;利用 history.pushState API 来完成 URL 跳转而无须重新加载页面
4.vue.nextTick()方法,和实现原理
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数,
何时使用?
(1)Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
(2)当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作
(3)在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件
使用原理
Vue DOM更新是异步执行
的,即修改数据时,视图不会立即更新,所以需要在dom更新后再去执行nextTick(),一个事件循环为一个tick,要想在更新dom后操作js,就需要在下一个tick中执行,所以需要使用nextTick();
5.computed 和 watch 的区别和运用的场景
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;(需要进行数值计算,并且依赖于其它数据时使用)
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;(可以执行异步,调用api,在得到借过前设置中间状态等)
computed
是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而watch
是监听已经存在且已挂载到vm
上的数据,所以用watch
同样可以监听computed
计算属性的变化(其它还有data
、props
)computed
本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问computed
属性,才会计算新的值,而watch
则是当数据发生变化便会调用执行函数- 从使用场景上说,
computed
适用一个数据被多个数据影响,而watch
适用一个数据影响多个数据;
watch中有哪些参数和方法
immediate:(立即执行)在初始化的时候watch是不会执行的,如果想要执行,设置immediate为true
deep:(深度监听)监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听。设置deep为true
handler:deep会给所有的属性都加上handler;如果只需要监听对象中的一个属性值,则可使用handler(new,old):使用字符串的形式监听对象属性:
data() {
return {
list: {
'id': 1,
'type': 0
}
}
},
watch: {
'list.id': {
handler(newVal, oldVal) {
......
},
deep: true
}
}
原文链接:https://blog.csdn.net/HelloMy_sun/article/details/106214277
6.Vue 响应系统
observe
:遍历data
中的属性,使用 Object.defineProperty 的get/set
方法对其进行数据劫持;dep
:每个属性拥有自己的消息订阅器dep
,用于存放所有订阅了该属性的观察者对象;watcher
:观察者(对象),通过dep
实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。
7.v-model指令的实现原理 - 如何利用v-model设计自定义的表单组件
v-model是一个语法糖,本质还是v-bind绑定了表单的input方法
官方文档:
8.keep-alive原理及实现
简单介绍:是一个vue的内置组件,提供了include与exclude属性,允许组件有条件地进行缓存,其中exclude的优先级比include高,max最多可以缓存多少组件实例;
钩子函数:对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
原理:在created钩子会创建一个cache对象,用来作为缓存容器,保存vnode节点。在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。在destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
源码:
export default {
props: {
include: patternTypes, // 允许组件有条件地进行缓存
exclude: patternTypes,
max: [String, Number] // 最多可以缓存多少组件实例
},
created () {
this.cache = Object.create(null) // 创建一个cache对象,用来作为缓存容器,保存vnode节点
this.keys = []
},
destroyed () {
// 清除cache缓存中的所有组件实例
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
}
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
怎么手动清除组件缓存?
可以在配置<keep-alive>标签的:include的时候用一个动态数组,然后在路由钩子(beforeRouteLeave(to, from, next))中清除动态数组中对应的不需要的缓存组件名
9.为什么Vue组件中的data是一个函数
因为如果是对象的话,属性都回被挂到vue实例上,会使得vue很重,组件之间的数据也会被污染,会使得组件变得耦合
10.不同版本vue-cli区别
大区别在于vue-cli2.0和vue-cli3.0/4.0
(1).安装和创建项目还有启动的命令不同;
(2).创建的目录结构不同
vue-cli3/cli4中移除了配置文件目录:config
和 build
文件夹。
同时移除了 static
静态文件夹,新增了 public
文件夹,将 index.html
移动到 public
中。
(3).环境变量名有所不同
11.vuex相关
-
1.state – 存放状态 使用this.$store.state.属性
-
2.getters – state的计算属性 使用$sotre.getters.fun()
-
3.mutations – 更改状态的逻辑,同步操作 使用$store.commit('方法名',params)入参可以使单个数据,也可以是对象 (如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难)
-
4.actions – 提交mutation,异步操作 使用$store.dispath('方法名',params)入参可以使单个数据,也可以是对象
-
5.mudules – 将store模块化
computed: {
...mapState(['increment']),
...mapGetters(['doneTodos'])
}
详细可以看原文https://blog.csdn.net/yusirxiaer/article/details/99634868
12.vue-router全局钩子函数+单个路由钩子函数+组件内钩子函数(也可以叫做路由守卫)
(1)全局钩子beforeEach参数:to/from/next和afterEach参数:to/from
(2)单个路由钩子函数beforeEnter参数:to/from/next
(3)组件内钩子函数beforeRouteEnter+beforeRouteUpdate+beforeRouteLeave参数:to/from/next
13.Webpack相关
入口(entry)
webpack打包的起点,可以有一个或多个,一般是js文件。webpack会从起点文件开始,寻找起点直接或间接依赖的其它所有的依赖,包括JS、CSS、图片资源等,作为将来打包的原始数据
输出(output)
出口一般包含两个属性:path和filename。用来告诉webpack打包的目标文件夹,以及文件的名称。目的地也可以有多个。
加载器(loader)
webpack本身只识别Js文件,如果要加载非JS文件,必须指定一些额外的加载器(loader),例如css-loader。然后将这些文件转为webpack能处理的有效模块,最后利用webpack的打包能力去处理。
插件(plugins)
插件可以扩展webpack的功能,让webpack不仅仅是完成打包,甚至各种更复杂的功能,或者是对打包功能进行优化、压缩,提高效率。
webpack的基本功能和工作原理?
- 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
- 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
- 自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
- 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
14.前端微服务
总结:使用qiankun插件实现。在父项目配置子项目对应的路由组件,写定子项目的IP端口即可调用
原文:https://blog.csdn.net/Ag_wenbi/article/details/106915580
15.v-if和v-for有没有优先级?为什么?
v-for的优先级高于v-if;因为在源码中先判断是否是v-for后判断v-if
16.vue/前端ssr(服务端渲染)
(1)vue ssr的优势,能大大缩减网页首屏的加载时间,提升用户体验;
(2)基本的原理:在服务端生成一个html字符串,由后端将这个字符串直接发送给前端(使用vue-server-renderer)
16.双向绑定的原理
核心是采用数据劫持结合发布者订阅者模式,通过Object.defineProperty()对每个属性的get和set进行拦截。在数据发生变化的时候发布消息给订阅者,触发相应的监听回调。(vue3.0采用proxy实现)
Object.defineProperty()有三个参数,第一个是属性所在的对象。第二个是要操作的属性,第三个是被操作的属性的特性,是一个对象{},一般是两个get:读取属性时触发,set写入属性时触发。
三个步骤
-
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
-
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
-
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
转载:https://blog.csdn.net/lishanleilixin/article/details/79360244
我的理解:1.通过Object.defineprototype给所有属性添加上数据劫持,通过set方法来监听数据是否变化;(在数据劫持的get方法中就已经形成了订阅者的初始化,或者说是订阅器的初始化)
2.所有被监听的属性都是一个watcher(订阅者),把他们全放在一个数组里dep[]这就是订阅器;(订阅者的初始化是通过属性的get方法来添加到订阅者中的)
3.如果数据发生变化,触发set方法里面的方法,通知所有订阅者,也就是通知订阅器;
4.订阅器通过循环遍历,将每一个属性都进行update(更新);
5.通过对dom的解析,获取所有节点的内容(文本字符串)来通过变量进行双向绑定
17.vue的渲染过程
- 把模板编译为render函数
- 实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom(虚拟节点里面有一个属性**
elm
**, 这个属性指向真实的DOM节点,可以对真是的dom直接进行替换)(虚拟dom是从VNode类实例化的对象) - 对比虚拟dom,渲染到真实dom
- 组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3
原文:https://blog.csdn.net/q3254421/article/details/89637099
18.Vue.set()
Vue.set( target, propertyName/index, value )
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。
19.vue官方api(感觉都会问到md)
https://cn.vuejs.org/v2/api/#Vue-extend
20.父子组件传值,兄弟组件传值
(1)父传子: 父组件:<child :inputName="name"> 子组件:props: ["inputName"]或props: {inputName: String,required: true}
(2)子组件向父组件传值:$emit 父组件:<child v-on:childByValue="childByValue"></child>(childByValue方法会传入子组件传过来的值) 子组件: this.$emit('childByValue', this.childValue)
(3)父组件调用子组件的方法通过ref:父组件: <children ref="c1"></children> this.$refs.c1.childMethods(); 子组件:childMethods() { alert("I am child's methods")}
(4)可以通过$parent和$children获取父子组件的参数
(5)vue 全局事件(eventBus) :在main.js里:window.eventBus = new Vue();//注册全局事件对象 evenBus.$emit('funcName',name)触发,evenBus.$on('funcName',function(data){})监听获取
(6)vuex
(7)localstorage和sessionstorage本地缓存(sessionStorage在关闭窗口或标签页之后将会删除这些数据。)
21.如何监听数组变化?
- 将数组的常用方法进行重写,通过包装之后的数组方法就能够去在调用的时候被监听到。
- 通过调用 new Proxy() ,你可以创建一个代理用来替代原数组,对数组完全监控。
JS相关问题
1.原型链和继承
这个我只记了原型链继承和寄生组合继承还有es6的class xxx extends xxxparents{}
//原型链继承
function a(rucan){
this.rucan = rucan
}
a.prototype.func = function(rucan){
console.log(rucan)
}
function b(){
}
b.prototype = new a()
//此时b已经继承了a的方法和属性
//call继承,寄生组合继承
function a(rucan){
this.rucan = rucan
}
a.prototype.func = function(rucan){
console.log(rucan)
}
function b(){
this.a().call(this) // 继承私有属性方法
}
b.prototype = Object.create(a.prototype)// 继承了公有方法 重定向了
b.prototype.constructor = b// 重定向后需要写这一行
//es6类继承
function a(rucan){
this.rucan = rucan
}
a.prototype.func = function(rucan){
console.log(rucan)
}
class b extends a {
}
2.call、applay和bind
call:改变this,立即执行,入参可以是多个参数
applay:用法跟call一样,如参是数组
bind:跟call入参使用一样,只不过是延迟执行(其实就是在执行bind函数的时候不会调用函数),会返回一个新函数,调用新函数可以出发改变this指向后的结果
3.用 JavaScript 实现发布/订阅模式
简单来说就是要实现:
(1)订阅(新增一个订阅者就将其push到订阅者数组中,每个订阅者在被添加到数组的时候就给他们都添加上一个方法比如sub() )
(2)发布订阅(将订阅者数组遍历一遍,调用他们的sub() 来通知他们消息)
(3)取消订阅 (讲订阅者从订阅数组里面删除)
(4)储存订阅者(是一个数组,用来存放订阅者的)
4.压缩连续重复字符串
var str = "xxxyyyzzcc";
var a1 = []; //定义空数组存放字符
var a2 = []; //定义空数组存放字符个数
//遍历字符串
for (var i = 0; i < str.length; i++) {
var ch = str[i]; //得到每一个字符
//如果当前字符与数组最后一个字符相等
if (ch == a1[a1.length - 1]) {
a2[a2.length - 1]++;
} else {
a1.push(ch);
a2[a2.length] = 1;
}
}
// a1-->[x,y,z,c]
// a2-->[3,3,2,2]
var str2 = ""; //定义空字符串,存放新字符串
//循环拼接
for (var i = 0; i < a1.length; i++) {
str2 = str2 + a2[i] + a1[i];
}
//去掉1
str2 = str2.split("1").join("");
document.write(str2);
————————————————
原文链接:https://blog.csdn.net/ZPD_zpd/article/details/113741512
5.闭包
闭包就是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,在本质上,闭包是函数内部和函数外部连接起来的桥梁。
缺点:滥用闭包会造成内存泄露,因为闭包中引用到的包裹函数中定义的变量都永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数。
原文:https://blog.csdn.net/weixin_43606158/article/details/90213668