vue相关
vue的生命周期,父子执行顺序
- 加载渲染顺序
`父组件beforeCreate`->`父组件的created`->`父组件的beforeMount` -> `子组件beforeCreate`->`子组件的created`->`子组件的beforeMount` -> `子组件的mounted` -> `父组件的mounted`
- 父组件更新
父组件beforeUpdate->父组件的updated
- 子组件更新
父beforeUpdate->子beforeUpdate->子updated->父updated
- 销毁顺序
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue响应式实现
vue通过数据劫持+发布订阅者模式来实现响应式
vue首先会遍历data的每一个属性,通过`Object.defineProty`给属性添加`getter`和`setter`,
并通过发布订阅者模式,给每一个属性创建一个发布者(dep)。
每个组件实例都对应一个watcher实例(订阅者),在渲染过程中组件接触过的属性,将会触发这个属性的getter,从订阅者加入到发布者的队列中。
当属性改变。触发setter调用时,该属性的发布者会通知所有订阅者更新内容
vue.nextTick是的使用场景和原理
- 使用场景:在数据改变后,想要获取改变后的dom属性
Vue.component('example',
{
template: '<span>{{ message }}</span>',
data: function () {
return { message: '未更新' }
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
原理
Vue 在更新 DOM 时是**异步**执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 `Promise.then`、`MutationObserver` 和 `setImmediate`,如果执行环境不支持,则会采用 `setTimeout(fn, 0)` 代替。
什么时虚拟dom及虚拟dom的优劣
虚拟dom实际就是一个js对象,用来模拟真实dom的属性。
优点:
1、减少对真实dom的操作
2、跨平台的兼容性好
缺点:
1、首次大量渲染dom时,由于多了一层虚拟dom的计算,比innerHtml插入慢
diff算法的原理
diff算法是一种对比算法。
对比的两者是新虚拟dom和旧虚拟dom,找出更改的虚拟dom节点,只更新这个虚拟dom节点对应的真实dom。
vue.$set
使用场景: 向data中添加一个新的属性,并且保证这个属性是响应式的。
原理:vue响应式是通过数据劫持完成的,数据劫持在初始化vue的时候就完成了,所以在后面添加的属性,是没有响应式 的,就通过使用vue.set给新的添加属性。
set方法接收三个参数:
第一个参数: target是对象,或者数组
第二个参数: 想要添加的属性名或下标
第三个参数: 想要添加的值
判断如果`target`是数组,判断传入的下标是不是合法的,如果合法调用数组的splice方法(splice方法被vue改写,它会把对象的元素都变成响应式的,然后手动触发依赖通知),并且返回想要添加的值
如果`target`是对象,判断属性是否已存在,如果不存在,会获取 `target.__ob__` 并赋值给 `ob`,它是在 `Observer` 的构造函数执行的时候初始化的,表示 `Observer` 的一个实例,如果它不存在,则说明 `target` 不是一个响应式的对象,则直接赋值并返回。最后通过 `defineReactive(ob.value, key, val)` 把新添加的属性变成响应式对象,然后再通过 `ob.dep.notify()` 手动的触发依赖通知,
export function
(target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
v-for为什么要设置key
key的作用:
key主要作用在虚拟dom的计算,在新旧节点对比时辨识虚拟节点。如果不设置key或者key为index时,在diff算法进行新旧节点对比时,会命中新旧节点相同的逻辑,会只更新元素的文本节点(就地更新)。这种模式只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
使用key时,他会基于key的变化重新排列顺序,并且会移除key不存在的元素。
watch
选项
{
deep: 是否深度监听,为true时可以 监听对象属性的变化
immediate: 为true时,立即以表达式的当前值触发回调(用于子组件监听prop变化)
handler: function(newVal, oldVal){}
回调函数不能为箭头函数,箭头函数绑定父级作用域上下文,this不会指向vue
}
vm.$watch()会返回一个取消观察函数
var unwatch = vm.$watch('a', cb)
// 之后取消观察
unwatch()
minxin的用法及优缺点分析
特点:
1、可以定义公共的变量在各个组件中使用,并且各个组件中的变量都是独立的。
2、在引入组件后会与组件中的方法和属性合并,如果有相同的属性名或者方法名,组件的属性和方法会覆盖minxin中的。
3、如果在组件和minxin中都定义了相同的生命周期函数,会将生命周期合并为数组,先执行minxin的生命周期函数。
使用场景:
在项目中有大量相同的业务逻辑,可以写在mixin中混入组件。
优点:
可以分发vue组件中的可复用功能
缺点:
如果使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)
组件属性和方法继承,及对组件库的二次封装
$attrs: 包含父作用域中不作为prop被识别(且获取) 的 attribute 绑定 (`class` 和 `style` 除外),
如果父作用域声名任何prop时,,这里会包含所有父作用域的绑定 (`class` 和 `style` 除外),并且可以通过 `v-bind="$attrs"` 接收传给子组件
$listeners: 包含了父作用域中的 (不含 `.native` 修饰器的) `v-on` 事件监听器。它可以通过 `v-on="$listeners"` 接收父组件的事件传给子组件
使用$attrs和$listeners对element组件进行二次封装
子组件
<div>
<div>{{label}}</div>
<el-select :value="value" placeholder="Select" v-bind="$attrs" v-on="$listeners">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
export default {
name: 'HelloWorld',
inheritAttrs: false,
model: {
event: 'change',
prop: 'value'
},
props: [
'label', 'value'
],
data () {
return {
options: [],
}
}
}
父组件
<HelloWorld :label="msg" v-model="loveVue" :clearable="true" :multiple="true" :filterable="true" @focus="focus" @input="input" @change="change" @click="toggle" />
v-model的实现
v-model可以作用与一些表单组件和自定义组件上。 v-model实际上时一个语法糖,相当与使用v-bind和$emit
vuex的了解
vuex是vue的状态管理模式。可以多个组件共享状态。vuex是单一状态树。
vuex的属性: {
state:() => ({ ... }), // 储存一些全局状态
getters: {}, // store的计算属性
mutations: {}, // 同步操作,修改state中的数据
actions: {}, // 异步操作,通过mutations修改state中的数据
modules: {} // 模块化
}
使用:
state: this.$store.state.name / ...mapstate({})
getters: this.$store.getter.name / ...mapgetter({})
mutations: this.$store.commit(name, paylod) / ...mapMutation([]),
actions: this.$store.dispatch(name, paylod) / ...mapAction([])
mutations为什么要是同步的?
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。
在页面刷新之后vuex的状态会被清空,怎么保持vuex持久化:
1、手动储存在本地
2、使用`vue-persist`插件
vue-router的两种路由模式
1、hash模式(**HashHistory**)
特点:
1.1hash虽然出现在URL中,但不会被包括在HTTP请求中。它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面。
1.2 每一次改变hash(window.location.hash),都会在浏览器的访问历史中增加一个记录
1.3、可以添加事件监听hash的改变window.addEventListener("hashchange", funcRef, false)
原理:
HashHistory有两个方法HashHistory.push()和HashHistory.replace()
HashHistory.push()是将新路由添加到浏览器历史访问站中
HashHistory.replace()是替换当前路由
2、history模式( HTML5History)
特点:
2.1、 利用history.pushState()来完成URL 跳转而无须重新加载页面
2.2、 监听地址变化window.onpopstate
原理:
window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)
区别:
- pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
- pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
- pushState可额外设置title属性供后续使用
- history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误。
路由的钩子函数
全局前置守卫:beforeEach
全局解析守卫:beforeResolve
全局后置守卫:afterEach
路由独享守卫:beforeEnter
组件内守卫:- `beforeRouteEnter``beforeRouteUpdate`- `beforeRouteLeave`
完整导航解析流程
1. 导航被触发。
2. 在失活的组件里调用 `beforeRouteLeave` 守卫。
3. 调用全局的 `beforeEach` 守卫。
4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。
5. 在路由配置里调用 `beforeEnter`。
6. 解析异步路由组件。
7. 在被激活的组件里调用 `beforeRouteEnter`。
8. 调用全局的 `beforeResolve` 守卫 (2.5+)。
9. 导航被确认。
10. 调用全局的 `afterEach` 钩子。
11. 触发 DOM 更新。
12. 调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数,创建好的组件实例会作为回调函数的参数传入。
vue.config.js常用配置
const path = require('path')
module.exports = {
publicPath: './', // 基本路径
outputDir: 'dist', // 输出文件目录
lintOnSave: false, // eslint-loader 是否在保存的时候检查
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
// webpack配置
chainWebpack: (config) => {
},
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
config.mode = 'production'
} else {
// 为开发环境修改配置...
config.mode = 'development'
}
Object.assign(config, {
// 开发生产共同配置
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@c': path.resolve(__dirname, './src/components'),
'@p': path.resolve(__dirname, './src/pages')
} // 别名配置
}
})
},
// 生产环境是否生成 sourceMap 文件
productionSourceMap: false,
// css相关配置
css: {
extract: true, // 是否使用css分离插件 ExtractTextPlugin
sourceMap: false, // 开启 CSS source maps?
loaderOptions: {
css: {}, // 这里的选项会传递给 css-loader
postcss: {} // 这里的选项会传递给 postcss-loader
},
// css预设器配置项 详见https://cli.vuejs.org/zh/config/#css-loaderoptions
modules: false // 启用 CSS modules for all css / pre-processor files.
},
// 是否为 Babel 或 TypeScript 使用 thread-loader。
//该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
parallel: require('os').cpus().length > 1,
// PWA 插件相关配置 see
//https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
pwa: {},
// webpack-dev-server 相关配置
devServer: {
open: process.platform === 'darwin',
host: '0.0.0.0', // 允许外部ip访问
port: 8022, // 端口
https: false, // 启用https
overlay: {
warnings: true,
errors: true
}, // 错误、警告在页面弹出
proxy: {
'/api': {
target: 'http://www.baidu.com/api',
changeOrigin: true, // 允许websockets跨域
// ws: true,
pathRewrite: {
'^/api': ''
}
}
} // 代理转发配置,用于调试环境
},
// 第三方插件配置
pluginOptions: {}
}