前言
本篇博客回顾了vue的模式、单页面的优缺点、渐进式框架的理解。
面试回答
1.MVVM模式:MVVM模式也就是Model-View-ViewModel,分别负责数据、视图以及通信桥梁的工作,这种模式实现了业务逻辑与渲染之间的解耦。在vue中最直接的表现就是胡子语法以及双向绑定。
2.React和vue的区别:vue组件分为全局注册和局部注册,而react中都是通过import引用;vue增加了许多语法糖比如computed和watch,而react需要自己实现;vue中多了指令系统,能够让开发更加便捷,而react只能自己去实现。
3.React绑定:不管有几种绑定方式,绑定的this都指向这个类,只不过在使用上有区别,总共有三种方式:第一种是在构造函数中用bind绑定this。第二种是在调用普通函数的时候用bind绑定this。第三种是通过使用箭头函数绑定this。
4.setState理解:setState本质上是同步任务,而这个异步指的是调用setState不会立即更新this.state。这是因为setState会通过一个队列机制,会将需要更新的state潜合并到状态队列中,但不会立即更新state,会找个时机批量更新。而这个时机指的是触发合成事件,也就是batchedUpdates函数,这个函数会在react调用事件函数之前被调用。一般我们用第二个参数设置回调拿到。
5.高阶组件:高阶组件是一种增强函数,接收一个组件,再返回一个组件,主要是为了代码复用。一般用来添加默认参数、提取状态转换成受控组件、新增DOM元素包裹组件、通过super进行反向继承,比如埋点这类性能监控、通过if进行条件渲染,比如权限控制。
知识点
1.MVVM
Vue采用的是MVVM的模式,即Model-View-ViewModel。
- Model:模型层,负责处理业务逻辑以及和服务器端进行交互
- View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
- ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁,含DOM Listeners和Data Bindings,其中 Data Bindings 用于将数据绑定到 View 上显示,DOM Listeners 用于监听操作。
View层和Model层并没有直接联系,而是通过ViewModel层的双向数据绑定将View层和Model层连接起来进行交互,双向数据绑定即
- 1.将模型转换成视图,即将后端传递的数据转换成看到的页面。 实现方式是:数据绑定。
- 2.将视图转换成模型,即将看到的页面(用户操作)转换成后端的数据。实现的方式是:DOM 事件监听
MVVM相对于MVC的优势?
- MVVM 实现了数据与页面的双向绑定,MVC 只实现了 Model 和 View 的单向绑定。
- MVVM 实现了页面业务逻辑和渲染之间的解耦,也实现了数据与视图的解耦,并且可以组件化开发。
VUE是如何体现MVVM思想的?
- 胡子语法,实现了数据与视图的绑定,PS:胡子语法,双大括号标签会把里面的值全部当作字符串来处理,如果值是HTML片段,则可以使用三个大括号来绑定。
- v-on 事件绑定,通过事件操作数据时,v-model 会发生相应的变化。
MVVM实现原理具体实现会在后续的vue.js两大核心的博客中去进一步理解,参考博客:https://juejin.cn/post/6844903960789123086;https://juejin.cn/post/6844904067525935118
主要分为四个模块:
- Compile(模板编译)
- Observer(数据劫持)
- Watcher(数据监听)
- Dep(发布订阅)
实现步骤:
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者(Dep)
- 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应的Dep (发布),从而更新视图
- MVVM入口函数,整合以上三者
简易图理解:
vue等SPA单页面应用及其优缺点
优点:
- 前后端分工明确,前端负责页面,后端负责数据。
- 用户体验好,速度快,路由切换时不需要再像服务端发起请求。
- 相对于服务器压力小
缺点:
- 不利于seo搜索,因为页面数据都是前端异步加载的方式,不利于搜索引擎的抓取。
- 如果打包的js过大,会导致请求过长,从而造成首屏加载响应慢。
2.vue与react的区别
参考博客:https://juejin.cn/post/7071889478305972255?searchId=202308251...
1.核心思想
Vue的核心思想是尽可能的降低前端开发的门槛,是一个灵活易用的渐进式双向绑定的MVVM框架。
React的核心思想是声明式渲染和组件化、单向数据流,React既不属于MVC也不属于MVVM架构。
- RQ1:声明式是什么意思?
声明式与之相对应的是命令式,命令式指的是通过DOM操作一步步把网页变成想要的样子,而声明式则是只需要通过状态去形容最后的网页长什么样子即可。
- RQ2:组件化是什么意思?
组件化指的是尽可能的将页面拆分成一个个较小的、可以复用的组件,这样让我们的代码更加方便组织和管理,并且拓展性页更强。
- RQ3:如何理解React的单向数据流?
React的单向数据流指的是数据主要从父节点通过props传递到子节点,如果顶层某个props改变了,React会重新渲染所有的子节点,但是单向数据流并非单向绑定,React想要从一个组件去更新另一个组件的状态,需要进行状态提升,即将状态提升到他们最近的祖先组件中,触发父组件的状态变更,从而影响另一个组件的显示。单向数据流的好处是能够保证状态改变的可追溯性,假如,父组件维护了一个状态,子组件如果能够随意更改父组件的状态,那么各组件的状态改变就会变得难以追溯。
2.组件写法上不同
Vue的组件写法是通过template的单文件组件格式,具体形式则是分为HTML、CSS、JS三个模块
React的组件写法是JSX+inline style,也就是吧HTML和CSS全部写进JavaScript中,具体形式则是将HTML、CSS、JS糅杂在一起,最后用render渲染
3.Diff算法
vue和react的diff算法都是进行同层次的比较,主要有以下两点不同:
- vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
- vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。
- vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树,而react每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
4.响应式原理不同
React的响应式原理
React主要是通过setState()方法来更新状态,状态更新之后,组件也会重新渲染。
总结:
- React基于状态机,手动优化,数据不可变,需要setState驱动新的State替换老的State。
- 当数据改变时,以组件为根目录,默认全部重新渲染
Vue的响应式原理
vue会遍历data数据对象,使用Object.definedProperty()将每个属性都转换为getter和setter,每个Vue组件实例都有一个对应的watcher实例,在组件初次渲染的时候会记录组件用到了那些数据,当数据发生改变的时候,会触发setter方法,并通知所有依赖这个数据的watcher实例调用update方法去触发组件的compile渲染方法,进行渲染数据。
总结:
- Vue依赖收集,自动优化,数据可变。
- Vue递归监听data的所有属性,直接修改。
- 当数据改变时,自动找到引用组件重新渲染。
5.封装程度不同
封装程度,vue封装程度更高,内置多个指令和数据双向绑定,react封装度比较低,适合扩展。
3.vue中高阶组件的应用
参考博客:https://juejin.cn/post/7212822448147382330?searchId=202308251...
高阶组件(HOC):是将另一个函数作为参数接受或将函数作为返回值的函数;
Hoc组件
export default function getHOC(comMap) {
const ignoreArr = ['change', 'input']
return {
computed: {
// 过滤掉input和change事件监听
listeners() {
const res = {}
for(let key in this.$listeners) {
if(!ignoreArr.includes(key)) {
res[key] = this.$listeners[key]
}
}
return res
},
// 处理插槽
slotsMap() {
const tempSlotsMap = Object.keys(this.$slots)
.reduce((arr, cc) => {
const slot = this.$slots[cc][0]
const type = slot.data.attrs.comType || 'default'
if(!arr[type]) {
arr[type] = []
}
slot.context = this._self
arr[type].push(slot)
return arr
}, {})
return tempSlotsMap
}
},
methods: {
// 处理scopedSlot插槽
handleScopeSlots(slots, name) {
let slotsMap = {}
for(let key in slots) {
const res = key.split('_')
if(res[0]===name) {
slotsMap[res[1]]=slots[key]
}
}
return slotsMap
},
// 处理渲染dom
handleNodes(h) {
const { config, model } = this.$attrs || {}
let hArr = []
for(let key in comMap) {
const slots = this.slotsMap[key]
const scopedSlots = this.handleScopeSlots(this.$scopedSlots, key)
hArr = [...hArr, h(comMap[key], {
props: {
value: model ? model[config?.[key]?.prop] : '',
...(this.$props?.config?.[key] || {}),
...config[key]
},
attrs: {
key: `${key}-${config[key].prop}`,
...config[key],
},
on: {
change: (v) => {
model[config?.[key]?.prop] = v
this.$emit('change', {prop: config[key].prop, v})
},
input: (v) => {
model[config?.[key]?.prop] = v
this.$emit('input', {prop: config[key].prop, v})
},
...this.listeners
},
style: {
marginBottom: '10px',
maxWidth: '300px',
...(config?.[key]?.style || {})
},
scopedSlots: scopedSlots
}, slots || null)]
}
return hArr
}
},
render(h) {
const nodes = this.handleNodes(h)
return h('div', null, [
h('div', null, this.slotsMap.default),
...nodes
])
}
}
}
高阶组件的使用
<template>
<div>
<Hoc
:model="data"
:config="config"
@input="input"
@change="change"
@blur="blur"
@select="handleSelect"
>
<template>
<h2 slot="header" style="padding-bottom: 20px;">我是高阶组件</h2>
</template>
<template>
<i slot="prefix" class="el-icon-date" style="margin-top: 13px;" comType="input"></i>
</template>
<template slot="autocomplete_default" slot-scope="{ item }">
<div class="name">{{ item.value }}</div>
<span class="addr">{{ item.address }}</span>
</template>
</Hoc>
<div>{{data}}</div>
</div>
</template>
<script>
import getHOC from '../components/hoc'
import MySelect from '../components/select.vue'
import { Input, Switch, rate, Autocomplete } from 'element-ui'
const Hoc = getHOC({input: Input, mySelect: MySelect, switch: Switch, rate, autocomplete: Autocomplete})
export default {
components: {
Hoc,
},
data() {
return {
data: {
inputValue: '',
autocompleteValue: '',
selectValue: '',
switchValue: false,
rateValue: 0
},
config: {
input: {
prop: 'inputValue',
placeholder:'我是输入框',
style: {},
},
autocomplete: {
prop: 'autocompleteValue',
placeholder: '请输入内容',
fetchSuggestions: this.querySearch
},
mySelect: {
prop: 'selectValue',
placeholder: '请选择',
list: [{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}]
},
switch: {
prop: 'switchValue',
activeColor: "#13ce66",
inactiveColor: "#ff4949"
},
rate: {
prop: 'rateValue'
},
restaurants: []
}
}
},
mounted() {
this.restaurants = this.loadAll();
},
methods: {
change(v) {
console.log('v: ', v);
},
input(v) {
console.log('v222: ', v)
},
blur(e) {
console.log('event: ', e)
},
handleSelect(item) {
console.log('item: ', item);
},
querySearch(queryString, cb) {
const restaurants = this.restaurants;
const results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
// 调用 callback 返回建议列表的数据
cb(results);
},
createFilter(queryString) {
return (restaurant) => {
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
};
},
loadAll() {
return [
{ "value": "三全鲜食(北新泾店)", "address": "长宁区新渔路144号" },
{ "value": "Hot honey 首尔炸鸡(仙霞路)", "address": "上海市长宁区淞虹路661号" },
{ "value": "新旺角茶餐厅", "address": "上海市普陀区真北路988号创邑金沙谷6号楼113" },
]
}
}
}
</script>
最后
走过路过,不要错过,点赞、收藏、评论三连~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。