关于watch侦听器和箭头函数的理解

watch侦听器

什么是watch侦听器?

它之所以叫侦听器,是因为它可以侦听一个或多个响应式数据源数据,并再数据源变化时调用所给的回调函数。 就是你传给watch侦听器一个响应式变量,然后当这个变量变化时,自动触发一个你定义的函数,可以理解为就像一个人被监控了一样,只要这个人一动,摄像头就会报警。

实例演示

先实现一个 Vue2 侦听器小例子

<template>
  <button @click="sum++">增加数值</button>
</template>

<script setup>
export default {
  data() {
    return {
      sum: 1,
    }
  },

  watch: {
    // New: 新值 | Old: 老值
    sum(New, Old) {
      console.log(`新值:${New} ——— 老值:${Old}`)
    },
  }
}
</script>

 再看Vue3中如何实现

1.差别

首先我们需要明白,在不同版本中,watch所能实现的功能有所不同,在Vue3中,watch特性进行了一些改变和优化。与computed不同,watch通常用于监听数据的变化,并可以执行一些副作用,例如 发送网络请求、更新DOM等

2.具体模版格式

watch(source, callback, options)
其中,source表示要监听的数据,可以是一个响应式的数据对象、一个计算属性或一个方法的返回值;callback表示当数据发生变化时要执行的回调函数;options表示watch的一些配置选项,例如immediate、deep、flush等。

3.实例

<template>
  <button @click="sum++">增加数值</button>
</template>

<script setup lang="ts">
import { watch, ref } from 'vue'
const sum=ref(1);
// New: 新值 | Old: 老值
watch(sum,(New, Old)=>{
	console.log(`新值:${New} ——— 老值:${Old}`)
})
</script>

对于reactive转化的响应式数组,下述也有一个简单的例子

<template>
  <button @click="sum.value++">增加数值</button>
</template>

<script setup lang="ts">
	import { watch,reactive} from 'vue'
    // 响应式变量
    const sum = reactive({
      value: 1
    })
    // watch是一个函数
    // 第一个参数: 要监听的响应式变量(数据源)
    // 第二个参数:执行的回调函数
    // 第三个参数: 配置(例如deep深度监听/immediate是否立即监听等)
    // 注意:当监听一个对象时,第一个参数数据源必须是函数且返回一个值即 "() => 对象.要监听的属性"
    watch(() => sum.value, (New, Old) => {
      console.log(`新值:${New} ——— 老值:${Old}`)
    })
</script>

 如何监听复杂数据

复杂数据是指对象套对象,对象里再套数组等等。那么这种情况怎么深度监听呢?只需要配置一个叫 deep 的属性为 true。

<template>
  <button @click="sum.car.config.isChina = !sum.car.config.isChina">改变数据</button>
</template>

<script setup lang="ts">
	import { watch, ref ,reactive} from 'vue'
    // 复杂嵌套响应式变量
    const sum = reactive({
      car: {
        name: '长安汽车',
        money: 15000,
        config: {
          color: 'black',
          isChina: true,
        }
      }
    })
    // 监听复杂响应式对象
    watch(() => sum.car, (New, Old) => {
      console.log(`新值:${New.config.isChina} ——— 老值:${Old.config.isChina}`)
    }, { deep: true })
</script>

 监听多个ref的值,采用数组形式

<template>
  <button @click="one++">改变 one 数据</button>
  <button @click="two.value++">改变 two 数据</button>
</template>

<script setup lang="ts">
	import { watch, ref ,reactive} from 'vue'
	// 变量1
	const one = ref(0)
	// 变量2
	const two = reactive({
	  value: 10
	})
	// 监听多个变量
	// 第一个参数变为数组形式,每个下标对应一个要监听的变量
	// 第二个参数的函数传参改为每项数组形式,每个数组对应新旧值的参数
	watch([one, () => two.value], ([oneNew, twoNew], [oneOld, twoOld]) => {
		console.log(`one: ${oneNew}(新) ——— ${oneOld}(旧)`);
		console.log(`two: ${twoNew}(新) ——— ${twoOld}(旧)`);
		console.warn("——————————————————————————————————————————————");
	});
</script>

侦听器是惰性的

正如前文所说,当我们写好 watch 监听后, 它不会主动触发监听函数,而是当触发某些事件,数据发生改变时才触发。刷新页面,并不会触发监听函数,这就叫做惰性,控制台没有任何输出。假如我就想初次进入页面的时候就让它执行,让它勤奋点,这也是有办法可以做到的,只需要一个配置项即可。

<template>
  <button @click="sum++">增加数值</button>
</template>

<script setup lang="ts">
	import { watch,ref} from 'vue'

    // 响应式变量
    const sum = ref(1)

    // 勤奋了
    watch(sum, () => {
      console.log('我触发啦!')
    }, { immediate: true })

</script>

箭头函数

关于上文中有几处地方我们可能感到疑惑,例如

 watch(() => sum.car, (New, Old) => {
 watch(sum, () => {
      console.log('我触发啦!')

这个()=>箭头函数到底是什么,我们又该如何去理解,下面我们来简单解释一下

 任何可以使用函数表达式的地方,都可以使用箭头函数,并且它的语法比传统的函数表达式更加简洁。可以理解为普通函数表达的另一种方式。

简单语法实例

// 函数表达式
let functionExpressionSum = function(a, b) {
    return a + b;
}
 
// 箭头函数
let arrowSum = (a, b) => {
    return a + b;
}
//更常见的写法(写到一行)
let arrowSum = (a, b) => { return a + b; }

如果只有一个参数,可以不用括号。只有没有参数或多个参数的情况下,才需要使用括号。

// 只有一个参数,可以不用括号,以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
 
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
 
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
 
// 无效的写法:
let multiply = a, b => { return a * b; };

如果函数体的返回值只有一句,可以省略大括号(省略大括号会隐式返回这行代码的值)。

// 以下两种写法都有效,而且返回相应的值
let double = (x) => { return 2 * x; };
let triple = (x) => 3 * x;
 
// 无效的写法
let multiply = (a, b) => return a * b;

如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字

// 最常见的就是调用一个函数
let fn = () => void doesNotReturn();

 由于其更简洁的语法,箭头函数的一个用处就是简化回调函数。

// 普通函数写法
var result = arr.sort(function (a, b) {
  return a - b;
});
 
// 箭头函数写法
var result = arr.sort((a, b) => a - b);

简单解释:

函数通过返回a - b来决定排序顺序。如果a - b的结果小于0,则a会被排在b之前;如果结果大于0,则b会被排在a之前;如果结果等于0,则ab的相对位置不变。

箭头函数使用,左侧是参数列表(可以省略括号当只有一个参数时,但这里是两个参数所以括号不可省略),右侧是函数体(如果函数体只有一行且不需要返回语句,可以省略大括号和return关键字)。

箭头函数没有自己的this

所有函数在执行时,会创建一个函数执行上下文,普通函数的执行上下文中会有一个变量this,而箭头函数没有,箭头函数不会创建自己的this对象,只会继承在自己作用域的上一层this。

var id = 'Global'
 
// 箭头函数定义在全局作用域
let fun1 = () => {
  console.log(this.id)
}
 
fun1() // 输出为'Global'

 对比一下理解

//箭头函数
const obj = { 
  getArrow() { 
    return () => { 
      console.log(this === obj); 
    }; 
  } 
}
 
//等含义转换后的函数
var obj = { 
   getArrow: function getArrow() { 
     var _this = this; //引用外层的this
     return function () { 
        console.log(_this === obj); 
     }; 
   } 
};

转换后的ES5版本清楚地说明了,箭头函数里没有自己的this,而是引用外层的this。

箭头函数的this不会改变

前文讲到,箭头函数没有自己的this,所以箭头函数中this的指向在它定义时就已经确定了,之后不会改变。

var name = 'GLOBAL';
var obj = {
    name: '南木元元',
    getName1: function(){
        console.log(this.name);
    },
    getName2: () => {
        console.log(this.name);
    }
};
obj.getName1();    // 输出结果为'南木元元'
obj.getName2();    // 输出结果为'GLOBAL'
 

对象obj的方法二是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向外层定义的this所指对象。所以其实定义对象的方法是不适合使用箭头函数的。

箭头函数没有prototype属性

let fn = function(name) {
    console.log(name);
}
let fn2 = name => console.log(name);
console.log(fn.prototype);
console.dir(fn2.prototype);//输出结果为undefiend

 箭头函数不能作为构造函数

上面说了,箭头函数没有自己的this,没有prototype属性,所以也就不能用作构造函数,即不可以对箭头函数使用new命令,否则会抛出错误。(这里的构造函数模式可以参考已经学习过的c++语言,this指针也可以类比理解)

let fn = (name, age) => {
    this.name = name;
    this.age = age;
}
 
// 报错
let p = new fn('南木元元', 18);

箭头函数不能使用arguments对象

arguments是一个对应于传递给函数的参数的类数组对象。

arguments是在所有普通函数中都可用的一个类数组对象,类数组不是数组,而是类似数组的对象,它除了length属性和索引之外,不能调用数组的方法。

所以通常会使用Array.prototype.slice.call(arguments)/Array.from(arguments)/[...arguments]的方式,将它转换成一个数组。 

let fn = function () {
    console.log(arguments);
    console.log(Array.prototype.slice.call(arguments));
}
fn('param1', 'param2');

 

箭头函数不可以使用arguments对象,该对象在函数体内不存在。

箭头函数不能用作Generator函数

箭头函数内部不可以使用yield命令,因此箭头函数不能用作Generator函数。

let fn = function *() {
    yield '南木元元';
}
let p = fn();
console.log(p.next());

 箭头函数不适用的场景

一、对象方法,且方法内部使用this

var name = 'GLOBAL';
var obj = {
    name: '南木元元',
    getName: () => {
        console.log(this.name);
    }
};
obj.getName();    // 'GLOBAL'

上述代码中,调用obj.getName()方法时,如果是普通函数,该方法内部的this指向调用它的那个对象;如果写成上面那样的箭头函数,使得this指向了全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致getName箭头函数定义时的作用域就是全局作用域。 

二、需要动态this

var button = document.getElementById('btn');
button.addEventListener('click', () => {
    console.log(this);    //由于使用了箭头函数,this会指向全局对象Window
});

 上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

结合财务管理微信小程序代码

watch侦听器 

在src\pages\bill\asset\manage.vue文件中,我们可以看到

watch: {
    type(val) {
      // console.log("账单类型", val);
      this.getCategoryGroups(val);
    },

 type后面跟着的是一个函数,这个函数会在type的值发生变化时被调用。函数接收一个参数val,这个参数就是type变化后的新值。然后,调用this.getCategoryGroups(val);,将新的type值作为参数传递给getCategoryGroups方法。这里getCategoryGroups是一个已经定义在Vue实例中的方法,可通过API去访问对应的账单分类,用于根据传入的账单类型(type)来执行一些逻辑,比如获取对应的分类组信息。在watch中的函数里,this关键字指向的是Vue实例本身,这使得可以访问Vue实例的数据和方法。

箭头函数 ()=>

在src\pages\bill\asset\edit.vue文件中,我们可以看到

dynamicHeight() {
      let that = this;
      uni.getSystemInfo({
        success(res) {
          let pH = res.windowHeight;
          let query = uni.createSelectorQuery().in(that);
          query.select("#edit-header").fields({ size: true });
          query.select("#edit-input").fields({ size: true });
          query.select("#save-btn").fields({ size: true });
          try{
			  query.exec((data) => {
				console.log(data);
				data.map((i) => {
				  that.scrollHeight += i.height;
				});
				that.scrollHeight = pH - that.scrollHeight;
				// console.log(that.scrollHeight);
			  });
		  } 
		  catch(err){
			  that.scrollHeight = pH - 210;
		  }
        },
      });
    },

 其中两处都用到了箭头函数分别将data和i作为参数进行传递,并且因为箭头函数中无法灵活运用this指针,所以特地创建了that指针并让他指向this,以防定义模糊,造成系统执行代码是出错,也方便读者更好的理解代码。

拓展

关于watch:Vue3 - watch 侦听器(保姆级详细使用教程)_vue3 watch-CSDN博客

箭头函数:一篇讲透:箭头函数、普通函数有什么区别_箭头函数和普通函数的区别-CSDN博客

上述博客中有更详细的案例和拓展,请大家空闲时间可以在仔细看看,助于代码理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值