1. Vue的优点?Vue的缺点?
优点:渐进式,组件式,轻量级,虚拟dom,响应式,单页面路由,数据与视图分开。
缺点:单页面不利于SEO,不支持IE8一下,首屏加载时间长。
2.Vue跟React的异同点?
相同点:
-
都使用了虚拟dom
-
组件化开发
-
都是单项数据流(父子组件之间,不建议子组件直接修改父组件的数据)
-
都支持服务端渲染
不同点:
-
React的JSX,Vue的 template
-
数据变化,React手动(setState),Vue自动(初始化已响应式处理,Object.defineProperty)
-
React单项绑定,Vue双向绑定
-
React用Redux,Vue用Vuex状态管理工具
3.MVVM是什么?和MVC有和区别?
MVC
-
Model(模型):负责从数据中取数据
-
View(视图):负责展示数据的地方
-
Controller(控制器):用户交互的地方,例如事件点击等
-
思想:Controller将Model的数据展示在View上。
MVVM
-
VM:也就是ViewModel,做了两件事达到了数据的双向绑定,一是将【模型】转换成【视图】,即将后端的数据转换成所看到的页面。实现的方式是:数据绑定。二是将【视图】转换成【模型】,即将所看到的页面转换成后端的数据。实现的方式是:DOM事件监听 。
-
思想:实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作dom,来改变view的显示,而是改变属性后该属性对应的View层显示会自动改变。它是一种双向数据绑定的模式,用viewModel来建立起model数据层和view视图层的连接,数据改变会影响视图,视图改变会影响数据
4、vue双向数据绑定的原理?
vue,js是采用数据劫持结合发布者--订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动的时候发布消息给订阅者,触发响应的监听回调。
具体步骤;
第一步:需要observe的数据对象进行递归遍历,包括子属性身上的属性也都加上setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就监听到了数据的变化。
第二步:compile解析模板指令,将模板中的变量转化为数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
第三步:watcher订阅者是observe和compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合observe、compile和watcher三者,通过observe来监听自己的model数据变化,通过compile来解析编译模板指令,最终利用watch搭起observe和compile之间的通信桥梁,达到数据变化-->视图更新,视图交互变化(input)-->数据model变更的双向绑定效果。
5、$nextTick
nextTick
是Vue
提供的一个全局API
,是在下次DOM
更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick
,则可以在回调中获取更新后的DOM
;
Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue
将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher
被多次触发,只会被推入到队列中去。这种在缓冲时去除重复数据对于避免不必要的计算和DOM
操作是非常重要的。nextTick
方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
我也有简单了解nextTick
实现,它会在callbacks
里面加入我们传入的函数,然后用timerFunc
异步方式调用它们,首选的异步方式会是Promise
。这让我明白了为什么可以在nextTick
中看到dom
操作结果。
它主要是处理我们在变更完数据以后,无法立刻拿到最新的DOM节点对象的问题。因为Vue更新是异步的,$nextTick是等vue执行完渲染之后再执行this.nextTick()里面的回调函数。
举个例子,在vue中:
this.name = '林三心'
this.age = 18
this.gender = '男'
我们修改了三个变量,那问题来了,是每修改一次,DOM就更新一次吗?不是的,Vue采用的是异步更新
的策略,通俗点说就是,同一事件循环内
多次修改,会统一
进行一次视图更新
,这样才能节省性能.
<div ref="testDiv">{
{name}}</div>
name: '小林'
this.name = '林三心'
console.log(this.$refs.testDiv.innerHTML)
答案是“小林”,前面说了,Vue是异步更新
,所以数据一更新,视图却还没更新,所以拿到的还是上一次的旧视图数据,那么想要拿到最新视图数据怎么办呢?
this.name = '林三心'
this.$nextTick(() => {
console.log(this.$refs.testDiv.innerHTML) // 林三心
})
6、递归
递归就是函数内部自己调用自己
在element-ui树形结构用过,将扁平的数据结构转换成树形结构。
7、Vue的生命周期有哪些?
vue实例从创建到销毁的过程就是生命周期,也就是从开始创建、初始化数据、编译模板、挂载dom-->渲染、更新-->渲染、准备销毁、销毁等一系列过程。
vue的生命周期常见的主要分为4大阶段8大钩子函数。
1、创建前后
在beforeCreate生命周期执行的时候,data和method还没有初始化。
在created的时候,data和method已经初始化完成
2、挂载前后
在beforeMounte的生命周期执行的时候,已经编译好了模板字符串,但是还没有真正渲染到页面中去
在mounted的时候,已经渲染完成,可以拿到dom
3、数据更新前后
beforeUpdate生命周期执行的时候,已经可以拿到最新的数据,但还没有渲染到视图中去
updated函数执行的时候,已经把更新后的数据渲染到视图中去了
4、销毁前后
beforeDestroy实例正准备销毁阶段,此时data,method,指令还是可用状态。
在destroyed的生命周期函数执行的时候,实例已经完全销毁,此时data,method,指令都不可用
另外还有三个生命周期不常用:
keep-alive主要用于保留组件状态或避免重复渲染。
activated只有在keep-alive组件激活时调用。
deactivated只有在keep-alive 组件停用时调用。
errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。
8、v-if和V-show的区别?
v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建,操作的实际上是dom元素的创建和销毁。
v-show就简单地多,不管初始条件是什么,元素总是会被渲染,并且只是简单的基于css进行切换,它操作的是display:none/block属性。
总结:v-if有更高的切换开销,v-show有更高的初始渲染开销,如果要非常频繁的切换,则使用v-show,如果运行时条件很少改变则使用v-if。
9、v-model语法糖的原理?
在vue中,我们可以使用v-bind实现单项的数据绑定,也就是通过父组件向子组件传入数据,但是反过来,子组件不可以修改父组件传递过来的数据,也就是所谓的单项数据绑定。
而v-model就实现了双向数据绑定,实际上它就是通过Vue提供的事件机制,即在子组件通过$emit()触发一个事件,而父组件使用v-on来监听对应的事件并修改相应的数据。
将上面处理简化为一个语法糖:
<input type='text' v-model='name'/>
它的本质是:
<input type='text' :value='name' @input='name=$event.target.value' />
Vue内部的v-model
是完成事件绑定 和事件监听 的语法糖.
10、柯里化?
柯里化就是通过函数调用继续返回函数的形式,实现多次传参并最后统一处理的编码形式。
作用是参数复用。
function sum(a, b, c, d) {
return a + b + c + d
}
// curry: 能把普通函数变成柯里化的形式进行调用
// const sumTemp = _.curry(sum)
function curry(fn) {
// 函数参数的长度
const len = fn.length // 4
return function temp(...args) {
if (args.length >= len) {
return fn(...args)
} else {
return function (...args2) {
return temp(...args, ...args2)
}
}
}
}
const sumTemp = curry(sum)
console.log(sumTemp(1, 2, 3, 4));
console.log(sumTemp(1)(2, 3, 4));
console.log(sumTemp(1, 2)(3, 4));
// 柯里化很重要的一个作用:参数复用
function heima(school, classs, xingming) {
console.log(`${school}的${classs}的${xingming}`)
}
/* heima('黑马', 67, '王沙')
heima('黑马', 67, '杨康')
heima('黑马', 67, '昊天') */
const itheima = curry(heima)
const fn = itheima('三年级', '二班')
fn('张三')
fn('李四 ')
fn('王五')
11、手动封装一个filter
function myFilter(fn){
let arr=[]
for(let i=0; i< this.length; i++){
if(fn(this[i],i,this)){
console.log(this[i])//将符合条件的值push进空数组中
arr.push(this[i])
}
}
//循环结束时,返回arr ,此时如果数组中有值,就返回新数组,如果没有值,就会返回空数组
return arr
}
let arr2=[1,2,3]
Array.prototype.myFilter=myFilter
arr2.myFilter(function(item,index,arr2)){
return item < 2
}
12、防抖和节流
防抖(debounce):重复触发不执行,不触发的一段时间之后才执行(例如王者回城,淘宝关键字搜索)
手写防抖:
//定义:触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。
//搜索框搜索输入。只需用户最后一次输入完,再发送请求
//手机号、邮箱验证输入检测 onchange oninput事件
//窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
const debounce = (fn, wait, immediate) => {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
if (immediate && !timer) {
fn.call(this, args);
}
timer = setTimeout(() => {
fn.call(this, args);
}, wait);
};
};
const betterFn = debounce(() => console.log("fn 防抖执行了"), 1000, true);
document.addEventListener("scroll", betterFn);
节流(throttle):持续触发也执行,只不过执行的频率变低了 (规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。)
都是性能优化的一种手段
//1. 懒加载、滚动加载、加载更多或监听滚动条位置;
//2. 百度搜索框,搜索联想功能;
//3. 防止高频点击提交,防止表单重复提交;
节流阀为空,表示可以执行下一次操作,不为空,表示不能使用下次操作.
当前操作执行完之后要将节流阀重置为空,表示可以执行下次操作了.
每次执行操作之前,先判断节流阀是否为空
const throttle = (fn, time) => {
let flag = true
return function (e) {
if (flag) {
flag = false
setTimeout(() => {
fn.call(this, e)
flag = true
}, 1000)
}
}
}
oDiv.onmousemove = throttle(function (e) {
let left = e.pageX - this.offsetLeft
let top = e.pageY - this.offsetTop
this.innerHTML = `x:${left},y:${top}`
})
13、为什么data是个函数并且返回一个对象呢?
data之所以只是一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行data函数并返回新的数据对象,这样可以避免多出调用之间的数据污染。
14. 使用过哪些Vue的修饰符呢?
.lazy: 作用是改变输入框的值时value不会改变,当光标离开输入时,v-model绑定的值value才会改变。
.trim:作用类似于JS中的trim()方法,把v-model绑定的值的首位空格去掉
.number:作用是将值转换为数字
.stop:作用是阻止冒泡
.capture:事件默认由里往外冒泡,capture修饰符作用是反过来,由外往里
.self:作用是,只有点击事件绑定本身才会触发事件
.once:作用是事件只执行一次
.prevent:作用是阻止默认事件(如a标签的跳转)
.native:加在自定义组件事件上,保证事件能执行
15. 组件之间的传值方式有哪些?
1.父子传值
父组件给子组件传值,子组件用props接收
2、子传父
子组件使用$emit+事件传值
组件中可以使用$parent和$children获取到父组件实例和子组件实例,进而获取数据
也可使用$refs获取组件实例,进而获取数据
3、兄弟组件之间的传参 eventBus
$emit自定义事件
$on接收事件
//组件A
methods:{
addList:function(){
//重点: $emit自定义事件
eventBus.$emit('add',this.todoList)
}
}
//组件B
methods:{
acceptList:function(){
// 重点:$on接收事件
eventBus.$on('add',(message)=>{
this.lists.push({ name:message,isFinish:false })
})
}
}
16、Vue中key的作用
Vue是通过虚拟dom来表示真是dom的,在去更新视图之前,要对前后两个虚拟dom树进行分析,以得出它们的区别。如果不设置key,vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改、复用相同类型元素的算法。而使用key时,它会基于key的变化重新排列元素顺序,并且会移出key不存在的元素。
key的作用主要是为了更高效的对比虚拟dom中的某个节点是不是相同节点,是用来提高diff算法的性能表现。更具体一点,vue在patch(执行diff的算法,可翻译为打补丁算法)过程中判断两个节点是否是相同节点,key值相同是一个必要条件。
key会提升效率某些特殊情况下,不写key会出错,在使用v-for循环时,尽量避免直接使用数组的下标为key,因为它们在删除操作时可能会导致渲染异常。最好是将key设成数据中的主键:可以把一项与另一项区别开的值。
17、vue中computed 和watch 的区别是什么?
1.computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)
2.computed 适合一些比较简单的计算,不能解决异步问题,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数
3.从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据;watch可以用于一些复杂的计算还有异步操作
18、常用的数组方法有哪些?
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。 (通常用它判断数组中有没有这个元素)
join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
pop()方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
push()方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
shift()方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。
sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的
19、常用的字符串方法有哪些?
charAt() 方法从一个字符串中返回指定的字符。
concat() 方法将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。
indexOf() 方法返回调用它的 String 对象中第一次出现的指定值的索引,如果未找到该值,则返回 -1。
match() 方法检索返回一个字符串匹配正则表达式的的结果。
padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。 (常用于时间补0)
replace() 方法返回一个由替换值(replacement
)替换一些或所有匹配的模式(pattern
)后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。
原字符串不会改变。
slice() 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
split()方法使用指定的分隔符字符串将一个String`对象分割成字符串数组,以将字符串分隔为子字符串,以确定每个拆分的位置。
substr()方法返回一个字符串中从指定位置开始到指定字符数的字符。
trim() 方法会从一个字符串的两端删除空白字符。
20、原型、原型链
原型: 对象中固有的__proto__
属性,该属性指向对象的prototype
原型属性。
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype, 但是仍然没有找到指定的属性,就会返回 undefined
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
hasOwnProperty函数:
hasOwnProperty是Object.prototype的一个方法,它可是个好东西,他能判断一个对象是否包含自定义属性而不是原型链上的属性,因为hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
21、常见的继承有哪些?
一、原型链继承
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原 型属性也会被修改!)
二、借用构造函数继承
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
三、组合继承(组合原型链继承和借用构造函数继承)(常用)
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
2、无法实现复用。(新实例属性都是后面添加的)
五、class类实现继承
通过extends 和super 实现继承
六、寄生式继承
重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。
22、new运算符具体干了什么?
以 new 操作符调用构造函数的时候,函数内部实际上发生以下变化:
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this.
23、vuex的有哪些属性?用处是什么?
state:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
getter:允许组件从store中获取数据,mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性。
mutation:是修改state里面的数据,必须是同步函数。
action:用于提价mutation,而不是直接变更状态,可以包含任意异步操作。
module:允许将单一的store拆分为多个store且同时保存在单一的状态树种。
24、watch有哪些属性,分别有什么用?
当我们监听一个基本数据类型时:
watch: {
value () {
// do something
}
}
当我们监听一个引用数据类型时:
watch: {
obj: {
handler () { // 执行回调
// do something
},
deep: true, // 是否进行深度监听
immediate: true // 是否初始执行handler函数
}
}
25、父子组件生命周期顺序
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
26、Vue自定义指令
Vue自定义指令有全局注册和局部注册两种方式。
1、全局指令的方式,通过 Vue.directive( id, [definition] )
方式注册全局指令。然后在入口文件中进行 Vue.use()
调用。
在 main.js
引入并调用
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)
指令定义函数提供了几个钩子函数(可选):
-
bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
-
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
-
update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化,通过比较更新前后的绑定值。
-
componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
-
unbind: 只调用一次, 指令与元素解绑时调用。
27. 插槽的使用以及原理?
28、清除浮动的方法有哪些?
为什么要清除浮动,因为浮动的盒子脱离标准流,如果父盒子没有设置高度的话,下面的盒子就会上来
1.额外标签法(在最后一个浮动标签后,新加一个标签,给其设置clear:both;)(不推荐)
2.父级添加overflow属性(父元素添加overflow:hidden)(不推荐)
3.使用after伪元素清除浮动(推荐使用)
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}
4.使用before和after双伪元素清除浮动
.clearfix:after,.clearfix:before{
content: "";
display: table;
}
.clearfix:after{
clear: both;
}
.clearfix{
*zoom: 1;
}
29、数据类型的判断有哪些方法?他们的优缺点及区别是什么?
然后判断数据类型的方法一般可以通过:typeof、instanceof、constructor、toString四种常用方法
typeof:只能检测简单数据类型
instanceof:能检测出复杂数据类型,不能检测简单数据类型,且不能跨iframe
constructor:基本能检测所有的类型(除了null和undefined),constructor易被修改,也不能跨iframe
Object.prototype.toString.call:检测出所有数据类型。
30、请描述一下ES6中的class类?
ES6中的class可以把它看成是ES5中构造函数的语法糖,它简化了构造函数的写法,类的共有属性放到constructor里面。
1、通过class关键字创建类,类名我们还是习惯性首字母大写
2、类里面有个constructor函数,可以接收传递过来的参数,同时返回实例对象。
3、constructor函数只要new生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数。
4、多个函数方法之间不需要添加逗号。
5、生成实例new不能省略
6、语法规范,创建类,类名后面不要加小括号,生成实例,类名后面加小括号,构造函数不需要加function。
-
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
-
继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类这个方法(就近原则)
-
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super调用父类的构造函数,super必须在子类this之前调用
7、时刻注意this的指向问题,类里面的共有属性和方法一定要加this使用
-
constructor中的this指向new出来的实例对象
-
自定义方法一般也是指向new出来的实例对象
-
绑定事件之后的this指向的就是触发事件的事件源
-
在ES6中类没有变量提升,所有必须先定义类,才能通过类实例化对象
31、浏览器渲染机制
-
HTML
被HTML解析器解析成DOM
树 -
css
则被css解析器解析成CSSOM
树 -
结合
DOM
树和CSSOM
树,生成一棵渲染树(Render Tree
) -
生成布局(
flow
),即将所有渲染树的所有节点进行平面合成 -
将布局绘制(
paint
)在屏幕上
32、什么是BFC?
BFC
是 Block Formatting Context
的缩写,即块格式化上下文。BFC
是CSS布局的一个概念,是一个环境,里面的元素不会影响外面的元素。 布局规则:Box是CSS布局的对象和基本单位,页面是由若干个Box组成的。元素的类型和display属性,决定了这个Box的类型。不同类型的Box会参与不同的Formatting Context
。 创建:浮动元素 display:inline-block position:absolute
应用: 1.分属于不同的BFC
时,可以防止margin
重叠 2.清除内部浮动 3.自适应多栏布局
33、作用域、作用域链、变量提升
作用域
负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链
。
34、原生ajax
ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。 过程:
-
创建
XMLHttpRequest
对象; -
调用
open
方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false)
; -
监听
onreadystatechange
事件,当readystate
等于4时返回responseText
; -
调用send方法传递参数。
35、ES6
-
新增symbol类型 表示独一无二的值,用来定义独一无二的对象属性名;
-
const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,也就是不存在变量提升。(const一般用于声明常量);
-
变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest);
-
模板字符串(
${data}
); -
扩展运算符(数组、对象);;
-
箭头函数;
-
Set和Map数据结构;
-
Proxy/Reflect;
-
Promise;
-
async函数;
-
Class;
-
Module语法(import/export)。
36、双向绑定实现原理
当一个Vue实例创建时,Vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher重新计算,从而致使它关联的组件得以更新。
37、你都做过哪些Vue的性能优化?
编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用 如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
SEO优化
预渲染
服务端渲染SSR
打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
38、Vue3有哪些新特性?
实现双向绑定 Proxy 与 Object.defineProperty 相比:
Object.definedProperty的作用是劫持一个对象的属性,劫持属性的getter和setter方法,在对象的属性发生变化时进行特定的操作。而 Proxy劫持的是整个对象。
Proxy会返回一个代理对象,我们只需要操作新对象即可,而Object.defineProperty只能遍历对象属性直接修改。
Object.definedProperty不支持数组,更准确的说是不支持数组的各种API,因为如果仅仅考虑arry[i] = value 这种情况,是可以劫持 的,但是这种劫持意义不大。而Proxy可以支持数组的各种API。
尽管Object.defineProperty有诸多缺陷,但是其兼容性要好于Proxy。
1、移出了$on方法,2、移出过滤器选项,使用methods替代,3、移出.sync语法,v-model语法合并,vue2选项式API,vue3组合式API
39、浏览器从输入url到渲染页面,发生了什么?
三个方面:
网络篇: 构建请求 --> 查找强缓存 --> DNS解析 --> 建立TCP连接(三次握手) --> 发送HTTP请求(网络请求后网络响应)
浏览器解析篇: 解析html构建DOM树--> 解析css构建CSS树、样式计算--> 生成布局树(Layout Tree)
浏览器渲染篇:建立图层树(Layer Tree) --> 生成绘制列表 --> 生成图块并栅格化 --> 显示器显示内容 --> 最后断开连接:TCP 四次挥手 (浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上)
40、Http和Https区别
-
HTTP
的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头 -
HTTP
是不安全的,而 HTTPS 是安全的 -
HTTP
标准端口是80 ,而 HTTPS 的标准端口是443 -
在OSI
网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层 -
HTTP
无法加密,而HTTPS 对传输的数据进行加密 -
HTTP
无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书
41、GET和POST区别
-
GET在浏览器回退不会再次请求,POST会再次提交请求
-
GET请求会被浏览器主动缓存,POST不会,要手动设置
-
GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会
-
GET请求在URL中传送的参数是有长度限制的,而POST没有限制
-
GET参数通过URL传递,POST放在Request body中
-
GET参数暴露在地址栏不安全,POST放在报文内部更安全
-
GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作
-
GET产生一个TCP数据包;POST产生两个TCP数据包
42、http特性以及状态码
-
200响应成功
-
301永久重定向
-
302临时重定向
-
304资源缓存
-
403服务器禁止访问
-
404服务器资源未找到
-
500 502服务器内部错误 504 服务器繁忙
-
1xx Informational(信息状态码) 接受请求正在处理
-
2xx Success(成功状态码) 请求正常处理完毕
-
3xx Redirection(重定向状态码) 需要附加操作已完成请求
-
4xx Client Error(客户端错误状态码) 服务器无法处理请求
-
5xx Server Error(服务器错误状态码) 服务器处理请求出错
43、http三次握手四次挥手
http三次握手
第一步:客户端发送SYN报文到服务端发起握手,发送完之后客户端处于SYN_Send状态
第二步:服务端收到SYN报文之后回复SYN和ACK报文给客户端
第三步:客户端收到SYN和ACK,向服务端发送一个ACK报文,客户端转为established状态,此时服务端收到ACK报文后也处于established状态,此时双方已建立了连接
http四次挥手
开始双方都处于establised
状态,假如是客户端先发起关闭请求,则:
-
第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
-
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
-
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
-
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
-
服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
44、前端性能优化的几种方式
1.浏览器缓存
-
防抖、节流
-
资源懒加载、预加载
-
开启Nginx gzip压缩 三个方面来说明前端性能优化
一: webapck优化与开启gzip压缩 1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件 其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true' 2.文件采用按需加载等等 3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句: accept-encoding:gzip 4.图片优化,采用svg图片或者字体图标 5.浏览器缓存机制,它又分为强缓存和协商缓存
二:本地存储——从 Cookie 到 Web Storage、IndexedDB 说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三:代码优化
1.事件代理 2.事件的节流和防抖 3.页面的回流和重绘 4.EventLoop事件循环机制 5.代码优化等等
45、跨域通信的几种方式
jsonp(利用script
标签没有跨域限制的漏洞实现。缺点:只支持GET
请求)
CORS(设置Access-Control-Allow-Origin
:指定可访问资源的域名)
在工作中一般都是找后端设置一下,比较快捷方便。
46、能不能说一说浏览器的本地存储?各自优劣如何?
共同点: 都是保存在浏览器端、且同源的
不同点:
-
cookie
数据始终在同源的http
请求中携带(即使不需要),即cookie
在浏览器和服务器间来回传递。cookie
数据还有路径(path
)的概念,可以限制cookie
只属于某个路径下sessionStorage
和localStorage
不会自动把数据发送给服务器,仅在本地保存。 -
存储大小限制也不同,
-
cookie
数据不能超过4K,sessionStorage和localStorage
可以达到5M -
sessionStorage
:仅在当前浏览器窗口关闭之前有效; -
localStorage
:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据; -
cookie
:只在设置的cookie
过期时间之前有效,即使窗口关闭或浏览器关闭
-
作用域不同
-
sessionStorage
:不在不同的浏览器窗口中共享,即使是同一个页面; -
localstorage
:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在 -
cookie
: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在
47、vue怎么进行跨域?
2.5.2以上的版本是要自己在根目录里建立:vue.config.js文件然后:
module.exports = {
devServer: {
proxy: {
"/api": { //自定义
target: "http://你的服务器地址/api", //这里可以跟随项目实际部署服务器来
changeOrigin: true,
ws: true,
pathRewrite: {
"^/api": ""//自定义
}
}
}
}}
48、vue-router有哪几种导航钩子?
三种 (1)、全局导航钩子
router.beforeEach(to, from, next),
router.beforeResolve(to, from, next),
router.afterEach(to, from ,next)
(2)、组件内钩子
beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave
(3)、单独路由独享组件
beforeEnter
路由切换路由钩子执行顺序:
beforeRouterleave-->全局beforeEach-->beforeRouteUpdate-->beforeEnter-->
beforeRouteEnter-->beforeResolve-->afterEach
完整的导航解析流程:
1.导航被触发。
2.在失活的组件里调用离开守卫。
3.调用全局的 beforeEach 守卫。
4.在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
5.在路由配置里调用 beforeEnter。
6.解析异步路由组件。
7.在被激活的组件里调用 beforeRouteEnter。
8.调用全局的 beforeResolve 守卫 (2.5+)。
9.导航被确认。
10.调用全局的 afterEach 钩子。
11.触发 DOM 更新。
12.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
50、对象深浅拷贝
//浅拷贝
1. Object.assign(target,source)
2. es6对象扩展运算符。
//深拷贝
function deepClone(obj) {
if (!obj || typeof obj !== "object") return;
let newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
51、Vue3
说一下 setup
// 方法
setup(props, context){ return { name:'沐华' } }
setup函数是一个新的组件选项,作为组件中组合式API的起点,是在beforeCreate() 生命周期函数之前执行的函数,setup函数只会在组件初始化时执行一次;
它接收两个参数 props和 context。它里面不能使用 this,而是通过 context 对象来代替当前执行上下文绑定的对象,context 对象有四个属性:attrs
、slots
、emit
、expose
里面通过 ref和 rective 代替以前的 data 语法,return
出去的内容,可以在模板直接使用,包括变量和方法
而使用 setup
语法糖时,不用写 export default {}
,子组件只需要 import
就直接使用,不需要像以前一样在 components 里注册,属性和方法也不用 return。
Vue3 的生命周期
基本上就是在 Vue2 生命周期钩子函数名基础上加了 on
;beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted;然后删了两个钩子函数 be foreCreate 和 created;新增了两个开发环境用于调试的钩子
onBeforeMoute-->onMouted-->onBeforeUpdate-->onUpdated-->onBeforeUnmounte-->onUnmounted-->onActivated-->onDeactivated-->onErrorCaptured-->onRenderTracked((dev)组件更新是跟踪所有变量和方法)
-->onRenderTriggered((dev)触发渲染时调用,返回变化新旧值)
52、Vue3 组件通信方式
-
props
-
$emit
-
expose / ref
-
$attrs
-
v-model
-
provide / inject
-
Vuex
-
mitt
53、首屏延迟加载怎么解决
常见的几种SPA首屏优化方式:
-
减小入口文件积
-
静态资源本地缓存
-
UI框架按需加载
-
图片资源的压缩
-
组件重复打包
-
开启GZip压缩
-
使用SSR
54、堆和栈(数据类型有哪两种)
栈:简单类型的数据会在栈里面开辟空间,保存数据。
堆:复杂数据类型会在堆里面开辟空间,保存复杂数据类型。地址在栈里。
55、token失效处理
后端会给一个refresh的接口,当token失效时会在响应拦截里面判断,如果是因为token失效的问题报错,并且当前vuex中存在用户信息,那就会通过这个接口重新请求一个token.
56、移动端适配
Vant 中的样式默认使用 px作为单位,如果需要使用 rem单位,可以使用以下两个工具:
-
postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 rem
-
lib-flexible 用于设置 rem 基准值
使用 lib-flexible 动态设置 REM 基准值(html 标签的字体大小)
使用 postcss-pxtorem 将 px转为 rem
57、sass和less的区别
Less和Sass的主要不同就是他们的实现方式。
Less是基于JavaScript,是在客户端处理的。
Sass是基于Ruby的,是在服务器端处理的。
关于变量在Less和Sass中的唯一区别就是Less用@,Sass用$。
58、axios
axios
是一个轻量的 HTTP
客户端
基于 XMLHttpRequest
服务来执行 HTTP
请求,支持丰富的配置,支持 Promise
,支持浏览器端和 Node.js
端。
特性
-
从浏览器中创建
XMLHttpRequests
-
从
node.js
创建http
请求 -
支持
Promise
API -
拦截请求和响应
-
转换请求数据和响应数据
-
取消请求
-
自动转换
JSON
数据 -
客户端支持防御
XSRF
59、css3新增的样式属性
边框:border-radius、box-shadow、border-image
背景:background-clip、background-origin、background-size、background-break
文字:word-wrap
-
normal:使用浏览器默认的换行
-
break-all:允许在单词内换行
颜色:rgba
transition 过渡 transform 转换 animation 动画
60、Vue.use
-
通过全局方法 Vue.use() 使用插件
-
Vue.use 会自动阻止多次注册相同插件
-
它需要在你调用 new Vue() 启动应用之前完成
-
Vue.use() 方法至少传入一个参数,该参数类型必须是 Object 或 Function,如果是 Object 那么这个 Object 需要定义一个 install 方法,如果是 Function 那么这个函数就被当做 install 方法。在 Vue.use() 执行时 install 会默认执行,当 install 执行时第一个参数就是 Vue,其他参数是 Vue.use() 执行时传入的其他参数。
61、promise
Promise
,是异步编程的一种解决方案,用来解决回调地狱的问题。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
promise
对象仅有三种状态:
pending
(进行中)fulfilled
(已成功)rejected
(已失败)
用法:
Promise
对象是一个构造函数,用来生成Promise
实例
const promise = new Promise(function(resolve, reject) {});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
Promise
构建出来的实例存在以下方法:
- then()
- then()
- catch()
- finally()
then()
then
是实例状态发生改变时的回调函数,第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数
then
方法返回的是一个新的Promise
实例,也就是promise
能链式书写的原因