总结一些vue中必知必会的知识点,让我们在面试之前能够胸有成竹一点~
一、Vue组件通信
1、父组件向子组件通信
方法一:props
<template>
<child :msg="message"></child>
</template>
复制代码
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
复制代码
方法二:使用$children
使用$children
可以在父组件中访问子组件。比如调用子组件的方法,并传入值等。
2、子组件向父组件通信
方法一:使用vue事件
父组件向子组件传递事件方法,子组件通过$emit
触发事件,回调给父组件。
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg); // ssss
}
}
}
</script>
复制代码
<template>
<button @click="handleClick">点我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc', 'ssss');
}
}
}
</script>
复制代码
方法二:使用$parent
使用$parent
可以访问父组件的实例,当然也就可以访问父组件的属性和方法了。
3、非父子组件、兄弟组件之间的数据传递
非父子组件通信,Vue官方推荐使用一个Vue实例作为中央事件总线。
意思就是将一个公共状态保存在一个这些组件共用的一个父组件上,这样就可以使用子组件通信父组件的方式来间接的完成通信了。
二、new Vue()之后都发生了什么?
vue入口源码如下:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
// initMixin给Vue.prototype添加:
// _init函数,
stateMixin(Vue)
/*
stateMixin给Vue.prototype添加:
$data属性,
$props属性,
$set函数,
$delete函数,
$watch函数,
...
*/
eventsMixin(Vue)
/*
eventsMixin给Vue.prototype添加:
$on函数,
$once函数,
$off函数,
$emit函数,
$watch方法,
...
*/
lifecycleMixin(Vue)
/*
lifecycleMixin给Vue.prototype添加:
_update方法:私有方法,用于更新dom,其中调用_patch产生跟新后的dom,
$forceUpdate函数,
$destroy函数,
...
*/
renderMixin(Vue)
/*
renderMixin给Vue.prototype添加:
$nextTick函数,
_render函数,
...
*/
export default Vue
复制代码
vue源码加载的时候,也就是在new Vue()之前做了一些初始化工作。
new Vue的时候会执行一个_init方法。代码如下:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
复制代码
从上面的代码我们看见_init
很清淅的干了几件事, 合并相关配置, 初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等
三、组件data为什么必须是函数?
因为组件可能被多处使用,但它们的data是私有的,所以每个组件都要return一个新的data对象,如果共享data,修改其中一个会影响其他组件。
四、vue 为什么采用Virtual DOM?
-
创建真实DOM的代价高:真实的 DOM 节点 node 实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
-
触发多次浏览器重绘及回流:使用 vnode ,相当于加了一个缓冲,让一次数据变动所带来的所有 node 变化,先在 vnode 中进行修改,然后 diff 之后对所有产生差异的节点集中一次对 DOM tree 进行修改,以减少浏览器的重绘及回流。
-
虚拟dom由于本质是一个js对象,因此天生具备跨平台的能力,可以实现在不同平台的准确显示。
-
Virtual DOM 在性能上的收益并不是最主要的,更重要的是它使得 Vue 具备了现代框架应有的高级特性。
五、Vue创建的一个大致过程
[图片上传中...(image-d35f22-1554733643705-12)]
<figcaption></figcaption>
首先实例化,然后数据绑定,组件挂在、内容替换。
结合上例子分析Vue的生命周期:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命周期'
},
beforeCreate: function() {
console.group('------beforeCreate创建前状态------');
console.log("%c%s", "color:red" , "el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //undefined
console.log("%c%s", "color:red","message: " + this.message)
},
created: function() {
console.group('------created创建完毕状态------');
console.log("%c%s", "color:red","el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeMount: function() {
console.group('------beforeMount挂载前状态------');
console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
mounted: function() {
console.group('------mounted 挂载结束状态------');
console.log("%c%s", "color:red","el : " + this.$el); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeUpdate: function () {
console.group('beforeUpdate 更新前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
updated: function () {
console.group('updated 更新完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
beforeDestroy: function () {
console.group('beforeDestroy 销毁前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
destroyed: function () {
console.group('destroyed 销毁完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message)
}
})
</script>
</html>
复制代码
[图片上传中...(image-6161f2-1554733643705-11)]
<figcaption></figcaption>
[1] 在beforeCreate和created钩子函数之间的生命周期
[图片上传中...(image-afa060-1554733643705-10)]
<figcaption></figcaption>
在这个阶段开始初始化事件、并且对数据进行了检测(劫持),这时候还没有el。
[2] created钩子函数和beforeMount间的生命周期
[图片上传中...(image-b96bca-1554733643705-9)]
<figcaption></figcaption>
- 首先会判断对象是否有el选项。如果有的话就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)。此时注释掉代码中:
el: '#app',
[图片上传中...(image-7104e2-1554733643705-8)]
<figcaption></figcaption>
如果我们在后面继续调用vm.$mount(el),可以发现代码继续向下执行了
- 然后,我们往下看,template参数选项的有无对生命周期的影响。
(1).如果vue实例对象中有template参数选项,则将其作为模板编译成render函数。 (2).如果没有template选项,则将外部HTML作为模板编译。 (3).可以看到template中的模板优先级要高于outer HTML的优先级。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue生命周期学习</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<!--html中修改的-->
<h1>{{message + '这是在outer HTML中的'}}</h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
template: "<h1>{{message +'这是在template中的'}}</h1>", //在vue配置项中修改的
data: {
message: 'Vue的生命周期'
}
</script>
</html>
复制代码
[图片上传中...(image-bfcbe7-1554733643705-7)]
<figcaption></figcaption>
那么将vue对象中template的选项注释掉后打印如下信息:
[图片上传中...(image-c90be6-1554733643705-6)]
<figcaption></figcaption>
这下就可以想想什么el的判断要在template之前了~是因为vue需要通过el找到对应的outer template。
在vue对象中还有一个render函数,它是以createElement作为参数,然后做渲染操作,而且我们可以直接嵌入JSX.
new Vue({
el: '#app',
render: function(createElement) {
return createElement('h1', 'this is createElement')
}
})
复制代码
所以综合排名优先级: render函数选项 > template选项 > outer HTML.
总之在这个阶段就是判断是否有el,或者template。然后进行处理
[3] beforeMount和mounted 钩子函数间的生命周期
[图片上传中...(image-dfc451-1554733643705-5)]
<figcaption></figcaption>
这段时间就是替换$el,将{{message}}转为真正的内容。
[4] mounted
[图片上传中...(image-3f2c66-1554733643705-4)]
<figcaption></figcaption>
在mounted之前h1中还是通过{{message}}进行占位的,因为此时挂在到页面上的还是JavaScript中的虚拟DOM形式。在mounted之后可以看到h1中的内容发生了变化。
[5] beforeUpdate钩子函数和updated钩子函数间的生命周期
当vue发现data中的数据发生了改变,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。
[6] beforeDestroy和destroyed钩子函数间的生命周期
beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。 destroyed钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
[图片上传中...(image-6ea275-1554733643705-3)]
<figcaption></figcaption>
[图片上传中...(image-642dda-1554733643705-2)]
<figcaption></figcaption>
六、自定义指令
[图片上传中...(image-8f46da-1554733643705-1)]
<figcaption></figcaption>
在代码的实例中
[图片上传中...(image-8d66a5-1554733643705-0)]
<figcaption></figcaption>
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
复制代码
七、Vue.js 插件开发详解
八、组件style的scoped
问题:在组件中用js动态创建的dom,添加样式不生效。
复制代码
<template>
<div class="test"></div>
</template>
<script>
let a=document.querySelector('.test');
let newDom=document.createElement("div"); // 创建dom
newDom.setAttribute("class","testAdd" ); // 添加样式
a.appendChild(newDom); // 插入dom
</script>
<style scoped>
.test{
background:blue;
height:100px;
width:100px;
}
.testAdd{
background:red;
height:100px;
width:100px;
}
</style>
复制代码
- 结果
// test生效 testAdd 不生效
<div data-v-1b971ada class="test"><div class="testAdd"></div></div>
.test[data-v-1b971ada]{ // 注意data-v-1b971ada
background:blue;
height:100px;
width:100px;
}
复制代码
原因
当 <style>
标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。 它会为组件中所有的标签和class
样式添加一个scoped
标识,就像上面结果中的data-v-1b971ada
。 所以原因就很清楚了:因为动态添加的dom没有scoped
添加的标识,没有跟testAdd
的样式匹配起来,导致样式失效。
- 解决办法:
去掉scoped即可。
九、Vue 数组/对象更新 视图不更新
data() { // data数据
return {
arr: [1,2,3],
obj:{
a: 1,
b: 2
}
};
},
// 数据更新 数组视图不更新
this.arr[0] = 'OBKoro1';
this.arr.length = 1;
console.log(arr);// ['OBKoro1'];
// 数据更新 对象视图不更新
this.obj.c = 'OBKoro1';
delete this.obj.a;
console.log(obj); // {b:2,c:'OBKoro1'}
复制代码
由于js的限制,Vue 不能检测以上数组的变动,以及对象的添加/删除,很多人会因为像上面这样操作,出现视图没有更新的问题。
解决方式:
- 1、this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)
this.$set(this.arr, 0, "OBKoro1"); // 改变数组
this.$set(this.obj, "c", "OBKoro1"); // 改变对象
复制代码
- 2、数组原生方法触发视图更新:
Vue可以监测到数组变化的,数组原生方法:
splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
复制代码
意思是使用这些方法不用我们再进行额外的操作,视图自动进行更新。
十、Vue 过滤器的作用
过滤器,通常用于后台管理系统,或者一些约定类型,过滤。Vue过滤器用法是很简单,但是很多朋友可能都没有用过。
<!-- 在双花括号中 -->
{{ message | filterTest }}
<!-- 在 `v-bind` 中 -->
<div :id="message | filterTest"></div>
复制代码
export default {
data() {
return {
message:1
}
},
filters: {
filterTest(value) {
// value在这里是message的值
if(value===1){
return '最后输出这个值';
}
}
}
}
复制代码
用法就是上面讲的这样,可以自己在组件中试一试就知道了,很简单很好用的。
十一、列表渲染相关
1、v-for循环绑定model:
input在v-for中可以像如下这么进行绑定,我敢打赌很多人不知道。
// 数据
data() {
return{
// 这里设置 key / value
obj: {
ob: "OB",
koro1: "Koro1"
},
// model 存储
model: {
ob: "默认ob",
koro1: "默认koro1"
}
}
},
// html模板
<div v-for="(value,key) in obj">
<input type="text" v-model="model[key]">
</div>
// input就跟数据绑定在一起了,那两个默认数据也会在input中显示
复制代码
2、v-if尽量不要与v-for在同一节点使用:
v-for
的优先级比 v-if
更高,如果它们处于同一节点的话,那么每一个循环都会运行一遍v-if。
如果你想根据循环中的每一项的数据来判断是否渲染,那么你这样做是对的:
<li v-for="todo in todos" v-if="todo.type===1">
{{ todo }}
</li>
复制代码
如果你想要根据某些条件跳过循环,而又跟将要渲染的每一项数据没有关系的话,你可以将v-if放在v-for的父节点:
// 数组是否有数据 跟每个元素没有关系
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
复制代码
如上,正确使用v-for与v-if优先级的关系,可以为你节省大量的性能。
十二、深度watch和watch立即触发回调
watch很多人都在用,但是这watch中的这两个选项deep、immediate,或许不是很多人都知道,我猜。
- deep
在选项参数中指定 deep: true,可以监听对象中属性的变化。
- immediate
在选项参数中指定 immediate: true
, 将立即以表达式的当前值触发回调,也就是立即触发一次。
watch: {
obj: {
handler(val, oldVal) {
console.log('属性发生变化触发这个回调',val, oldVal);
},
deep: true // 监听这个对象中的每一个属性变化
},
step: { // 属性
//watch
handler(val, oldVal) {
console.log("默认立即触发一次", val, oldVal);
},
immediate: true // 默认立即触发一次
},
},
复制代码
十三、这些情况下不要使用箭头函数:
- 不应该使用箭头函数来定义一个生命周期方法
- 不应该使用箭头函数来定义 method 函数
- 不应该使用箭头函数来定义计算属性函数
- 不应该对 data 属性使用箭头函数
- 不应该使用箭头函数来定义 watcher 函数
箭头函数绑定了父级作用域的上下文,this 将不会按照期望指向 Vue 实例。 也就是说,你不能使用this来访问你组件中的data数据以及method方法了。 this将会指向undefined。
十四、路由懒加载写法
// 我所采用的方法,个人感觉比较简洁一些,少了一步引入赋值。
const router = new VueRouter({
routes: [
path: '/app',
component: () => import('./app'), // 引入组件
]
})
// Vue路由文档的写法:
const app = () => import('./app.vue') // 引入组件
const router = new VueRouter({
routes: [
{ path: '/app', component: app }
]
})
复制代码
如果我们的路由比较多的话,是不是要在路由上方引入十几行组件?
第一种跟第二种方法相比就是把引入赋值的一步,直接写在component上面,本质上是一样的。两种方式都可以的,只是第一种是懒加载的方式。
十五、项目启动路由和404
export default new Router({
routes: [
{
path: '/', // 项目启动页
redirect:'/login' // 重定向到下方声明的路由
},
{
path: '*', // 404 页面
component: () => import('./notFind') // 或者使用component也可以的
},
]
})
作者:meils
链接:https://juejin.im/post/5ca3103bf265da30d02fa9ea
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。