vue 从入门到精通之【数据操作(data,methods,computed,watch)+filter】(二)

10 篇文章 1 订阅

Vue.prototype

vm.$watch 设置监听器,和直接在配置项中写的 watch 类似

vm.$set 设置响应式数据的值

vm.$delete 删除某个响应式数据

vm.$destroy 销毁这个组件/实例 会触发 beforeDestory 和 destroyed 周期函数

vm.$forceUpdate 强制通知组件更新「一般响应式数据更改后,组件会更新;但是非响应式数据更新后,我们也想让组件更新,可以基于这个办法」

vm.$mount 把编译后的内容挂载到某个容器中

vm.$nextTick 响应式数据更新,视图重新渲染,等到虚拟DOM已经变为真实DOM后触发这个函数

vm .emit/on/$off 发布订阅,实现父子组件通信

vm.$once 监听一个事件「只触发一次,原理是在第一次触发完,就移除了事件绑定」

Vue组件

创建 xxx.vue 就是创建一个vue组件,每一个vue组件都是 VueComponent 这个类的实例「而VueComponent是Vue的子类」 在组件的周期函数或者方法中,基于this就可以获取这个组件的实例

私有属性:和Vue实例具备相同的私有属性和特点

data设置的值是响应式数据,会挂载到实例上

methods设置的方法也会挂载到实例上,不是响应式数据

props接收的属性也会挂载到实例上,不是响应式数据

computed计算属性也会挂载到实例上,不是响应式数据

filters设置的函数,并没有挂载到实例上,而是在模板编译的时候,直接把函数执行,把返回结果渲染编译了

小技巧:

实例.proto -> VueComponent.prototype -> Vue.prototype

所以只要我们在Vue.prototype设置一些东西,这样所有组件最后都可以基于“this.xxx”获取到设置的东西,例如:

Vue.prototype.$api = {};
this.$api.xxx

data

基于data构建的数据,会自动挂载到vue的实例上「vm.xxx去访问这些数据」;而且这些数据是被劫持监听的「可以看到get/set」,这样的数据我们称之为“响应式数据”:响应式数据更新,视图会自动重新渲染!!

  • 默认情况下,初始化vue的实例,会把data中的数据进行深度遍历和劫持...

  • 对于数组数据来讲
    • 数组本身会被get/set vm.arr=xxx会通知视图渲染

    • 数组中的索引不会get/set,所以 vm.arr[0]=xxx不会通知视图渲染

    • vue重写了7个数组的方法 pop / push / shift / unshift / reverse / sort / splice ,操作这7个方法,在改变数组内容的同时,也会通知视图重新渲染;操作其它方法,不会通知视图渲染...

Object.defineProperty() 双向数据绑定的原理(vue2)

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性 Object.defineProperty(obj, prop, desc)

  1. obj 需要定义属性的当前对象

  2. prop 当前需要定义的属性名

  3. desc 属性描述符 一般通过为对象的属性赋值的情况下,对象的属性可以修改也可以删除,但是通过Object.defineProperty()定义属性,通过描述符的设置可以进行更精准的控制对象属性。
    • writable:是否可以修改

    • enumerable:描述属性是否会出现在for in 或者 Object.keys()的遍历中(是否可枚举)在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。内置的属性是不可枚举的,所以不能被for…in访问到

    • configurable:描述属性是否配置,以及可否删除

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

核心:通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的

要实现mvvm的双向绑定,就必须要实现以下几点:

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

  2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

  4. mvvm入口函数,整合以上三者

props

在子组件的模板内不能直接引用父组件的数据。

父组件的数据需要通过 prop 才能下发到子组件中.也就是说props是子组件访问父组件数据的唯一接口。

父组件通过v-bind向子组件传值,子组件通过props来获取父组件传递过来的值,被引用的就是子组件

methods

在Vue中,methods被命名为方法,它也是让我们调用在对象上下文中的函数,它可以操作对象中包含的数据。

函数必须在Vue中的methods属性下添加。这个属性没有依赖缓存

<template>
.......
<button @click="myMethods1()">
<button @click="myMethods2(31)">
.......
</teplate>
<script>
......
methods:{
   myMethods1:function(){
  alert("I am methods1")
   },
   
   myMethods2:function(num){
  alert("I am methods"+num)
   },
 
}
......
</script>

computed

computed:计算属性。 当依赖某些状态(data中的数据)发生变化时,优先选择使用computed

计算属性可用于快速计算视图( View )中显示的属性。这些计算将被缓存,并且只在需要时更新。

计算属性该属性里面的方法必须要有return返回值,这个返回值就是(value值)。

  1. 计算后属性不需要在data中重复定义

  2. 计算后属性必须渲染后,绑定的方法才会生效这里指就是定义后的变量名在上面html中显示

  3. 计算后属性绑定的方法中的任意变量值更新,方法都会被调用比如说方法中一个变量A,变量A变了函数会重新调用

  4. 计算后属性为只读属性(不可写)计算后属性为只读属性

watch(监听)

用来解决beforeUpdate, updated两个钩子里修改状态导致死循环的问题,采用watch

监测可能异步改变值得时候

  1. 监听绑定的属性,该属性可以get、set

  2. 监听的属性一旦发生值更新,绑定的方法就会被调用

  3. 监听的属性是已定于的属性必须在data中定义

在vue中,使用watch来响应数据的变化。watch的用法大致有三种。

  1. 直接写一个监听处理函数,当每次监听到 Uname 值发生改变时,执行函数。同样是写在script中,与methods同级

其中使用v-model使用到的参数需要先在data中申明一下,例如上面写的例子,data与methods也是同级关系,写在script中,例如:

<template>
.......
<input type="text" v-model="Uname">
.......
</teplate>
<script>
......
watch:{
   Uname(newValue, oldValue){
  alert(newValue)
  alert(oldValue)
   }
 
}
......
</script>

  1. Immediate和handler。

当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true

watch:{
   Uname(newValue, oldValue){
  alert(newValue)
  alert(oldValue)
   },
   immediate: true
}

immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。

  1. deep。

当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

<template>
.......
<input type="text" v-model="person.name">
.......
</teplate>
<script>
.......
data(){
  return{
 person:{
      name:'',
      age:'',
      sex:''
 }
  }
}
......
watch:{
   person:{
  handler(newValue, oldValue){
    alert(newValue)
    alert(oldValue)
     },
     deep:true,
     immediate:true
 }
 
}
......
</script>
watch:{
   'person.name':{
  handler(newValue, oldValue){
    alert(newValue)
    alert(oldValue)
     },
     deep:true,
     immediate:true
 } 
}

设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,可以使用字符串的形式监听对象属性,这样只会给对象的某个特定的属性加监听器。

数组(一维、多维)的变化不需要通过深度监听,对象数组中对象的属性变化则需要deep深度监听。

filters过滤器

  • 过滤器方法中的this不再是实例,而是window

  • 视图中使用过滤器 { 传递的值(多个值用逗号分隔)过滤器方法名},过滤器方法执行返回的是啥,视图中就渲染啥...

  • 和methods一样,每一次视图渲染,过滤器方法都需要执行

  • 一般只用于某个数据的格式化处理「当然我们可以使用性能更好的计算属性去代替它」

自定义过滤器 多个过滤器可以连用 {{}}、和v-bind能用过滤器,其他指令不能用,注意:过滤器不能用于v-model 等

<!-- 在双花括号中 -->
 {{ message | capitalize }}
 
 <!-- 在 `v-bind` 中 -->
 <div v-bind:id="rawId | formatId"></div>

    {{msg|toDo("12","5")}}
    
    {{msg|toDoa|toDob}}

全局过滤器:

Vue.filter('capitalize',function (val){
           let firstcap=val.slice(0,1).toUpperCase();
           let lastcap=val.slice(1);
           return firstcap+lastcap;
})

本地过滤器:

<div id="app">
   {{msg | capitalize('aaa','123')}}
   
   {{msg | capitalize}}
  </div>
  <div id="app2">
   {{msg | capitalize}}
  </div>
  <script>
//   2.0全局过滤器---->可以作用在任何vue实例上
    Vue.filter('capitalize',function (val,str="",num=""){
                 let firstcap=val.slice(0,1).toUpperCase();
                 let lastcap=val.slice(1);
     
                 return firstcap+lastcap+str+num;
             })
         
   var vm=new Vue({
    el:"#app",
    data:{
     msg:"apple"
    },
    methods:{
     
    },
filters:{
     capitalize(val){//局部过滤器
       let firstcap=val.slice(0,1).toUpperCase();
                   let lastcap=val.slice(1);
       
                   return firstcap+lastcap;
     }
    }
   })
   
   var vm2=new Vue({
    el:"#app2",
    data:{
     msg:"orange"
    },
    methods:{
     
    }
   })
  </script>

computed计算属性对比于methods:

  1. computed响应式,而methods非响应;computed在调用时只有当其引用的响应式属性改变时才会调用函数重新计算值(存在缓存),而methods是一个函数,每次调用就是执行其函数式

  2. computed中的成员可以只定义一个函数作为只读属性,也可以自定义get/set作为读写属性

  3. computed以vue对象的属性形式存在

在实际开发中,使用computed和mothods往往都能起到作用——返回或处理一个我们要的值,但是适用场景不同;比如:当我们要去时刻监控一个视图层对应的数据层的值的变化时,使用computed就比较合理了,因为computed可缓存的,只要数据层所依赖的值不改变,computed就不会改变,而只要变了 ,computed的值就会实时更新到视图层上,即computed是响应式的。

而在这个例子中,如果使用watch也可以实现,但是那就是对视图层对应的数据层的值的依赖数据进行监听,发生变化时再调用相应的函数更改该值,那么watch和computed又有什么区别呢? 异同如下:

computed计算属性对比于watch侦听器:

  1. 相同点:都是vue对监听器的实现,都起到监听/依赖一个数据并进行处理的作用

  2. 在应用中,computed主要应用于同步数据,而watch是用来观测一个值的变化去实现一段开销较大的复杂业务逻辑或执行异步操作

  3. 能用computed时优先使用computed,避免当我们需要得到的值依赖于多个数据时多次调用watch的尴尬

  4. watch监听的值接收两个参数——新值、旧值 ,可以设置在初始化时调用

例:watch:监听一个属性值的变化并执行相应的函数

var vm = new Vue({
    el: '#demo',
  data: {
      firstName: 'Foo',
    lastName: 'Bar',
    fullName:"Foo Bar"
  },
  watch:{
    firstName:function(val){
      this.fullName = val+this.lastName
    },
    lastName:function(val){
      this.fullName = this.firstName+val
    }
  }
})

computed:依赖其他属性所计算出来的值

var vm  = new Vue({
  el:'#demo',
  data:{
        firstName: 'Foo',
    lastName:'Bar'
  },
  computed:{
    fullName(){
      return this.firstName+this.lastName;
    }
  }
})

高级用法

计算属性的setter

var vm = new Vue({
  el:'#demo',
  data:{
    firstName:'Foo',
    lastName:'Bar'
  },
  computed:{
    fullName:{
      get(){
          return this.firstName + '·' + this.lastName        
      },
      set(newVal){
        var name = NewVal.split('·')
        this.firstName = name[0];
        this.lastName = name[name.length-1]
      }
    }
  }
})
//运行vm.fullName = 'Kobe Bryant'时,set方法会被调用,vm.firstName和vm.lastName的值会被更新

侦听器的handler方法和immediate属性

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      console.log('第一次没有执行')
      this.fullName = val + '·' + this.lastName
    }
  }
})

如果想firstName在第一次被绑定的时候就执行:

watch: {
  firstName: {
    handler(val){
      console.log('第一次执行了')
      this.fullName = val + '·' + this.lastName
    },
    immediate:true//在watch中声明后立即执行handler
  }
}

侦听器的deep属性

var vm = new Vue({
  el:'#demo',
  data:{
    item:{
      a:'',
      b:''
    }
  },
  watch:{
    item:{
      handler(val){
        console.log('item.a changed')
      },
      immediate: true
    }
  }
})
//运行vm.item.a = '123',发现控制台没有打印“item.a changed”

改变item.a的值发现控制台没有打印字符串,这是因为vue无法检测到对象属性的添加或者删除。由于vue会在初始化实例时给实例的属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让vue转换它,才能是响应式的

默认情况下watch只监听对对象的引用,如当this.item = {a: '123',b:'123'}执行时handler就会执行,

深度遍历:

watch: {
  obj: {
    handler(val) {
      console.log('item.a changed')
    },
      immediate: true,
      deep: true
  }
}

deep的作用是:在对象一层层往下遍历,每一层都加上侦听器

优化 但是使用deep属性会给每一层都加上监听器,性能开销可能就会非常大了。这样我们可以用字符串的形式来优化:

  watch: {
    'item.a': {
      handler(val) {
       console.log('item.a changed')
      },
      immediate: true
      // deep: true
    }
  }

直到遇到item.a属性,才会给该属性设置监听函数,提高性能。

源码实现:

/* @flow */

import { _Set as Set, isObject } from '../util/index'
import type { SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'

const seenObjects = new Set()

/**
 * Recursively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 */
export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

如果设置this.deep == true,则触发每个深层对象的依赖,追踪其变化。traverse方法递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系。这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep.id 记录到 seenObjects,避免以后重复访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值