前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从Vue源代码中来聊聊[Symbol.toPrimitive]方法

从Vue源代码中来聊聊[Symbol.toPrimitive]方法

作者头像
19组清风
发布2021-11-15 14:48:48
6880
发布2021-11-15 14:48:48
举报
文章被收录于专栏:Web Front End

背景叙述

背景

在阅读Vue3的触发更新trigger函数中对于数组新增key索引中有这样一段hack代码。

代码语言:javascript
复制
switch (type) {
            case "add" /* ADD */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                break;
            case "set" /* SET */:
                if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY));
                }
                break;
        }
复制代码

简单来聊聊v3中的这段代码,实质上是在做触发更新的一些hack处理。来看看这里:

代码语言:javascript
复制
                case "add" /* ADD */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                // 主要留意这段代码
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
复制代码

你可以这样理解这段代码,当我们在页面中定义了一个响应式的数组时

代码语言:javascript
复制
import { reactive } from 'vue' 
const a = reactive({
    arr:[1,2,3]
})

// 假使模版中已经使用了a.arr 进行过来依赖收集
// 当我改变它的值,为她新增一个索引
a.arr[5] = 'wang.haoyu'
复制代码

**我们知道在V3中Vue已经支持对于修改数组下标的响应式支持了。**此时就会进入上边的逻辑中。

当满足类型是add时,并且新增的是数组的一个索引。我们明白为数组新增一个索引一定是会该改变length属性,所以这里调用了add(depsMap.get('length'));进行添加更新effect函数。这没有任何问题。

问题

但是你有没有想过,当我们在模板中这样使用呢?

代码语言:javascript
复制
<template>
  <div>
      {{ obj.arr }}
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const obj = reactive({
      arr:[1,2,3]
    })
    setTimeout(() => {
      obj.arr[10] = 'wang.haoyu'
    }, 2000);
    return {
      obj
    }
  }
}
</script>
复制代码

我们在模板中直接调用了obj.arr,它是一个数组。当我们新增数组arr的索引的时候,首先按照vue3中的依赖收集。他会对与整个数组进行依赖收集。

当我们为arr新增一个索引的时候,按照上边的逻辑会触发到这里

代码语言:javascript
复制
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
复制代码

他会去depsMap中去找(depsMap就是关于数组所有依赖收集对象的一个map对象)length属性,从而添加关于length属性收集的effect去触发更新。

可是我明明是为数组新增了一个索引,而且我在模板中使用的是obj.arr整个数组对象。为什么它的length属性就会被依赖收集了呢?

这个其实就源自于Symbol.toPrimitive

Symbol.toPrimitive方法

方法介绍

javascript引擎中,当执行特定操作时,经常会尝试对对象转化到相应的原始值,例如,比较一个字符串和一个对象,如果使用双等号==运算符,对象会在比较操作执行前被转换为一个原始值。到底使用哪一个原始值以前是由内部操作决定的。在ES6中,通过Symbol.toPrimitive方法可以更改那个暴露触发的值。

Symbol.toPrimitive方法呗定义在每一个标准类型的原型上,并且规定了当对象被转化为原始值时当执行的操作。每当执行原始值转换时,总会调用Symbol.toPromitive方法传入一个值作为参数,这个值在规范中被称为类型提示hint,类型提示参数的值有三种选择:number,string或者default,传递这些参数的时,Symbol.toPrimitve返回的分别是:数字,字符串或无类型偏好的值。

对与绝大多数标准对象,数字模式有以下特性,根据优先级顺序排序如下:

  1. 调用valueOf方法,如果结果为原始值则返回。
  2. 否则,调用toString()防范,如果为原始值,则返回。
  3. 如果再无可选值,则抛出错误。

同样,对与大多数标准对象,字符串模式具有以下优先级:

  1. 调用toString()方法,如果结果为原始值则返回。
  2. 否则,调用valueOf方法,如果结果为原始值,则返回。
  3. 如果再无可选值,则抛出错误。

如果自定义Symbol.toPrimitive方法则可以覆盖这些默认的强制转化特性。

关联问题

这个时候大家应该大概已经明白了,当我们在模板中调用obj.arr访问整个数组的时候,vue中首先会调用这个数组的Symbol.toPrimitive方法将它转化为字符串,也就是调用数组的toString()方法,再将结果进行依赖收集。

当调用数组的toString()方法的时候,究竟发生了什么。我们在看看

代码语言:javascript
复制
const arr = [1, 2];

const proxy = new Proxy(arr, {
 get: (target, key, receiver) => {
   console.log(key);
   return Reflect.get(target, key, receiver);
 },
});

proxy.toString()

// 打印结果
toString
join
length
0
1

复制代码

可以看到数组的toString方法内部其实首先调用join方法,然后访问length以及数组中每一个元素,最终转为为1,2

这个时候其实就很清晰了。

  1. Vue中模板使用obj.arr访问数组
  2. 调用obj.arr.prototype[Symbol.toPrimitive]尝试将obj.arr转为字符串
  3. 内部调用toString方法
  4. arr.toString()方法会访问数组中的每一个元素和它的length进行join

回到开始

这个时候我们可以看到,当在模板中访问整个数组进行依赖收集的时候,实质上vue3中将整个数组的转化成为了字符串类型调用了内部Symbol.toPrimitive方法。 从而依赖手机中对与这个数组的每一项以及对应length进行了依赖收集,此时当数组新增一个索引。v3中手动调用了数组中的length去触发对应更新。

新增索引一定会修改数组长度,当模版中访问整个数组将数组转为String时候,对与长度进行了依赖收集。所以触发更新时,新增索引就会触发数组的更新。

遗留问题

留下一个问题之后去解决,在vue中如果在模板中使用一个对象比如{{ obj }},(const obj = { name:wang,haoyu })。

平常如果我们直接app.innerHTML = obj,页面中div中显示的是[Object object],而在vue模板中显示的是name:wang.haoyu。不知道大家有没有注意过这个小细节。

那么内部是不是和Vue内部重写了Symbol.toPrimitive有关呢?大家可以先猜一猜

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年08月09日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景叙述
    • 背景
      • 问题
      • Symbol.toPrimitive方法
        • 方法介绍
          • 关联问题
            • 回到开始
              • 遗留问题
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档