Vue全解(精华摘要)

95 阅读6分钟

搞懂 Vue 需要先回答好以下五个问题:

  1. 初始化时可以写哪些对象?选项options里面有什么
  2. vm自身有哪些属性?
  3. Vue函数自身有哪些属性?
  4. Vue.prototype里面有哪些属性?
  5. Vue.prototype的原型链里面还有哪些属性?

1. Vue实例

const vm = new Vue(options)
// 可简写为 new Vue


vm.__proto__ = Vue.prototype
// 任何一个对象的原型 = 其构造函数的共有属性

vue.__proto__ = Function.prototype

vue的实例vm就是对象,optionsnew Vue的参数,vm会根据给的选项options得出一个对象,这个对象封装了对视图层的所有操作,包括数据读写、事件绑定、DOM更新(不包括网络层的AJAX

image.png

2. new Vue的参数options里面有什么:

options的五类属性具体内容
数据
DOM
生命周期钩子
资源
组合

2.1 数据

数据备注
data定义内部数据
methods定义方法
事件处理函数 或 普通函数

每次渲染都会执行
props定义外部属性
computed定义计算属性
watch监听data变化时做响应改变
propsData本质是 propsValue
单元测试时用到
全局扩展时的数据传递

方法与函数的区别:方法是面向对象的,函数是数学概念;下面代码在面向对象里称为方法,在数学里称为函数,而方法依附于对象,也就是对象.方法,而在经典面向对象里,sayhi()是不能单独出现的,JS融合后,就可以单独写。

obj.sayhi()    //sayhi() 就是方法

function (p1,p2){
    return xx
}

2.1.1 data 内部数据(支持对象和函数,优先用函数)

  • 用函数的原因?
    通俗的理解:为了组件的复用,使得每个组件都可以有一份data的拷贝,防止不同组件修改数据时被相互覆盖;防止指向同一个内存地址。
import Demo from './Demo.vue'

render: h => h(Demo) // 把 Demo 传给new Vue后,会自动把Demo前面加 new Vue(Demo)

render:h => h(X, [h(Demo),h(Demo)]) 
// data若为对象,前后两次传demo,就会把一个内部数据传给两个组件,共用一个data,其中一个data修改另外一个也会变化

// data若为函数,先会调用Demo.data()得到Demo真正的data,第二次调用的时候,还是会调用 
// 使得每次调用都是一个全新的对象,避免了两个组件共用data的问题
Demo._data = Demo.data()  

  • data 的使用
const Vue = window.Vue
new Vue({
    data:{     // data为一个对象
        n:0
    },
    data:function(){     //data为函数,可缩写为 data(){}
        return{
          n:0
        }
    },    
    template:`
    <div id="app">
    {{n}}
    <button @click="add">+1</button>
    </div>
    `,
    methods:{
      add(){
      this.n += 1
      }
    }    
}).$mount('#app')
  • data扩展之:settergetter
let obj0 = {
  姓: "高",
  名: "圆圆",
  age: 18
};

//如何得到姓名
let obj1 = {
  姓: "高",
  名: "圆圆",
  姓名(){
      return this.姓 + this.名
  },
  age: 18
};

console.log("需求一:" + obj.姓名())

//删掉姓名函数后面的括号,不用加括号也能得出值

let obj2 = {
  姓: "高",
  名: "圆圆",
  get 姓名(){
      return this.姓 + this.名
  },
  age: 18
};
console.log("需求一:" + obj2.姓名);
// getter就是不加括号的函数,上例中`obj2.姓名`即为`计算属性`

let obj3 = {
  姓: "高",
  名: "圆圆",
  get 姓名(){
      return this.姓 + this.名
  },
  set 姓名(xxx){
      this.姓 = xxx[0]
      this.名 = xxx.substring(1)
  },
  age: 18
};

obj3.姓名 = '高圆圆'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
// setter就是用'xxx'触发'set函数'

  • data扩展之:Object.defineProperty

  • Object.defineProperty

  1. 可以给对象添加属性value
  2. 可以给对象添加getter/setter
  3. getter/setter用于对属性的读写进行监控

适用于:定义完一个对象之后,想在它身上额外的添加gettersetter,通过Object.defineProperty定义属性来实现,所传的参数,第一个是define的对象,第二个是定义的具体内容,

var _xxx = 0
Object.defineProperty(obj3, 'xxx',{
    get(){
        return _xxx  //_xxx用来存放其值
    },
    set(value){
        _xxx = value
    }
})
  • vm = new Vue({data: myData})
  1. 会让vm成为myData的代理(proxy)
  2. 会让myData的所有属性进行监控
  3. 为什么要监控,为了防止myData的属性变了,vm不知道;
  4. vm知道属性变了就可以调用render(data),UI=render(data)
  • 啥是代理(设计模式)
  1. 对myData对象的属性读写,全权由另一个对象vm负责
  2. vm就是myData的代理

  • data扩展之:数据响应式

Vue的data是响应式:

const vm = new Vue({data:{n:0}})

我如果修改vm.n,那么UI中的n就会响应我 Vue通过Object.defineProperty来实现数据响应式


  • data扩展之:Vue.set或者this.$set

对象中新增的 key

Vue 没有办法事先监听和代理 要使用 set 来新增 key,创建监听和代理,更新 UI 最好提前把属性都写出来,不要新增 key 但数组做不到「不新增 key」


  • data扩展之:data中数组

数组中新增的 key

用 set 新增 key,会更新 UI,但不会创建监听和代理 不过尤玉溪篡改了 7 个 API 方便你对数组进行增删 这 7 个 API 会更新 UI,但不会自动处理监听和代理 this.array[n] = xxx,即不会更新 UI,也不会自动处理监听和代理 结论:数组新增 key 最好通过 7 个 API


2.1.2 methods:事件处理函数或普通函数

const Vue = window.Vue
new Vue({
    data(){
      return{
        n:0,
        array:[1,2,3,4,5,6,7,8]
      }
    },
    template:`
    <div id="app">
    {{n}}
    <button @click="add">+1</button>
    <hr>
    {{filter()}}
    </div>
    `,
    mothods:{
      add(){
        this.n += 1
      },
      filter(array){
        this.array.filter(i => i % 2 === 0)
      }
    
    }

  • props外部属性:与data内部数据有所不同,由外部来传值。
<template>
 <div class="red">
    这里是 Demo 的内部
    {{message}}
 </div>
</template>

<script>
export default{ 
    props:['message']  //声明方式是写上属性名,从外部接受一个'message',并自动绑在this上面
}
</script>

<style scoped>
.red{
    color:red;
    border: 1px solid red;
}
</style>
const Vue = window.Vue
window.Vue.config.productionTip = false 

import Demo from './Demo.vue'  

new Vue({
  components:{Demo},
  data:{
    visible: true
  },
  template:`
    <Demo message="你好 props"/>  //从外部传'message',传的方式是在组件后面,加上key=value
  `,
  methods:{
    toggle(){
      this.visible = !this.visible
    }
  }
  // render: h=>h(Demo)
}).$mount('#frank')


  • propers能够把内部数据data内容传入呢?

默认的html写法,传的是字符串,比如把data里的变量n写入,却不是显示0,而是显示字符串;如何把变量n传入呢?需要在key前面加上冒号:<Demo :message="">,加上冒号就是说其后面的内容不是字符串,而是JS代码,也就是说写成<Demo :message=" n ">,意思是变量n,优先从内部数据data里面找,因为data有n的定义n:0,所以会显示为0,总结为以下两种写法都表示,传字符串0,而且带上冒号:

template:`
  <div>
      <Demo message="0"/>
      <Demo :message="  '0'  "/>
  </div>
`

propers能否传methods方法呢?实现逻辑为:main.js里面的实例的add方法,传给了Demo.vueDemo.vue里面的click事件调用了add,让子元素去add这个n,也可以直接再子元素里面添加:message="n",也实现在子元素里add这个n

<template>
 <div class="red">
    这里是 Demo 的内部
    {{message}}
    <button @click="fn">call fn</button>
 </div>
</template>

<script>
export default{ 
    props:['message','fn']  //声明
}
</script>
import Demo from './Demo.vue'  

new Vue({
  components:{Demo},
  data:{
    visible: true,
    n:0
  },
  template:`
    <div>
      {{n}}
      <Demo :message="n" :fn="add"/>
    </div>
    `,
  methods:{
    add(){
      this.n += 1
    },

2.2 DOM

DOM备注
el指定替换数据的容器
等同于 append
与$mount有替换关系
template(完整版)html + 占位符
render(非完整版)很少用 不会手写
用 vue-loader 生成 render
renderError

2.2.1 el:挂载点

new Vue({
    el:'#app'  //对应index.html里面的元素id属性
    render:h =>h(Demo)
}).$mount('#app')  //与el效果相同,可以二选一
const vm = new Vue({
    render:h =>h(Demo)
})
vm.$mount('#app')

2.3 生命周期钩子(本质就是回调函数):

Vue在关键时刻调用(勾住)的特殊名称的函数
生命周期函数this指向vm或组件对象;

主要流程回调函数
挂载beforeCreate
挂载created
挂载beforeMount
挂载mounted
更新beforeUpdate
更新updated
销毁beforeDestroy
销毁destroyed

一切的起源是 new Vue(),先来解释周期:

(一)挂载流程

  • Init Events & Lifecycle

初始化:生命周期、事件、但数据代理还未开始

  • beforeCreate

此时无法通过vm访问到data中的数据、methods中的方法;

  • Init injections & reactivity

初始化:数据监测、数据代理

  • created

此时可以通过vm访问到data中的数据、methods中配置的方法;

  • Compile el's outerHTML as template

此阶段开始解析模板,生成虚拟DOM,页面还不能显示解析好的内容

  • beforeMount

页面呈现未经Vue编译的DOM结构,所有对DOM操作最终都不奏效

  • Create vm $el and replace 'el' with it

将内存中的虚拟DOM转为真实DOM插入页面

  • mounted

重要的钩子:页面呈现的是经Vue编译的DOM,所有对DOM操作有效,初始化过程结束。

(二)更新流程

  • beforeUpdate

此时数据时新的,页面是旧的

  • Virtual DOM re-render and patch

完成了Model到View的更新

  • updated

此时数据时新的,页面是新的

(三)销毁流程

  • beforeDestroy

马上要执行销毁过程,vm中的data等处于可用状态

  • Teardown watchers,child components and event listeners

  • destroyed


createElement('div')创建完created 
--> append('#app')挂载mounted 
--> n:0 =>1  更新updated 
--> 消亡destroyed 
created(){
    debugger   //可以判断钩子是否生效
    console.log('这个死鬼出现在内存中,没有出现在页面中')
  },
  mounted(){
    console.log('我已出现在页面中')
  },
  updated(){
    console.log('更新了')
    console.log(this.n)
  },
  destroyed(){
    console.log('已经消亡了')
  },

destroyed钩子需要写一个组件:

<template>
 <div class="red">
    {{n}}
    <button @click="add">+1</button>
 </div>
</template>

<script>
export default{ 
  data(){
    return{
      n:0,
      array:[1,2,3,4,5,6,7,8,9]
  }},
  created(){
    console.log('实例出现在内存中,没有出现在页面中')
  },
  mounted(){
    console.log('实例出现在页面中',Vue完成模板的解析并把初始的真实DOM元素放入页面后调用mounted,完成挂载)
  },
  updated(){
    console.log('实例更新了')
    console.log(this.n)
  },
  destroyed(){
    console.log('实例已经消亡了')
  },
  methods:{
    add(){
      this.n += 1
    },
  },
}
</script>

<style>
</style>
const Vue = window.Vue
window.Vue.config.productionTip = false 

import Demo from './Demo.vue'  

new Vue({
  components:{Demo},
  data:{
    visible: true
  },
  template:`
    <div>
    <button @click="toggle">toggle</button>
    <hr>
    <Demo v-if="visible === true "/>
    </div>
  `,
  methods:{
    toggle(){
      this.visible = !this.visible
    }
  }
  // render: h=>h(Demo)
}).$mount('#frank')


2.4 资源

资源备注使用
directives 指令减少DOM操作的重复全局用 Vue.directive('x',{...})
局部用options.directives
filters 过滤器数组里筛选尽量少用
components 组件引用其他vue文件

2.5 组合

组合备注使用
mixins 混入减少data、methods、钩子的重复--
extends 扩展作用跟mixin一样,也是复制全局用 Vue.extend({...})
局部用options.extends:{...}
provide 依赖大范围、隔n代共享信息 祖先提供东西--
inject 注入大范围、隔n代共享信息 后代注入东西--
parent--全局用 Vue.mixin({...})
局部用options.mixins:[mixin1,mixin2]

3. Vue组件(components)

3.1 何为组件:

实现应用中局部功能代码和资源的集合;
区别于new Vue({})的实例写法,而是用Vue.component或者是Demo.vue的文件就称之为组件;

  • 非单文件组件:一个文件中包含有n个组件 (样式不写在文件里)
  • 单文件组件:一个文件中只包含有1个组件

3.2 单文件组件

Xx.vue文件是要通过vue/cli脚手架的方式编译(也可以通过webpack编译)

  • 结构:
<template>
  //组件的结构
<template>

<script>
//组件交互相关的代码(数据、方法)
export default {
    name:'Money'
}

</script>

<style>
//组件的样式
</style>

3.3 如何引入Vue组件

  • 方法一:在new Vue 里用 components:{},优先使用此方法。
<template>
    <div class='red'>
      demo
    <div>
</template>
const Vue = window.Vue
window.Vue.config.productionTip = false

import Demo from './Demo.vue'  //1)先引入组件详细内容的vue文件

const vm = new Vue({
  components:{      //2)引入组件的方法一
     Frank:Demo    //我要用的组件名为Frank,值为Demo
  },
  template:`
  <div id="app">
    {{n}}
    <button @click="add">+1</button>
    <Frank/>    //3)对应的是Demo组件
    <hr>
    {{filter()}}
  </div>
    `,
})
  • 方法二:声明一个全局的,整个demo都可以用,直接把组件的内容写完整,用Vue.component()
//1)使用组件方法二,直接写内容
Vue.component("Demo2",{     
  template:`
    <div>demo2222222</div>
  `
})

new Vue({ 
  data(){
    return{
      n:0,
      array:[1,2,3,4,5,6,7,8,9]
  }},
  template:`
    <div class="red">
    {{n}}
    <button @click="add">+1</button>
    <Demo2/>     //2)对应的是Demo2组件
    <hr>
    {{filter()}}
    </div>
  `,
  ...
  • 方法三:结合两种方法

这种写法可以视为组件就是实例中的实例,对象Frank的写法跟外面实例中的写法完全一样的,

import Demo from './Demo.vue' 

new Vue({ 
  components:{
    Frank:{
      data(){
        return {n:0}
      },
      template:`
      <div>Frank's n:{{n}}</div>
      `
    }
  },
  data(){
    return{
      n:0,
      array:[1,2,3,4,5,6,7,8,9]
  }},
  template:`
    <div class="red">
    {{n}}
    <button @click="add">+1</button>
    <Frank/>
    <hr>
    {{filter()}}
    </div>
  `,
...
  • 综合三种方法,有一种简洁写法:
import Demo from './Demo.vue' 

new Vue({ 
  components:{
    Frank: Demo   //可以把Frank直接命名为Demo
      },
  • 可以把Frank直接命名为Demo,可以简写为:
new Vue({ 
  components:{
    Demo: Demo   
    //合并为{Demo}
      },
import Demo from './Demo.vue' //大小写不敏感,组件最好首字母大写,避开原生的
new Vue({ 
  components:{Demo},
  template:`
  ...
  <Demo/>
  ...
  `

本例完整代码;

const Vue = window.Vue
window.Vue.config.productionTip = false 

import Demo from './Demo.vue'  
console.log(window.Vue)  

new Vue({ 
  components:{
    Frank:Demo
  },
  data(){
    return{
      n:0,
      array:[1,2,3,4,5,6,7,8,9]
  }},
  template:`
    <div class="red">
    {{n}}
    <button @click="add">+1</button>
    <Frank/>
    <hr>
    {{filter()}}
    </div>
  `,
  methods:{
    add(){
      this.n += 1
    },
    filter(array){
      return this.array.filter(i=>i % 2 ===0)
    }
  },
}).$mount('#frank')