常见面试题总结
1、生命周期函数
Vue的生命周期是指一个组件从创建到销毁的全过程,包含创建、挂载、更新和销毁4个阶段。每个阶段对应2个钩子函数。
1.1、在创建阶段,Vue实例被创建:
对应的钩子函数为beforeCreate()和created():
- beforeCreate()是在创建前被调用,此时组件尚未被创建,无法获得data和methods方法。
- created()是在创建后被调用,此时可以获取data和methods方法,我们通常在这个阶段进行数据请求。
1.2、在挂载阶段,Vue实例被挂载到指定结点:
对应的钩子函数为beforeMounte()和mounted():
- beforeMounte()是在挂载前被调用,此时实例对象尚未被挂载到页面成为真实DOM,因此我们还不能操作真实DOM。
- mounted()是在挂载后被调用,此时实例对象已经被挂载,成为真实DOM,我们通常在这个阶段对DOM进行操作。
1.3、在更新阶段,Vue实例对数据进行更新:
对应的钩子函数为beforeUpdate()和updated():
- beforeUpdate()是在页面更新前被调用,此时虚拟DOM上的数据已经更新,但是页面中的数据尚未更新,我们通常在这个阶段对虚拟DOM上的数进行处理。
- updated()是在页面更新后被调用,此时页面中的数据已经更新完毕。
1.4、在销毁阶段,Vue实例将被销毁:
对应的钩子函数为beforeDestory()和destoryed():
- beforeDestory()是在页面销毁前被调用,此时Vue实例对象尚未销毁,我们通常在这个阶段解绑方法函数。
- destoryed()是在页面被销毁时调用,此时Vue对象实例被彻底销毁。
2、父子组件的生命周期创建执行顺序
2.1、原理
我们知道在Vue的生命周期中,实例在调用beforeMounte()之后,才会访问template中的结点;并且父组件将在template中触发子组件。
2.1、流程
因此,在父组件执行完beforeMounte()之后,子组件会按照正常的生命周期流程执行到mounted(),然后再触发父组件的mounted()。
stateDiagram-v2
父:beforeCreate() --> 父:created()
父:created() --> 父:beforeMount()
父:beforeMount() --> 父:mounted()
父:beforeMount() --> 子:beforeCreate()
子:beforeCreate() --> 子:created()
子:created() --> 子:beforeMount()
子:beforeMount() --> 子:mounted()
子:mounted() --> 父:mounted()
3、生命周期父子更新和销毁的执行顺序
3.1、更新
在父组件执行完beforeUpdate()之后,调用子组件的beforeUpdate()、updated(),然后再执行父组件的updated()。
3.2、销毁
在父组件执行完beforeDestory()之后,调用子组件的beforeDestory()、destoryed(),然后再执行父组件的destoryed()。
stateDiagram-v2
父:mounted() --> 父:beforUpdate()
父:beforUpdate() --> 子:beforUpdate()
子:beforUpdate() --> 子:updated()
子:updated() --> 父:updated()
父:beforUpdate()--> 父:updated()
父:updated()--> 父:beforDestroy()
父:beforDestroy()--> 父:destroyed()
父:beforDestroy()--> 子:beforDestroy()
子:beforDestroy()--> 子:destroyed()
子:destroyed()--> 父:destroyed()
4、v-if和v-show
4.1、相同点
- v-if和v-show都可以实现显示或者隐藏的效果
4.2、不同点
- v-if是通过创建和销毁结点元素来实现控制效果
- v-if适合不频繁切换的情况下使用
- v-show是通过修改display的属性值来实现
- v-show适合频繁切换的情况下使用
5、v-model的语法糖
v-model是用来实现数据双向绑定的,是一种语法糖的写法。
原始写法:在结点元素中使用v-bind赋值变量,实现数据向页面的数据流,再通过v-on配合$event.target.value来实现页面向数据的数据流。
6、v-model在组件上的使用
我们可以在父子组件的通信中使用v-model的语法糖来实现数据通信。在父组件中,通过v-model向子组件进行传值;在子组件中,通过props来接收数据,并通过$emit来将数据返回。
7、v-model自定义解析
在使用v-model实现父子组件通信时,我们可以在子组件中对v-model进行组定义解析。例如,在子组件中创建model属性,通过event来自定义触发的事件(默认是input事件),通过prop来自定义传递的数据(默认是value)。
8、v-for和v-if为什么避免一起使用
8.1、原理
v-for的优先级比v-if要高,因此,我们在创建页面的时候,会先循环渲染出所有的结点,然后在通过v-if来判断是否需要销毁。这样会造成性能的浪费。
8.1、优化
如果v-if的判断条件和v-for的数据相关时,我们通过computed()属性对v-for依赖的数据项进行筛选计算,从而实现v-if的效果。
如果v-if的判断条件和v-for的数据无关时,我们可以在v-for的结点外层包裹一个template标签,在标签中加入v-if属性。这样,在页面创建的过程中,会首先判断v-if的是否渲染,然后在执行v-for的循环。
9、 $router和$route的区别
$router是全局路由对象,包含所有的路由信息,包含如果个$route对象。我们通常在$router中通过.push()、.forward()、.back()、.go()等方式来操作路由的跳转。
$route是当前页面的路由对象,里面包含当前页面的路由信息。我们通常使用.path()、.name()、.meta()来获取当前页面中路由传递的参数。
10、vuex中的mapState使用
vuex是集中式状态管理工具,我们可以在vuex的store对象中存储数据,然后在页面中通过this.$store.state来获取存储的数据。
这样的操作比较繁琐,因此我们引出了一些辅助函数,例如:mapState、mapMutations、mapActions等。我们通常在组件的computed属性中,通过扩展运算符的方式来使用mapState,从而实现获取响应式的存储数据。
11、Mutations
在Vuex中,官方规定我们只能通过mutations中的方法来对state中的数据进行操作(只能进行同步操作),这样可以避免数据管理时的紊乱。在mutations的方法中,我们通常传入两个参数,第一个参数是state,表示原本存储的数据;第二个参数是更新的数据,我们通过赋值的方式对原本存储的数据进行修改。
12、vuex的actions和getters和modules
在Vuex中,由于mutations只能进行同步操作,无法执行异步操作,因此我们通常使用actions来进行异步请求,并在actions中调用mutations中的方法。actions中的函数可以接受两个参数,第一个参数是context,表示上下文的意思,我们通过$context.commit的方式来调用当前store对象中的mutations方法。第二个参数是传递的数据。
getters是对state中的数据进行筛选,并将筛选后的返回值进行返回。我们通常使用其为数据建立一对一的映射关系,方便的页面中便捷的获取数据。
modules是Vuex中的模块化,我们可以将不同组件中的数据封装在不同的store模块中,并且为每个模块开启namespaced,然后在统一导入、导出的模块中通过modules对象来整合所有模块的信息。
13、Vuex的总结
Vuex是一个集中式的状态管理工具,包含state、mutations、actions、getters、modules等核心模块。
我们在实际开发的过程中,通常使用模块化的思想来封装数据。在每一个模块化的store对象中,我们使用state来保存数据,通过mutations来操作存储的数据,通过actions来进行异步的数据请求,通过getters来对state中的数据进行筛选或者建立一对一的便捷映射关系,在通过modules来整合不同的模块数据。
需要注意的是,在mutations中我们通常传入两个参数,第一个是state,第二个修改的数据。在actions中我们通常传入两个参数,第一个是context(表示上下文),第二个时传入的参数。
此外,在实际开发中,我们通常在组件的computed属性中使用mapState或者mapgetters配合扩展运算来获取数据,在methods中使用mapMutation或者mapAction来获取方法。
14、Data为什么是一个函数
data是用来定义组件数据的,我们在封装组件的时候,每一个对象都是一个对象。如果data不是一个函数,而是组件对象上的一个属性,那么当这个组件被不同父组件多次调用的时候,所有父组件操作的都是同一个data数据,会造成数据的紊乱。
因此我们将data定义为一个函数,并通过返回对象的方式来返回数据。这样,每当组件被调用并且操作数据的时候,每个组件都会创建一个独立存储空间来保存数据,进而避免数据的紊乱。
15、组件通信ref
我们可以通过ref来获取一个结点的对象。在组件通信的过程中,我们可以给子组件的标签上定义一个ref属性,并且在父组件中通过this.$refs的方法来获取到子组件的对象,进而获取其中的数据和方法。但是这种方法是在直接操作DOM,会造成大量的计算,我们并不推荐这样的方法。
16、组件通信的$children和$parent
在组件通信的过程中,我们可以使用$children的方式来当前组件中的所有子组件对象,进而对这些子组件对象的数据或者方法进行从操作。
而$parent表示当前组件的父组件,我们通过这个方法来对父组件中的数据个方法进行操作,但是在实际开发中我们并不推荐这样使用,因为如果一个组件被多个父组件所引用,那么$parent就找不到对应的父组件,从而造成紊乱。
17、组件通信的eventBus
eventBus可以实现跨组件级的通信,我们可以在src文件夹下创建一个eventBus.js文件,在文件中创建一个Vue实例,并命名为EventBus导出。在发送数据的组件中通过eventBus.on来接收数据。
其实eventBus是订阅发布者模式的应用,EventBus就是中间平台的事件频道,数据的发布者和订阅者分别是数据的发送方和接收方。
18、发布订阅者模式
18.1、定义
订阅发布者模式是一种一对多的数据依赖关系,当被依赖的数据发生变化,会通过事件频道来进行数据通信,并将变化后的数据通知给所有依赖者。其中,我们称被依赖者为发布者,称数据依赖项为订阅者。
18.2、与观察者模式的区别
他与观察者模式的区别在于,观察者不需要中间的事件频道,被依赖项与依赖项之间直接通信,彼此知道相互的存在。而发布订阅者是需要通过中间的事件频道才能实现通信,彼此之间不能直接通信,也不知道彼此之间的存在。
18.3、原理
发布订阅者模式的实现原理是在事件频道中创建一个$emit和$on方法,以及一个存储订阅者的对象。当有数据被依赖的时候,会触发$on的方法,我们可以在这个方法中将订阅者自定义的方法名作为属性名,属性值为一个数组,并将自定义的方法存储到这个数组中。
当被依赖的时候发生变化的时候,就会调用$emit方法,我们在这个方法中匹配订阅者对象中的对象数据项,并遍历调用数组中存储的方法,从而将数据进行传递。
19、浏览器的缓存机制
浏览器的缓存分为强缓存与协商缓存两种形式。
当我们第一次打开网页的时候,浏览器会执行强缓存。将后台返回的数据强制缓存至本地,并在返回的数据中添加时间戳用于判断该数据的有效期,通常是使用expries或者cache-control,前者是一个绝对时间,后者是一个相对时间,当有后者时,优先使用后者。
当我们第二次打开这个网页的时候,首先判断expries或者cache-control是否超过优先时间,如果没有超过,则执行强缓存,读取本地的数据。如果超过了,则执行协商缓存,此时,浏览器会向后端发送一个带有ETag和last-modified的请求,前者表示当前数据的唯一标识,后者表示该数据最后一次更改的时间。后端在接受到请求以后,会对缓存是否真的过期进行判断。如果真的过期,则返回最新的数据;如果尚未真的超时,则继续执行强缓存机制。
20、数据响应式
20.1、定义
数据响应式是指当组件中data的数据发生变化,页面中的视图随之变化。
20.2、原理
我们可以通过Object.defineProperty的方法可以对一个对象的原型进行修改。我们可以借助其为一个对象的原型对象上添加set和get的方法,当读取这个对象的属性时,就会触发get方法,并将get方法中return的数据返回。当对对象中的属性值进行修改的时候,就会触发set方法。因此,我们可以通过Object.key.forEach配合递归,深层次遍历对象中的每一个属性,为每一个属性添加set和get的方法,当触发set方法的时候,在修改数据的同时,通过document.getElementById等方法对页面中的视图进行同步,进而实现数据响应式。
事实上,VUE的数据驱动页面渲染,也是通过数据响应式+观察者模式来实现的。Vue会遍历所有对象的property,并通过Object.defineProperty将其转化为getter和setter。并且每一个组件都会对应一个watcher,这些watcher会将接触过的数据的property作为依赖项,当依赖项的setter被触发的时候,就会通知watcher,从而实现数据驱动页面渲染。
20.3、Vue中数据不响应的情况
在Vue中有两种情况,数据是不响应的。第一种是我们为某个对象类型的数据添加新的属性,第二种是单独修改数组类型中的一个数据。
出现第一种情况的原因是Object.defineProperty已知的缺陷。它只能对现有的属性进行劫持,不能劫持数据新增的属性。当我们的页面被创建时,组件中的数据就已经被劫持,故而我们再单纯的去添加新的属性,是不会被劫持到的,自然也就不会触发数据响应式。
出现第二种情况,是因为Vue的官方团队舍弃了这一功能。尤大大在Vue的官网中,曾回答过网友的这个问题他说是因为数组的长度是不固定的,因此“性能代价与用户体验不成正比”,故而舍弃。
21、观察者模式
21.1、定义
观察者模式是指一种一对多的数据关系,当一个数据被多个对象所依赖,被依赖项的数据发生变化时,就会通知所有依赖项也随之变化。我们称被依赖项为被观察者,依赖项为观察者。
21.2、与订阅发布者模式的区别
与订阅发布者不同,观察者模式中,当数据发生变化的时候,由被观察者直接通知观察者进行数据更新,两者知道彼此之间的存在。
21.3、原理
我们可以在被观察者上定义两个函数以及一个数组,在观察者上定义一个回调函数。但有数据被依赖的时候,就触发观察者上的第一个函数,将观察者对象保存到数组中。当被依赖的数据发生变化的时候,就触发第二个函数,遍历数组中的每一个对象,并且调用这些对象上的回调函数,用于传递数据。
21.4、应用
在Vue中数据驱动页面渲染就是利用数据劫持+观察者模式来实现的。Vue的官网中说,每个组件有一个与之对应的Watcher实例,这个实例会将接触过的所有数据的property作为依赖项,当依赖项发生变化的时候,触发观察者模式,进而实现数据驱动页面更新。
22、虚拟DOM和Diff算法
虚拟DOM的本质一个JS对象,他是用来描述我们在页面中看到的节点元素,其作用是抽象数据的渲染过程,使其具备跨平台开发的能力。在Vue中,Vue官方团队利用虚拟DOM+diff算法,巧妙的提高了页面渲染的效率。
当页面第一次被创建的时候,会首先生成一个虚拟DOM树,然后将这个虚拟DOM树挂载到指定的结点形成真实DOM树。当组件中的数据发生变化时,也会生成一个新的虚拟DOM树,然后通过DIff来判断是否利旧现有的结点元素。因为我们知道,直接操作真实DOM会引起页面的重绘与回流,造成大量的计算,性能代价很大;而通过虚拟DOM+Diff算法,可以有效提高渲染的效率。
那么Diff算法是如何实现的?Diff是一种广度优先、逐层比较的算法。当页面中有V-for与key的时候,Diff会以key作为依据,当key没有发生变化的时候,就复用现有的结点,仅对数据进行更新;当key发生变化的时候,就重新创建一个新的节点。当页面中没有V-for与key的时候,DIff算法会首先比较元素节点的模板是否发生变化,如果变了则创建新的,没变则仅对数据进行更新。
23、同步与异步
我们知道JS是单线程执行的,即处理完一个任务再执行另一个任务。这些依次排队执行的任务会进入主线程执行,称之为同步任务。但是有一些任务所需的时间过长,会造成等待时间过长。因此,JS会将一些任务放到一个任务队列,将这些任务交给它的寄主环境(浏览器)来执行,当浏览器执行完毕的时候,再通知JS来执行回调,这些交由浏览器来执行的任务称之为异步任务。
24、宏任务与微任务
在异步任务中,又可以分为宏任务与微任务,宏任务是指参与浏览器循环机制,由浏览器来执行的异步任务。微任务是指不参与浏览器循环机制,有JS引擎来执行的异步任务。常见的宏任务有setTimeout、setTimeinterval等,常见的微任务有Promise.then中的回调、await后面的异步函数等。
至于宏任务与微任务那个先执行,这个问题存在一些理解上的歧义。如果我们将特殊的代码块看做是宏任务,那么就是首先执行宏任务,然后再执行微任务。或者说,我们从浏览器的循环机制来描述的话,浏览器会首先执行主线程中的同步任务,然后检查是否有异步任务需要执行,在这些异步任务中,有微任务就先执行微任务,没有微任务则执行宏任务,如果这个宏任务中又包含微任务,同样的也先执行微任务,之后进入下一个事件循环。
25、Promise
Promsie的本质是一个对象,它是用来返回一个异步函数的执行结果或者返回值的,当然,它也解决了层层嵌套地回调地狱的问题,让我们可以按照同步编程的思想来实现异步操作。
promise有三种状态:待定态、成功态与失败态。它是根据返回的有无以及结果来判断的,当Promise中的异步操作尚未被执行,此时处于待定态,我们不知道异步操作会成功还是会失败;当异步操作执行成功的时候,会触发resolve()来将成功后的结果进行返回,这个方法可以将成功的结果返回到then中,作为下一个回调的参数传入,此时处于成功态;当异步执行失败的时候,会通过reject()来将失败后的结果返回,此时处于失败态。
至于promise如何解决回调地狱,让我们通过同步编程的思想来实现异步操作。是因为在promise.then中,如果没有返回,则默认返回一个promise对象。如果有返回值,但不是promise,则通过resovle的方式将结果返回。如果返回值就是一个promise对象,则返回这个promise对象。这就使得我们可以使用链式编程的思想来使用.then,即.then只有继续.then,这些.then会依次执行下去。这样就实现了以同步编程的思想来实现异步操作,并且解决了回调地狱的问题。
26、Promise的静态方法
promise常用的静态方法有4种。
- promise.all用于一次发起多个异步请求,并将请求的结果全部返回,但是只要有一个结果触发了reject(),就会导致promise进入失败态。
- promise.allSettlesd与promise.all类似,但是无论成功与否,它都会将所有的结果进行返回。
- promise.race用于一次发起多个请求,并将第一个返回值进行返回,无论这个返回值是成功还是失败。
- promise.any与promise.race类似,但它返回的是第一个执行成功的结果。
27、Async和await
async和await是Generator函数和yield的语法糖写法,他是用来控制函数的执行过程,也可以用于以同步编程的方式操作异步函数。我们通常在异步函数的前面添加await,并在外层的包裹函数前面添加async。这样await后面的代码会等await的异步操作执行完毕之后才开始执行。
28、Generator函数
Generator函数可以让我们将一个函数设置为暂停态。我们在使用的时候,在function和函数名之间使用*声明Generator状态。并在函数内部需要暂停的地方使用yield关键字来实现暂停。这样函数在运行的时候,执行到yield关键字的地方就会暂停,并返回一个Generator对象,通过调用Generator对象上的next()方法可以让函数继续执行。
29、通过yield向外传值
当函数运行到yield的位置时,会通过yield向外传递一个Generator对象,可以通过next()来接收这个对象。这个对象中包含两个键值对,第一个键值对是value,表示函数当前返回的结果;第二个键值对是done,是一个布尔型数据,表示当前的函数是否执行完毕。
30、Generator函数向内传参
Generator函数可以通过next()向内传参,这个参数会传递到上一次暂停的yield关键字的位置。但是需要注意,第一次的next()不能进行传参,因为第一次的参数是在函数被调用的时候进行传参的。
31、实现async和await的效果
Generator函数配合promise可以实现async和await的效果,这也是async和await的原生写法。具体实现原理是在Generator函数中yield关键字后面new Promise(),在Promise中执行异步操作。然后在函数被调用的时候,使用递归的方式通过next().value.then()的方式来调用。当然,也可以借助co库自动执行。
32、async和await和Generator的对比
async和await实际上是Generator函数的语法糖,它是将Generator函数与自动执行器包装到一个函数中,从而让使用更加便捷。他的有点主要有3点:
- 内置自动执行器:将Generator函数和自动执行器打包在一起,使用的时候只需要一行代码。
- 语义化更好:async和await的语义化更加明确,例如await异步等待,使得代码的可读性更加。
- 可适用性更好:yield关键字后面只能使用thunk函数或者promise对象。但是await后面可以使用promise对象或者简单类型的数据,使其适用性更佳。
33、Thunk函数
Thunk函数就是接收一个参数,并且return一个回调函数,在回调函数中接收其余的参数,并且在回调函数中实现我们真正想要实现的效果。这样我们在调用thunk函数的时候,可以用一个变量接收thunk函数的返回值,此时这个变量也就变成了一个函数,我们可以给这个新的函数继续传值,进而实现异步调用的效果。
34、深拷贝与浅拷贝
深拷贝与浅拷贝是相对复杂类型数据而言的,因为简单类型的数据在拷贝赋值的时候,是直接将新的数据赋值在栈中,不存在引用的问题,因而也就不存在深浅拷贝的问题。而复杂类型的数据在拷贝赋值的时候,是在栈中存储一个指向堆中的地址,真实的数据是存储在堆中的。
那么当我们使用直接赋值,或者object.assign()等方法进行操作的时候,只是将原数据的堆地址赋值在新数据的栈中,此时就是浅拷贝,这回引发一个问题,当我们对新数据进行修改的时候,原数据也会发生变化,因为两者指向的数据存储空间是同一个,这会造成数据的紊乱。
而深拷贝则是为新的数据开辟一块堆空间,将原数据的数据值拷贝到这个新的数据空间,从而避免新旧数据间的相互影响。例如:我们可以通过JSON的方式来实现,但是这个方法对方法无效;也可以使用递归+遍历的方式深度拷贝数据;或者通过lodash库中的clonDeep()实现。
35、Lodash的使用
loadsh是一个函数库,内置很多使用的方法,例如深拷贝的cloneDeep()方法等。
36、闭包的基本原理与缺点
闭包是指一个函数在局部作用域中可以访问另一个函数的局部作用域的现象。例如:高级函数就是一个闭包。 闭包的实现原理是作用域链原理,当一个变量在子级作用域中查不到的时候,就会沿着作用域链查找父级作用域内师傅存在。我们将一个函数作为另一个函数的参数或者返回值,或者在一个函数内部定义另一个函数,这样就可以利用作用域链原理实现闭包。
闭包的现象可以延长一个变量的用于范围,使数据变量私有化,防止变量的污染。但是这也带来一个问题,可能会引起栈溢出,因为这要闭包没有执行完毕,这个变量就不会被销毁,知道闭包的函数全部执行完毕。
37、垃圾回收机制
垃圾回收机制分为2中:
引用计数法:当一个变量被另一个变量所引用的时候,就将标记+1,当这个引用关系解除的时候,就让标签-1,这样,如果一个数据被引用,他的标记就>0,不会被清除。当一个数据的标记为0的时候,就会触发垃圾处理机制,将这个变量销毁。但是,这种方法存在一个弊端,即两个变量相互引用,那么就永远不会被清除。
标记清除法:我们将全局变量称为根节点,所有从根节点能够访问到的数据将被标记,这些标记是被全局变量所引用的,不会被销毁,没有被标记的,就是没有引用的,就会被销毁。
38、地址栏敲回车发生什么
当我们在地址栏敲下回车的时候,浏览器会根据域名,首先在本地寻找有无IP地址与之映射,如果没有的话,在向DNS服务器发送请求,将域名转化为具体的IP地址。
之后向IP地址指向的服务端发送数据请求,服务端在接受到请求之后,会对请求进行处理,之后返回数据。
返回的数据中包含HTML、CSS、JS等文件。浏览器会对HTML、CSS进行解析,分别形成HTML树以及CSS样式树,再将两者合并形成renderTree渲染树,之后判断是否需要触发回流机制,计算页面盒子的大小及位置,最后进行重绘,将页面渲染出来。
39、自定义指令
自定义指令是我们在Vue中自定义的一些命令,我们可以在src下main.js文件中通过Vue.directive的方式来生命全局自定义指令,也可以在组件中注册局部自定义指令。自定义指令中包含4个钩子函数:
- bind()函数,在指令与元素挂钩的时候触发,只会执行一次。
- inserted()函数,当元素插入到指定结点的时候执行,只执行一次。
- update()函数,当数据更新的时候被触发,可以多次执行。
- unbind()函数,在指令与元素解绑的时候触发,只触发一次。
40、组件通信
组件通信是指组件之间的数据传递,常用的方法有8种:
$emit :
通常使用在子向父传递参数,在子组件中通过$emit的来自定义方法来接收两个参数,第一个是事件名称,第二个是传递的数据。并且在父组件中使用@事件名的方式来绑定函数接收数据。
$patent:
我们通过$parent可以获取到当前组件的父组件对象,进而可以调用父组件的data和methods。但是这种方式有个弊端,如果当前组件被多个父组件所引用,可能会导致获取对象异常。
$children:
我们可以通过$children的方式来获取当前组件中的所有子组件,这个方法会获得一个数组,数组中的元素是子组件的对象。我们可以通过这个方法来获得子组件对象,进而操作它的数据和方法。
eventBus:
这个方法时利用了订阅发布者模式来实现,我们在src文件夹下创建一个JS文件,并返回一个Vue对象。在这个发布数据的组件中引入这个文件,并使用on的方式来接收数据。但是,在实际的开发中,我们通常不适用这种方式。
props:
我们在父组件中通过赋值属性的方式向子组件来传递参数,在子组件中通过props来接收,props可以是一个数组,也可以是一个对象,在对象模式的使用中,可以通过request、default等属性依次定义是否必须以及默认值。但是props是一个只读属性,我们不可以在子组件中直接对props中接收到的数据进行修改。
v-model:
通常用于父子组件间的数据双向传递,是一种语法糖的写法。他的原生写在子组件的标签上通过v-on+v-bind来实现数据的双向传递。
sync
sync的作用与v-model相同,也是用于数据的双向绑定。但不同的是,在子组件中,我们通过$emit('update:数据名',数据值)的方式来对数据进行更新。
Vuex
Vuex是一种集中式的状态管理工具,我们可以在src目录下通过vue.use的方式将Vuex注册为全局使用,通过mutations来修改数据,通过state或者getters来获取数据。
41、Var、let和const的区别
var与let和const不同,Var可以声明已经声明过的变量,并且后者会覆盖前者,并且存在变量提升,也没有块级作用域的限制。但是这种方式在实际开发的过程中应该避免使用,因为并不严谨。
let和const都不可以进行重复声明,let适用于声明可变变量,变量声明之后可以进行修改。const是声明不可变的变量,对于已经声明的引用型变量,除非整体修改,否则会报错,对于简单型数据不可变。
42、Webpack
Webpack是一款JS工具,可以实现语言的处理、降级、打包、压缩等功能。主要的核心模块有4个:entry、output、loader和plugin。
entry是项目打包的主入口,我们将需要打包的文件直接或者间接的与入口挂钩,我们可以在webpack.config.js中进行自定义配置。
output是打包的出口,我们同样可以在webpack.config.js中通过path来自定义打包后文件存储的路径(注意,需要是一个绝对路径),通过filename来自定义打包后的文件名。
loader是webpack的依赖项,因为webpack默认只能处理JS语言,因此需要loader来协助处理css、scss等语言。常用的loader还有bable-loader(用于语法降级)、file-loader(压缩图片)、style-loader(将处理后的css插入行内样式)、postcss-loader(用于处理rem、em之类的css语言)。
plugin是webpack的插件,常用的有html-webpack-plugin(用于指定打包的模板),mini-css-extract-plugin(用于将css文件打包成独立的文件夹)。
43、处理数组的方法
数组常用方法可以按照增删改查进行分类。
增:
- push()在数组的最后添加元素
- unshift()在数组的最开始添加新的数据
- spilce()在指定位置插入新的元素
删:
- pop()删除数组最后的数据
- shift()删除数组最前面的数据
- splice()在指定的位置,删除指定长度的数据
改:
- forEach()是循环遍历数组中的每一个元素,并对原数组进行操作。
- map()也是循环数组中的每一个数据,但不会对原数组进行操作,而是克隆出一个新的数组,对新的数组进行操作,并将操作后的数组进行返回。
查:
- filter()是筛选过滤数组,将筛选后的数据以新数组的形成进行返回。
- every()是根据条件对数组中的数据进行判断,当每一个数据都符合条件的时候,才返回true。
- some()是根据条件判断数据总是否包含满足条件的数据,当遇到第一个满足条件的数据时,会停止遍历,并且返回true,如果全部遍历完,没有符合的,则fanhuifalse。
- includes()查找数据中是否包含某个数据,返回布尔值。
- find()查找元素,将第一个符合条件的数据返回
- findIndex()查找元素,并将第一个符合条件的数据的下角标进行返回,如果没有,则返回-1。
44、浏览器的本地存储
浏览器的本地存储分为5种:
- localStorage:容量较大,存储的持久,只要不手动清空,即使关闭浏览器也不会清空。
- sessionStorage:容量先对较大,但是是会话级的存储,单页面刷新的时候,数据就会被清空。
- cooike:私密性较好,但是容量较小,适合存储是密性的信息
- WebSQL:已经废弃,通常不使用
- IndexDB:相当于浏览器的数据库
45、改变this指向
改变函数this的方法有:
- call():第一个参数是改变this的指向,后面的参数是传入的形参,一次性改变
- apply():第一个参数是改变this的指向,第二个参数是一个数组,里面的数据是传递的参数,一次性改变
- bind():创建一个新的函数,将新函数的this进行永久性的改变。
46、new的过程
new的过程分为4步:
- 首先创建一个新的存储空间
- 将构造函数中的this指向这个新的空间,并将新空间的原型指向构造函数的原型对象
- 执行构造函数中的代码,为新创建的对象实例进行赋值操作
- 将新创建的对象实例进行返回
47、静态成员与实例成员
实例成员是在构造函数中通过this添加的成员,这些成员只能通过实例对象进行访问。 静态成员是在构造函数上添加的成员,这些成员只能通过构造函数来访问,在实例对象上没有办法访问
48、为什么需要构造函数
构造函数可以提高代码的复用性、减少代码量。例如,我们通常将一些公用的属性和方法封装到构造函数中,之后通过构造函数传参的形式批量、快捷的创建对象。
49、原型链
我们可以通过构造函数来创建实例对象,例如在构造函数中定义属性和方法,再通过new的方式来创建,但是这样有一个弊端,构造函数中的方法每当创建一个对象的时候,就会在内存中开辟一个空间用于存储这个方法,造成内存的极大浪费。因此我们通过原型对象中添加方法,这样所有的对象都会公用这个原想对象上的方法,不会开辟新的内存。从而节约内存。每个实例对象上都有一个__proto__的属性指向原型对象,而原型对象上又有一个construct的属性指向构造函数,这样就形成了一种三角形的关系。
而原型对象的本质也是一个对象,因此这个原型对象也有自己的构造函数和原型对象。它的构造函数就是Object,它的__proto__属性指向Object的原型对象。但是这个Object的原型对象的__proto__属性指向null。这样就形成了一个链式结构,我们称之为原型链。一个实例对象可以通过原型链来查找原型链上年的属性及方法。
50、继承
继承是指子类通过某种方式来获得父类的属性及方法。常见的继承有5种:
原型链继承:我们让子类的原型对象指向父类的实例对象,再将原型对象的construct属性指回构造函数。这样就使子类继承了父类的方法,但是无法继承属性。、
构造函数继承:我们在子类的构造函数中通过call等方法调用父类,从而继承父类的属性,但是无法获得他的方法。
组合式继承:我们使用原型链继承+构造函数继承的方法来实现子类对父类属性和方法的继承。
寄生组合式继承:与组合式继承类似,但是我们让子类的原型对象赋值为一个Object.create()方法,方法内传入父类的原型对象,这样就以父类的原型对象为原型创建了一个新的对象,实现寄生是组合继承。
extend关键字继承:通过class与extend关键字实现继承,这是一种新增的语法糖,写法简单,使用率较高,我们只需使用class Son extend Father既可以实现继承。
51、盒子的水平居中
postion:
通过postion进行绝对定位,将left设置为50%,此时盒子会处于中间偏右的位置,然后可以通过translate的transform方法将盒子向左移动自身的50%。
flex:
通过将父盒子的display设置为flex布局,然后将Jc属性设置为center实现盒子的水平居中。
margin:
给盒子设置margin:0 auto;的属性,从而实现盒子的置顶水平居中。
postion+left+right:
给盒子的position属性设置为绝对定位,然后将left和right都设置成0,这样就将盒子挤到中间位置
52、盒子模型
盒子模型有2种:
W3C标准盒子模型:
标准盒子是由版心宽高、内边距、边框、外边距组成。盒子的实际宽度=宽度+内边距+边框,这样的盒子的box-sizing属性是content-box。
IE盒子/怪异盒子模型:
盒子的box-sizing属性是border-box。盒子开启了内减模式,盒子的width就是他的实际宽度。
53、Flex1
flex是一种符合属性,包含flex-grow、flex-shark以及flex-basis三个属性。
flex-grow是当所有子盒子的宽度小于父盒子的宽度时,子盒子将按照flex-grow设置的属性值为比例,平分父盒子的剩余空间。
flex-shark是当所有子盒子的宽度大于父盒子的宽度时,子盒子将按照flex-shark设置的属性值为比例,较少超出的空间。
flex-basis是设置盒子的宽度,他的优先级比width要高,当有flex-basis的时候,width属性不生效。
因此,flex1的意思是指:flex-grow:0+flex-shark:0+flex-basis:0%
54、C3的新属性
- box-sizing
- background-size
- border-radue
- flex
- 等
55、bfc的理解
BFC是指块级格式化上下文,是指给当前盒子开启一个独立的渲染空间,使其不受其他环境的影响。BFC模式常用于解决清除浮动、盒子塌陷等问题。触发BFC的模式有:display不为float、overfloew设置为hidden、position设置为absolute。
56、bfc的使用
BFC是指块级格式化上下文,是指给当前盒子开启一个独立的渲染空间,使其不受其他环境的影响。BFC模式常用于解决清除浮动、盒子塌陷等问题。触发BFC的模式有:display不为float、overfloew设置为hidden、position设置为absolute。
57、数据类型
数据可以分为简单数据类型与复杂数据类型:
简单数据类型是指数据直接存储在栈空间中的数据,常见的简单型数据类型有数字、字符串、布尔型、null、undefind和Symbol等。简单数据类型在赋值的时候,直接将数据赋值在栈空间中。
复杂数据类型是指数据存储在堆空间中,在栈空间中仅存储一个指向堆空间的地址,常见的复杂数据类型有数组、对象和函数等。复杂类型数据在赋值的时候分为深、浅拷贝,都是只在栈空间中赋值一个地址,区别是地址指是否指向原数据的堆空间。
58、数据类型的检验
常见的检测方法有3种:
typeof: 只能检测简单数据类型,因为对于数组以及null的检测结果都是Object。
instanceof: 该方法是检测一个对象的原型链上是否包含某个原型对象,因此只能用于复杂数据类型的检测。
Object.prototype.toString.call: 这种方法是改变Object原型上toString方法的指向,用于将被检测数据的类型以字符串的方式显示出来。
59、subStr和subString的区别
两者都是用于截取字符串,当只有一个参数的时候,效果相同。但是如果有第二个参数,则不相同。
- substr():第一个参数是指从第几位开始截取,第二个参数是指截取字符串的长度。
- subString():第一个参数是指从第几位开始截取,第二个参数是指截取到第几位字符。
60、slice和splice的区别
slice():是用于截取字符串或者数组,他有两个参数,第一个是从第一位开始截取,第二可选,表示截取到第几位。这个方法不会对原数据进行操作,会创建一个新的数据,并将操作后的数据进行返回。
splice():可以用于添加或者删除数组中的数据,会对原数据进行操作。他可以接收多个参数,第一个参数表示从第几位开始操作,第二位表示参数几个数据(当不需要删除的时候写0),后面的参数可选,表示新增的数据。
61、作用域预解析
JS的执行过程分为2步,第一步先进行预解析,第二步再逐句执行。
- 对于变量,会将变量提升到当前作用域的最前面进行声明,但是并不赋值。
- 对于函数,会将函数提升到当前作用域的最前面进行声明,但是并不调用。
62、Reduce的使用
Reduce函数就是将上一次计算结果作为本次循环参数执行的一个函数,经常用于数组求和等场景使用。他就收两个参数,第一个参数是一个函数,表示循环项执行的语句,这个函数接收两个参数,第一个是上一次计算的结果,第二个是当前的循环项。二个参数是上一次结算结果的初始值。
63、Es6新增
ES6新增的方法有:let和const、解构运算、模板字符串、startWith()、endWith()、includes()等,class和extend语法糖,箭头函数、函数形参的默认值等,Object.assgin()、Object.create()等,promise等。
64、Es6新增字符串方法
模板字符串: 通过` `配合${}来进行字符串的拼接。${}中可以书写一个变量,或者执行算数及三元运算,最终会形成一个字符串。
startWith(): 用于判断一个字符串是否以指定字符开头。
endWith(): 用于判断一个字符串是否以指定字符结尾。
includes(): 用于判断字符串是否包含指定字符串,返回布尔型。
65、es6新增对象方法
对象的简写: 当对象内的属性名与属性值相同时,可以只写变量名。
Object.assgin(): 将一个或者多个对象拷贝到指定的对象内。如果属性名相同,则会被覆盖。
Object.create(): 以一个对象为另一个对象的原型对象进行新对象的创建。
66、es6模块化规范
在es6模块化规范产生之前,已经存在AMD、CMD、commonJS模块化规范,这些模块并不统一。因此我们创建了es6模块化规范。
该规范中规定,每一个模块都是一个独立的JS文件,通过export来暴露对外共享的数据,通过import来接收。对于导出有2中方式:一种是通过export default来进行默认导出,通过import+变量名来进行接收;另一种是通过export+变量名的方式按需导出,通过import {}的方式来进行接收,注意,此时{}中的变量名需要与默认导出的变量名相同。
67、封装组件的思想
我们在封装一个组件的时候,通常会将公用的HTML结构、CSS样式及数据单独封装到一个Vue文件中,并将其注册为组件使用。但是我们需要注意,在封装HTML结构的时候,我们通常会使用slot占位符来保证结构的灵活性,通过CSS中的变量声明及混入来保证样式的灵活性,通过组件通信、插槽表达式或者动态属性等保证数据和方法的灵活性。
68、CommonJS模块化规范
CommonJS模块化规范规定,我们需要将每一个组件独立的封装到一个JS文件中,在这个文件中module表示表示当前文件的模块,我们通过medule.export来对外暴露我们想要共享的数据,在引用的文件中我们通过require的方式来进行引用。
69、路由守卫
路由守卫分为3种:全局守卫、路由守卫以及单个组件路由守卫。
全局守卫:全局内所有的路由都会触发的钩子函数,分为beforeEach(路由跳转前触发)、afterEach(路由跳转后触发),我们可以在创建的router实例对象上直接添加该函数,例如:router.beforeEach(),该函数接收三个参数,to、from和next(),to表示要去向的地址,from是从哪个地址跳转过来的,next()是下一步要执行的操作,例如跳转路径等。我们通常在跳转前判断是否有token,进而实现登录页的跳转。
路由守卫:路由守卫是在路由规则中进行配置的,分为beforEnter和afterEnter。该函数在某一页面的路径跳转的时候被触发,用法与全局守卫中的钩子函数类似。
组件路由守卫:组件路由守卫是一个在组件内添加的钩子函数,相当于为组件添加了一个和生命周期。
70、响应拦截
响应拦截是指在发送axios请求之后,axios在接收到相应的时候,会首先对数据进行拦截,然后根据我们在axios对象中的配置规则,对响应的数据进行处理。我们可以在axios的配置文件中,通过axios.interceptors.responed.use来对响应拦截进行处理,这个方法会接收两个参数,一个是响应成功之后调用的方法,另一个是响应失败之后调用的函数。我们通常在此处对响应错误进行统一处理,并对接收到的数据进行解构处理,同时还可以在此处对token过期的情况进行处理。
71、权限控制(动态路由、动态菜单、权限按钮)
权限控制分为3个层面:动态路由、动态菜单、权限按钮
动态路由:不同的人员能够进入的路由是不同的。我们可以将路由规则分类静态路由和动态路由两种,静态路由是最后可以向用户展示的路由,包含默认都可以访问的路由,例如:登录页、404页面等;动态路由是指需要特定权限才可以访问的页面。我们在登录之后,后台会传给我们一个权限点,里面包含里该用户可以访问的权限信息,我们可以遍历这个权限点,利用他对动态路由进行筛选,筛选出用户可以访问的路由,并将其添加到静态路由中进行展示。
动态菜单:动态菜单是指根据用户可以访问的权限不同,动态的渲染可以展示的菜单项目。我们可以先创建一个数组,用于存储可以展示的菜单栏信息,让菜单栏通过循环渲染这个数组的方式来渲染。而数组中的数据,是通过全局路由中的静态路由数组来得到的。这样就实现了动态菜单栏与动态路由的统一。
权限按钮:权限按钮是指不同的用户对于同一个按钮是否可以操作或者是否可见的情况是不同的。我们可以在用户登录的时候,由后台返回一个用户权限点的信息,这个信息中包含了用户可以操作的按钮的权限点。在页面渲染的时候,我们通过循环用户的权限点判断用户是否具备该按钮的操作权,从而设置该按钮是否可以显示或者编辑。
72、RBAC权限设计模型
RBAC权限设计是指基于角色的访问控制,包含三个要素,人员、角色与权限。我们可以抽象出不同的角色,为这些角色添加权限,然后再为不同的人员分配不同的角色,这样就减少为每个人员直接赋值权限从而产生重复的操作。
RBAC权限设计基于三个重要的安全原则:最小权限原则,即为人员添加必要的最少的角色。责任分离原则,每个角色的责任相互独立,通过不同角色合作的方式实现复杂的工作。数据抽象原则:每个权限的数据进行抽象,而不是简单的赋予增删改查的权限。
RBAC的优点在于简化了用户与权限之间的关系,易扩展、易维护。缺点在于没有操作顺序的控制机制。
73、浏览器的渲染机制
浏览器首先将HTML、CSS文件解析为虚拟DOM树和CSS样式树,然后将两颗树合并成渲染树,在HTML文件解析的过程中,如果遇到JS对DOM进行操作,则会终端对MTHL的解析。当两者合并为渲染树之后,会判断是否需要触发回流机制,之后再触发重绘机制,从而实现浏览器页面的渲染。
74、浏览器的回流与重绘
回流是指:根据样式计算盒子的尺寸与位置。
触发回流的形式有:
- 页面的初次渲染
- 盒子的尺寸大小发生变化
- 盒子的位置样式发生变化
- 盒子中的内容或图片发生变化 等
重绘:根据回流的结果,将盒子渲染在页面中。
触发重绘的形式有:
- 回流会引起重绘
- 盒子的阴影发生变化
- 盒子内容的方向发生变化
- 等
75、浏览器的缓存机制
浏览器的缓存分为强缓存与协商缓存两种形式。
当我们第一次打开网页的时候,浏览器会执行强缓存。将后台返回的数据强制缓存至本地,并在返回的数据中添加时间戳用于判断该数据的有效期,通常是使用expries或者cache-control,前者是一个绝对时间,后者是一个相对时间,当有后者时,优先使用后者。
当我们第二次打开这个网页的时候,首先判断expries或者cache-control是否超过优先时间,如果没有超过,则执行强缓存,读取本地的数据。如果超过了,则执行协商缓存,此时,浏览器会向后端发送一个带有ETag和last-modified的请求,前者表示当前数据的唯一标识,后者表示该数据最后一次更改的时间。后端在接受到请求以后,会对缓存是否真的过期进行判断。如果真的过期,则返回最新的数据;如果尚未真的超时,则继续执行强缓存机制。
76、反向代理与正向代理
正向代理:在客户端与目标服务器之间架设代理服务器,客户端的请求会发送到代理服务器上,由代理服务器将这个请求发送到目标服务器。这样,目标服务器就不知道发送这个请求的真实客户端,常用于翻墙。
反向代理:在服务端架设代理服务器,请求返回的数据会先返回到代理服务器,然后代理服务器将这些数据专递到内部网络,从而避开浏览器的同源机制,将这些数据返回客户端。常用于跨域请求。
77、监听属性与计算属性
监听属性是对已经声明的变量进行监听,当变量的数据发生变化的时候,会触发,并且拿到数据变化前后的数据。当属性没有缓存,可以执行异步操作。
计算数据是根据已有变量进行运算,并将返回的结果作为新的变量进行声明。该方法具有缓存,在计算结果没有发生变化的时候,不会重复计算,会从缓存中读取数据。之后当计算的结果发生变化的时候,才会进行计算。
78、如何实现三角形
通过为一个盒子设置边框,在需要保留的一侧设置颜色,不需要保留的将颜色设置为透明。
或者使用现有的库的icon图标,例如element等
79、如何实现0.5px
在CSS中没有0.5px,但是我们可以现将其设置为1px,在通过tranform:scroal(0.5)的方式将其缩放为0.5px的效果。
80、provide与inject注入依赖
我们可以在祖先组件中使用provide函数来注入依赖项数据,并且在后代组件中通过inject属性的方式来使用这些依赖的数据。这个方法的好处是,我们在祖先组件中注入依赖项之后,可以在所有后代组件中使用依赖项的数据。rovide函数中不仅可以传递数据,还可以传递函数,用于在后代组件中调用传参,从而实现后代组件向祖先组件通信。
81、路由传值的方法
路由的传参有3中:
通过动态路径传值:
我们在设置路径的时候,通过路由的动态传参来进行数据传递,具体做法是在路径的后面添加?,然后在通过key=value的方法传递数据,如果多个数据的话,中间以@分隔。
name + parames传递参数:
在使用this.$push的时候,在方法内添加一个对象,对象内的name属性用于匹配路由,parames属性用于传递数据。在组件中,可以通过this.$route.parames来接收数据。
path + quary传递参数:
在使用this.$push()的时候,在方法内添加一个对象,在对象内通过path属性用于匹配路由,quary属性用于传递参数。在组件中通过this.$route.quary的方法接收数据。
82、Link和@import的区别
link是HTML中的标签,不仅可以引入CSS文件,还可以引入其他文件,在页面加载的时候就开始加载引入的文件,兼容性更加,JS可以操作它引入的样式文件。
@import是CSS中的语法,只能引入CSS文件,在页面加载完成之后才开始加载引入的样式文件,兼容性欠佳,JS也不能操作它引入的样式文件。
83、防抖和节流
防抖和节流都是减少一个功能在一段时间内触发的频率。
防抖是设置一个时间段,当这个功能被触发的时候并不会立即执行,而是需要等待一段时间;如果在这个时间段内该功能再次触发,则将计时清零重新开始。直到该时间段内不再触发,才开始执行。
节流是设置一个时间段,功能触发的时候不会立即执行,而是等延时一段时间后才开始执行。
举例:就像坐电梯,节流是等第一个人上来后,15秒后投送。防抖是第一个人来以后,如果15秒内没人上就投送,有人上就在等15秒。
84、数组去重的方法
通过set的方法、两个数组的扩展运算符、手写函数(创建一个新数组,遍历两个数组中的数据,如果新数组中没有,就添加,如果有就舍弃,返回新的数组)。
85、双飞翼布局
两侧的宽度固定不变,中间的板块响应式。
- 给父盒子开启flex布局,左右盒子的宽度写死,中间盒子的flex设置为1。
- 通过绝对定位布局
- 三个盒子开启浮动,第一个盒子的边距为-100%,右边盒子的边距为-自身宽度,中间盒子的宽度为100%,左右的外边距是左右盒子的宽度。
86、常见的布局方式****
静态布局、弹性布局、自适应布局、响应式布局、浮动布局、流动式布局、绝对定位布局。
87、Vue的两大核心
数据驱动及组件化(详见前面)
88、页面优化的方法
常见的方法有5中:
- 压缩资源+减少HTTP请求:使用精灵图、懒加载、压缩代码等。
- 非核心代码异步加载:使用denfer\async等方式异步加载JS脚本。
- 利用浏览器的缓存机制:多使用本地的强缓存,减少网络请求。
- 使用CDN:让用户使用就近的CDN网络,减少网络加载的速度。
- 使用DNS预加载:我们可以在link标签中通过rel设置"dns-prefetch",href设置为预解析的地址。
89、CSS优化的方法
- 建议使用link来引入样式文件,而不是使用@import等到页面加载完毕以后在加载。
- 准确使用选择器,并且减少昂贵选择器的使用(例如:*等)
- 异步加载CSS样式
- 减少无用和重复的代码
90、跨域如何解决
同源策略是一种浏览器的安全机制,当域名、IP、端口不一致的时候,就会触发浏览器的同源策略。常见的解决方法有:JSONP、cors、反向代理。
JSNOP:只有AJax请求才有跨域问题,script标签没有跨域问题,所以JSONP就是利用script标签解决跨域的。
cors:白名单策略,在白名单中添加当前的地址。
反向代理:在服务端架设代理服务器,请求的数据发送到代理服务器,由代理服务器将数据发送到内部网络,进而绕开同源策略。可以在vue.config.js中使用proxy进行设置,生产环境中使用nignx来进行配置。
91、$nextTicket
nextTick()函数只在DOM发生变化,页面渲染后,立即被调用的一个回调函数。我们想让一个操作在页面渲染更新后立即执行,那么就可以在nextTick()的回调中进行操作。