vue知识总结

目录

为什么data必须是一个函数

一、实例和组件定义data的区别

一、实例和组件定义data的区别

vue常用的指令

v-if与v-show的区别

Vue生命周期的执行过程

Vue的双向数据绑定原理

浏览器的回流与重绘

Vue中的虚拟dom

vue中的diff算法

Vue组件通信(父传子、子传父、兄弟通信)

一.父组件传到子组件

二.子组件向父组件传值

3. 非父子传参 (事件总线)

事件总线推荐下面写法:

vue监听和深度监听watch

Deep    handler    immediate

computed,methods,watch的区别

mvvm和mvc

三者的关系:

 vue中的修饰符

一、事件修饰符

二、 v-model的修饰符

三、键盘事件的修饰符

四、element的修饰符  

slot插槽

keep-alive

v-for为什么要使用key

$nextTick()的作用


为什么data必须是一个函数

一、实例和组件定义data的区别

vue实例中的data属性既可以是一个对象,也可以是一个函数

const app = new Vue({
    el:"#app",
    // 对象格式
    data:{
        foo:"foo"
    },
    // 函数格式
    data(){
        return {
             foo:"foo"
        }
    }

组件中定义data属性,只能是一个函数

如果为组件data直接定义为一个对象

Vue.component('component1',{
    template:`
组件
`, data:{ foo:"foo" } })

则会得到警告信息

在这里插入图片描述

警告说明:返回的data应该是一个函数在每一个组件实例中

一、实例和组件定义data的区别


上面讲到组件data必须是一个函数,不知道大家有没有思考过这是为什么呢?

在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例

这里我们模仿组件构造函数,定义data属性,采用对象的形式

function Component(){
 
}
Component.prototype.data = {
 count : 0
}

创建两个组件实例

const componentA = new Component()
const componentB = new Component()

修改componentA组件data属性的值,componentB中的值也发生了改变

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 1

产生这样的原因这是两者共用了同一个内存地址,componentA修改的内容,同样对componentB产生了影响

如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)

function Component(){
 this.data = this.data()
}
Component.prototype.data = function (){
    return {
     count : 0
    }
}

修改componentA组件data属性的值,componentB中的值不受影响

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 0

vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

三、总结
根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况

组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

vue常用的指令

  1. v-model 多用于表单元素实现双向数据绑定
  2. v-for 格式: v-for="(item,index) in/of 数组json" 循环数组或json
  3. v-show 显示内容 ,通过display=block/none来控制元素隐藏出现
  4. v-hide 隐藏内容 同上
  5. v-if 显示与隐藏 (dom元素的删除添加 同angular中的ng-if 默认值为false)
  6. v-else-if 必须和v-if连用
  7. v-else 必须和v-if连用 不能单独使用 否则报错 模板编译错误
  8. v-bind 动态绑定 作用: 及时对页面的数据进行更改
  9. v-on:click 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面
  10. v-text 解析文本
  11. v-html 解析html标签
  12. v-bind:class 三种绑定方法 1、对象型 ‘{red:isred}’ 2、三元型 ‘isred?“red”:“blue”’ 3、数组型 ‘[{red:“isred”},{blue:“isblue”}]’
  13. v-once 进入页面时 只渲染一次 不在进行渲染
  14. v-cloak 防止闪烁 该属性需配合 样式使用:


{{message}}

15.v-pre 把标签内部的元素原位输出

v-if与v-show的区别

一、相同点
都可以动态控制着dom元素的显示隐藏

一、区别
v-if: 控制DOM元素的显示隐藏是将DOM元素整个添加或删除;

v-show: 控制DOM 的显示隐藏是为DOM元素添加css的样式display,设置none或者是block,DOM元素是还存在的

二、性能对比
v-if有更高的切换消耗;
v-show有更高的初始渲染消耗

三、使用场景
v-if适合运营条件不大可能改变的场景下;
v-show适合频繁切换;

例如:
v-if:
在请求后台接口,用接口返回数据渲染生成多个元素的时候,由于网速太慢,导致数据刚开始没有值,此时我们页面也会进行渲染空数据就会报错。
我们可以把一个元素作为包装元素,并使用v-if 进行条件判断,如果有值,才会显示内部元素。最终的渲染不会包含这个元素,v-show是不支持这么做的。
 

{{seller.supports.length}}个
export default {
data() {
return {
   seller:null
}
},
//生命周期 - 创建完成(访问当前this实例)
created() {
  this.$axios.get('http://www.test.com').then(res=>{
      this.seller=res.data
  })
},
}

vue知识总结_第1张图片
组件刚创建,渲染完成后,请求未必能完成。所以一开始的seller.supports只是一个空值,必须在之前加上v-if,确保有值以后再开始计算length

  
{{seller.supports.length}}个

v-show:
商品分类选项卡,频繁切换;

Vue生命周期的执行过程

答: 总共分为8个阶段。创建前/后,载入前/后,更新前/后,销毁前/后。

创建前/后: 在beforeCreated阶段,vue实例的挂载元素$el和数据对象 data 都为undefined,还未初始化。在 created阶段,vue实例的数据对象data有了,$el还没有。

载入前/后: 在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

更新前/后: 当data变化时,会触发beforeUpdate和updated方法。

销毁前/后: 在destroy阶段,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。destroyed阶段,组件销毁

是不是很简单,看完了吸口气,配合下图,理解更清楚
vue知识总结_第2张图片

Vue的双向数据绑定原理

答:

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。

具体步骤:

第一步: 需要observer(观察者)对数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

第二步: compile(模板解析器)解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步: Watcher(订阅者)是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

第四步: MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

回答以上内容即可,下方内容,可以帮助大家理解

名词解释:

1. 什么是setter、getter
答:首先,别误以为他们就是一会要说的get、set

对象有两种属性:

数据属性: 就是我们经常使用的属性
访问器属性: 也称存取器属性(存取器属性就是一组获取和设置值的函数)
再看一行代码:
vue知识总结_第3张图片

 log打印出来的如下:

vue知识总结_第4张图片

数据属性就是a和b;

get和set就是关键字 它们后面各自对应一个函数,这个函数就是上面红字部分所讲的,存储器属性。

get对应的方法称为getter,负责获取值,它不带任何参数。set对应的方法为setter,负责设置值,在它的函数体中,一切的return都是无效的。

2. 什么是Object.defineProperty() ?
答:我们先看一句定义:

对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。

定义对象可以使用构造函数或字面量的形式:
vue知识总结_第5张图片


除了以上添加属性的方式,当然还可以使用Object.defineProperty定义新属性或修改原有的属性;

语法:

Object.defineProperty(obj, prop, descriptor)

参数:

obj: 必需。目标对象;
prop: 必需。需定义或修改的属性的名字;
descriptor: 必需。目标属性所拥有的特性;

返回值:

传入函数的对象,即第一个参数obj;

OK,定义介绍完了,我们现在说一下一会关于双向绑定我们要用到的知识点:存取器描述;(诶?你是不是发现和上面好像有点关系)

对头,它是这样使用的:


vue知识总结_第6张图片

现在无论是你获取还是设置我们都可以接到通知,是不是有一点双向数据绑定的影子了,别急下面还有;

OK,我终于叨叨完没用的了,现在开始说正题,如何理解Vue的双向数据绑定,哈哈,先来一个定义:

Vue是采用数据劫持结合发布/订阅模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

我们来看一个很粗暴的栗子,low版双向绑定:
vue知识总结_第7张图片

 是不是有点明白了呢,当然这也不是全部,我们刚刚说的大概就是下面Observer的部分,对每个vue中的data中定义的属性循环用Object.defineProperty()实现数据劫持,以便利用其中的setter和getter,然后通知订阅者,订阅者会触发它的update方法,对视图进行更新。
vue知识总结_第8张图片

别急,下面也很简单:

Dep,它就像一个依赖管理一样,小伙伴又问啥是依赖管理倪? 上图!

vue知识总结_第9张图片

我用一个例子来解释一下上面这张图 下面高能预警:

在vue中v-model,v-name,{{}}等都可以对数据进行展示,也就是说假如一个属性都通过这三个指令了,那么每当这个属性改变的时候,相应的这个三个指令的html视图也必须改变;

于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者(addSub),其订阅者只是更新自己的指令对应的数据,也就是v-model='name'和{{name}}有两个对应的订阅者,各自管理自己的地方;

每当属性的set方法触发,就循环更新Dep中的订阅者(notify);

OK,Dep是不是很明白了呢

集合上面的那张图来看,就是Observer一旦有了set触发,就会通知到Dep,那Dep接到通知之后呢?从图上来看,下面所讲的就应该是Compile了,也很简单:

首先,先要知道它负责干什么?

compile主要做的事情是解析模板指令,将模板中的变量替换成数据

其次知道它什么时候要工作,只有两种情况,先上图:
vue知识总结_第10张图片

1)初始化,init的时候 初始化渲染页面视图;

2)将每个指令对应的节点绑定更新函数,添加监听数据的订阅者;

Dep负责维护依赖,而订阅者则来自于compile,一旦有数据变动,则会绑定更新函数,此时也就是产生了订阅者,这个时候Dep内就增加了一个订阅者,而一旦数据变动,则会收到通知,更新视图;

好了,你是不是觉得上面这行说不通,或是读不通,当然,因为上面的这个流程了缺少了,我们最后要说的Watcher,我把上面这句话补全,就是Watcher的工作了;

Dep负责维护依赖,而订阅者则来自于compile,一旦有数据变动,则会通过Watcher绑定更新函数,此时Watcher也向Dep中添加了订阅者,一旦Dep接到Observer的通知,它就会再去通知Watcher,Watcher则会调用自身的update()方法,并触发Compile中绑定的回调,更新视图;

最后敲黑板:

首先我们为每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep;

然后在编译的时候在该属性的数组dep中添加订阅者,v-model会添加一个订阅者,{undefined{}}也会,v-bind也会,只要用到该属性的指令理论上都会;

接着为input会添加监听事件,修改值就等于为该属性赋值,则会触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。

下面让我们来看代码演示:

先简单的实现一个js的双向数据绑定来熟悉一下Object.defineProperty()方法
 

 
    输入的值为:
    

这样我们就能实现js的双向数据绑定,随着文本框输入文字的变化,span中会同步显示相同的文字内容;这样就实现了 model => view 以及 view => model 的双向绑定。
通过给in输入框添加事件监听input来触发obj对象的set方法,而set再修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本。

浏览器的回流与重绘


回流:当页面元素的尺寸,样式,布局,dom增删发生改变时,浏览器会重新构造页面,这就是回流

重绘:当页面中的元素属性发生改变,例如颜色,背景色,而这些属性只影响元素的外观风格,不影响布局时,浏览器就会进行重绘,修改这些更改的属性

回流与重绘的区别

回流一定会引起重绘,但是重绘不一定要回流,只有在页面的布局和几何属性发生改变时,才会进行回流,例如:删除元素后引发元素位置的改变,属性导致大小,边框,内外边距改变等

如何避免重绘

1 集中改变样式,通过同样的类名进行改变

2 将频繁操作变为一次性操作,例如进行for循环添加dom结构或者修改数据


Vue中的虚拟dom

一、什么是虚拟DOM
通过js创建一个Object对象来模拟真实DOM结构,这个对象包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,通过vue中的render()函数把虚拟dom编译成真实dom,在通过appendChild()添加到页面中。

再来一张图:
vue知识总结_第11张图片

是不是一下子秒懂 没懂再来一张

vue知识总结_第12张图片

创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

在vue中如何应用虚拟DOM的

定义真实DOM


   

节点内容


   

{{ foo }}


实例化vue

const app = new Vue({
    el:"#app",
    data:{
        foo:"foo"
    }
})
 

const app = new Vue({
    el:"#app",
    data:{
        foo:"foo"
    }
})
 

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  functionalContext: Component | void; // only for functional component root nodes
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions
  ) {
    /*当前节点的标签名*/
    this.tag = tag
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
    /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.functionalContext = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
    /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    this.isOnce = false
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next https://github.com/answershuto/learnVue*/
  get child (): Component | void {
    return this.componentInstance
  }
}

这里对VNode进行稍微的说明:

  • 所有对象的 context 选项都指向了 Vue 实例
  • elm 属性则指向了其相对应的真实 DOM 节点
    vue是通过createElement生成VNode

源码create-element.js

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

上面可以看到createElement 方法实际上是对 _createElement 方法的封装,对参数的传入进行了判断

export function _createElement(
    context: Component,
    tag?: string | Class | Function | Object,
    data?: VNodeData,
    children?: any,
    normalizationType?: number
): VNode | Array {
    if (isDef(data) && isDef((data: any).__ob__)) {
        process.env.NODE_ENV !== 'production' && warn(
            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
            'Always create fresh vnode data objects in each render!',
            context`
        )
        return createEmptyVNode()
    }
    // object syntax in v-bind
    if (isDef(data) && isDef(data.is)) {
        tag = data.is
    }
    if (!tag) {
        // in case of component :is set to falsy value
        return createEmptyVNode()
    }
    ... 
    // support single function children as default scoped slot
    if (Array.isArray(children) &&
        typeof children[0] === 'function'
    ) {
        data = data || {}
        data.scopedSlots = { default: children[0] }
        children.length = 0
    }
    if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
    } else if ( === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
    }
 // 创建VNode
    ...
}

可以看到_createElement接收5个参数:

context 表示 VNode 的上下文环境,是 Component 类型

tag 表示标签,它可以是一个字符串,也可以是一个 Component

data 表示 VNode 的数据,它是一个 VNodeData 类型

children 表示当前 VNode的子节点,它是任意类型的

normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,主要是参考 render 函数是编译生成的还是用户手写的

根据normalizationType 的类型,children会有不同的定义
 

if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
} else if ( === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
}

simpleNormalizeChildren方法调用场景是 render 函数是编译生成的

normalizeChildren方法调用场景分为下面两种:

render 函数是用户手写的
编译 slot、v-for 的时候会产生嵌套数组
无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范(使children 变成了一个类型为 VNode 的 Array),这里就不展开说了

规范化children的源码位置在:src/core/vdom/helpers/normalzie-children.js

在规范化children后,就去创建VNode
 

let vnode, ns
// 对tag进行判断
if (typeof tag === 'string') {
  let Ctor
  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  if (config.isReservedTag(tag)) {
    // 如果是内置的节点,则直接创建一个普通VNode
    vnode = new VNode(
      config.parsePlatformTagName(tag), data, children,
      undefined, undefined, context
    )
  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
    // component
    // 如果是component类型,则会通过createComponent创建VNode节点
    vnode = createComponent(Ctor, data, context, children, tag)
  } else {
    vnode = new VNode(
      tag, data, children,
      undefined, undefined, context
    )
  }
} else {
  // direct component options / constructor
  vnode = createComponent(tag, data, context, children)
}

createComponent同样是创建VNode

源码位置:src/core/vdom/create-component.js

export function createComponent (
  Ctor: Class | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array,
  tag?: string
): VNode | Array | void {
  if (isUndef(Ctor)) {
    return
  }
 // 构建子类构造函数 
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // 安装组件钩子函数,把钩子函数合并到data.hook中
  installComponentHooks(data)

  //实例化一个VNode返回。组件的VNode是没有children的
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  return vnode
}

稍微提下createComponent生成VNode的三个关键流程:

构造子类构造函数Ctor
installComponentHooks安装组件钩子函数
实例化 vnode
小结

createElement 创建 VNode 的过程,每个 VNode 有 children,children
每个元素也是一个VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树结构

vue中的diff算法

diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。

Vue组件通信(父传子、子传父、兄弟通信)

一.父组件传到子组件


父组件是通过props属性给子组件通信的
数据是单向流动 父—>子 (子组件中修改props数据,是无效的,会有一个红色警告)

1 . 父组件parent.vue代码如下:

 
 

2 . 子组件son代码如下:

 
 

子组件通过props来接受数据

第一种方法

props: ['childCom']

第二种方法

props: {
    childCom: String //这里指定了字符串类型,如果类型不一致会警告的哦
}

第三种方法

props: {
    childCom: {
        type: String,
        default: 'sichaoyun' 
    }
}

二.子组件向父组件传值


通过绑定事件然后及$emit传值

vue2.0只允许单向数据传递,我们通过出发事件来改变组件的数据

1.父组件parent代码如下:

父组件通过绑定自定义事件,接受子组件传递过来的参数

 
 

2.子组件son代码如下:

子组件通过$emit触发父组件上的自定义事件,发送参数

 
 

3. 非父子传参 (事件总线)

假设你有两个Vue组件需要通信: A 和 B ,A组件按钮上面绑定了点击事件,发送一则消息,B组件接收。

1. 初始化,全局创建$bus
直接在项目中的 main.js 初始化 $bus :

// main.js
window.$bus=new Vue();

注意,这种方式初始化一个 全局的事件总线 。

2. 发送事件
$bus.$emit("aMsg", '来自A页面的消息');





接下来,我们需要在 B页面 中接收这则消息。

4. 接收事件
$bus.$on("事件名",callback)





<<<<<<<<<<<<<<下方是拓展,面试不必说>>>>>>>>>>>

事件总线推荐下面写法:


集中式的事件中间件就是 Bus。我习惯将bus定义到全局:
app.js


var eventBus = {
    install(Vue,options) {
        Vue.prototype.$bus = vue
    }
};
Vue.use(eventBus);

然后在组件中,可以使用$emit, $on, $off 分别来分发、监听、取消监听事件
分发事件的组件

// ...
methods: {
  todo: function () {
    this.$bus.$emit('todoSth', params);  //params是传递的参数
    //...
  }
}

监听的组件

// ...
created() {
  this.$bus.$on('todoSth', (params) => {  //获取传递的参数并进行操作
      //todo something
  })
},
// 最好在组件销毁前
// 清除事件监听
beforeDestroy () {
  this.$bus.$off('todoSth');
}

如果需要监听多个组件,只需要更改 bus 的 eventName:

// ...
created() {
  this.$bus.$on('firstTodo', this.firstTodo);
  this.$bus.$on('secondTodo', this.secondTodo);
},
// 清除事件监听
beforeDestroy () {
  this.$bus.$off('firstTodo', this.firstTodo);
  this.$bus.$off('secondTodo', this.secondTodo);
}

vue监听和深度监听watch

watch可以让我们监控一个值的变化,从而做出相应的反应。

    
用户名:

你的名字是:{{name}}

通过watch属性可以监控data属性中name值的变化,定义监控时,name这个值对应的是一个监控处理函数name()。

将name属性和对话框绑定,并在对应页面绑定name值,然后在浏览器中测试,页面上数据会随着对话框中输入值的变化而变化。

其中监控处理函数name有两个参数:

v1表示当前监控的值。
v2表示上一次监控的值。
如果监控的是一个对象,需要进行深度监控,才能监控到对象中属性的变化:
 


    
用户名:

你的名字是:{{person.name}}

你的年龄是:{{person.age}}

现在定义监控时,person是一个对象,并且要指定两个属性:

deep:代表深度监控,不仅监控person变化,也监控person中属性变化。
handler:就是以前的监控处理函数。
通过v.name和v.age获取对象中具体的值。

  1. watch监听器会监听data中数据的变化,只要一变化,就能够执行相应的逻辑
    2.监听的数据名放到这里面作为函数名,这个函数里面有两个参数,一个是新值,一个是旧值

与computed比较:

  • 对比computed而言,这个computed性能更好,所以能用computed实现就用computed实现。
  • 在涉及到异步数据操作的时候,就只能用watch去实现了。

Deep    handler    immediate

computed,methods,watch的区别


computed是计算属性,对应数据变化,自动计算,需要return返出最终的值,拥有缓存,数据不变时,调用缓存

methods是vue中的事件与方法,需要手动调用才能触发

watch是数据监听,不需要return返回值,数据变化自动监听,可判别变化前后的差异

mvvm和mvc

mvvm
概念:MVVM是 Model-View-ViewModel 的缩写,分别对应着:数据,视图,视图模型。Model是我们应用中的数据模型,View是我们的UI视图层,通过ViewModle,可以把我们Modle中的数据映射到View视图上,同时,在View层修改了一些数据,也会反应更新我们的Modle。简单理解就是双向数据绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。

(1)MVC:是后台的框架模式

分为M:(model模型)、V(view试图)、C(controller控制器)

(2)MVVM是为了实现MVC中的V

MVVM分为:M(model数据)、V(view试图)、VM(viewModel控制数据的改变和控制试图)

以vue为例:
view 对应 template,vm 对应 new Vue({…}),model 对应 data

1 . view 视图层


Hello, {{ name }}!

--View层 --VUE中的单向绑定 --View层 --VUE中的双向绑定

2 . model数据层

data: {
            name: ''               --Model层
        }

3 .VM层

 VUE框架已封装好
new Vue({…})

三者的关系:

view 可以通过事件绑定(或v-model指令)的方式影响 model,model 可以通过数据绑定的形式影响到view,viewModel是把 model 和 view 连起来的连接器

在这里插入图片描述

MVC模式概要(典型的框架有angular.js)
1 . 即Model、View、Controller即数据模型、视图、控制器。

View:视图层;
Model:业务数据层;
Controller: 控制器。接收View层传递过来的指令,选取Model层对应的数据,进行相应操作。

2.举一个现实中的类似的例子,MVC如同一家商铺的运作模式,View层相当于是这家商铺的店面,Model层相当于这家商铺的仓库,Controller层相当于是这家商铺的执行部门。

特点:MVC有如下两种模式,不管哪种模式,MVC的通信都是单向的,由图也可以看出,View层会从Model层拿数据,因此MVC中的View层和Model层还是存在耦合的。
在这里插入图片描述

 vue中的修饰符

一、事件修饰符

.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为





...
...
...

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用v-on:click.prevent.self会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

二、 v-model的修饰符

<1> .lazy
默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change事件再同步。


<2> .number
自动将用户的输入值转化为数值类型


<3> .trim
自动过滤用户输入的首尾空格


三、键盘事件的修饰符


在我们的项目经常需要监听一些键盘事件来触发程序的执行,而Vue中允许在监听的时候添加关键修饰符:


对于一些常用键,还提供了按键别名:

      

全部的按键别名:

.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
修饰键:

.ctrl
.alt
.shift
.meta




Do something

与按键别名不同的是,修饰键和 keyup 事件一起用时,事件引发时必须按下正常的按键。换一种说法:如果要引发 keyup.ctrl,必须按下 ctrl 时释放其他的按键;单单释放 ctrl 不会引发事件。



 





四、element的修饰符  

对于elementUI的input,我们需要在后面加上.native, 因为elementUI对input进行了封装,原生的事件不起作用。

slot插槽

插槽分为匿名插槽,具名插槽,作用域插槽

匿名插槽

子组件内部,设置 ,就可以使用匿名插槽,数据将方式在插槽对应的位置,如果有多个匿名插槽,将会每个插槽都有一份数据渲染

匿名插槽其实也是有名字的,为 default ,通常都是不写的

具名插槽

通过给插槽命名,以此来区分多个插槽

命名:v-slot:name  可简写为  #name

V2的使用
 

V3的使用

作用域插槽

通过自定义属性,来预备插槽所使用的数据

子组件中

子组件标签中

keep-alive

keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。

在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性

被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated(组件激活时使用) 与 deactivated(组价离开时调用)                    

如果需要缓存整个项目,直接在app.vue中用keep-alive包裹router-view即可。要缓存部分页面,需要在路由地址配置中,在meta属性中添加一个状态,在app.vue中判断一下包裹的router-view即可
 

v-for为什么要使用key

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点

$nextTick()的作用
 

$nextTick()是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

使用场景

你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,因为在此时还拿不到dom节点

如果是一个input框,在让他显示之后,立即让他自动获取焦点,是无法实现的,因为vue的render()函数将虚拟dom转换真实dom的时候,是异步的,无法在当时直接获取到dom,因此,可以使用this.$nextTick(),再里面书写获取焦点的代码,就可以实现这个功能了


vue-router

前端路由原理
路由就是用来解析URL以及调用对应的控制器,在不重新请求页面的情况下,更新页面的视图

路由模式
路由分为hash模式与history模式

hash与history的切换

在router的index.js文件中,有一个new Router(),里面的mode默认为hash模式,可以在这里更改为history模式
 

const router = new VueRouter({
    routes,
    mode:'hash'  //可在这里更改为history模式
})

hash模式
hash模式:在浏览器中符号的“#”,以及#后面的字符称之为hash

http://localhost:8080/#/login


特点

1 hash不会被包含在http请求中,因此,改变hash不会重新加载页面

2 hashchange 用来监听hash改变的事件

3 每一次改变,都会在浏览器中添加一个历史记录

方法

HashHistory.push     跳转路由,添加历史记录栈

HashHistory.replace()  跳转路由,直接替换当前页面,不会添加历史
 

history模式

http://localhost:8080/home

特点

1 history模式的URL要与后端的URL一样,后端如果没有对应路由,则会返回404错误

2 popState(window.onpopstate)用来监听history改变的事件

方法

history.pushState()   跳转路由,添加历史记录栈

history.replaceState()   跳转路由,直接替换当前页面,不会添加历史

路由传参
query,params,动态路由传参

(1)  params只能使用name  query可以使用name和path  

(2)  使用params传参刷新后丢失,而query传参刷新后不会丢失

(3)  Params在地址栏中不会显示,query会显示

(4)  Params可以和动态路由一起使用,query不可以

路由跳转

导航式路由

编程式路由

router.push( )

将有记录存放在历史栈中,点击后退,将返回上一级的记录

router-link to 就是调用的push

router.replace()

不会存放历史栈,而是替换了当前的历史

可以在router-link中添加replace或router.push对象中添加replace:true 激活当前功能

router.go(-3)

()里的参数为整数,指的是在历史栈中前进或后退的步数

-1为返回上一历史,0为刷新页面,1为返回下一历史

vue的路由守卫

路由守卫分为全局守卫,路由独享守卫,组件内守卫

每个守卫接收三个参数 

to        即将要进入的目标路由对象

from   当前导航正要离开的路由

next   钩子函数,里面定义参数,确认下一步路由要做什么

全局守卫

router.beforeEach 全局前置守卫  最合适用在判断进入的页面是否需要登录
router.beforeResolve

全局解析守卫  

在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

router.afterEach 全局后置钩子  一般用来设置动态的请求头   再这里next()是多余的

组件内守卫

beforeRouteEnter

进组组件前的守卫  

注意:在这里是拿不到vue的this的,但是可以通过传一个回调给 next来访问组件实例

next ( vm=> { console.log(vm) } )    

vm就是vue的组件实例

通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消

beforeRouteUpdate 路由更新时的守卫  
路由更新时的守卫   离开组件时的守卫  

完整的导航解析流程

1   在失活的组件里调用 beforeRouteLeave 组件离开守卫。
2   调用全局的 router.beforeEach 全局前置守卫。
3   在重用的组件里调用 beforeRouteUpdate 组件更新守卫 。
4   在路由配置里调用 beforeEnter 路由独享守卫
5   解析异步路由组件。
6   在被激活的组件里调用 beforeRouteEnter 组件进入守卫
7   调用全局的 router.beforeResolve 全局解析守卫 。
8   导航被确认。
9   调用全局的 router.afterEach 全局后置钩子。
10 触发 DOM 更新。
11  调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

重写route的方法看这里

vue2和vue3的区别

  1. 双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
  1. 可直接监听数组类型的数据变化
  2. 监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
  3. 可直接实现对象属性的新增/删除
  1. 默认使用懒加载

在2.x版本里。不管数据多大,都会在一开始就为其创建观察者,在数据很大时,就会造成性能的问题。在3.x中,只会对渲染出来的数据创建观察者,而且3.x的观察者更高效。

  1. 3.0新加入了TypeScript以及PWA支持
  2. 重构Virtual DOM
  1. 模板编译时的优化,将一些静态节点编译成常量
  2. Slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组织
  1. 生命周期有了一定的区别

      Vue2--------------vue3

beforeCreate  -> setup() 开始创建组件之前,创建的是data和method

created       -> setup()

beforeMount   -> onBeforeMount 组件挂载到节点上之前执行的函数。

mounted       -> onMounted 组件挂载完成后执行的函数

beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。

updated       -> onUpdated 组件更新完成之后执行的函数。

beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。

destroyed     -> onUnmounted 组件卸载之前执行的函数。

activated     -> onActivated 组件卸载完成后执行的函数

deactivated   -> onDeactivated

你可能感兴趣的:(vue.js,前端,javascript)