JavaScript 动态属性赋值方式与原理剖析

      工作中遇到一个场景,是给不同的按钮挂载不同的跳转地址,在vue页面实现的。实际实现的就是给一个前端定义好的对象添加后台返回的数据,这里用一个实例代替业务代码来展现 一下这个场景:

首先页面按钮代码与触发方法:

<template>
  <div>
    <button v-for="(path, id) in paths" :key="id" @click="handleButtonClick(id)">
      {{ id }}
    </button>
  </div>
</template>

原业务代码这里的按钮是一个个手写出来,这里用v-for循环一个意思。主要为了实现,多个按钮绑定对应不同的地址,然后实现点击跳转,下面就是这个点击操作触发的方法:

<script>
export default {
  data() {
    return {
      paths: {}, // 初始化为空对象
      userId: 'your-user-id', // 用户ID
      rowId: 'your-row-id' // 行ID
    };
  },

  methods: {
    async fetchButtonPaths() {
      try {
        const res = await thingsToDo(this.userId, this.rowId);
        if (res.data.msg === 'success') {
          const buttonPaths = res.data.data.buttonPaths;
          buttonPaths.forEach(path => {
            this.paths[path.id] = path.path; // 动态添加属性
          });
        } else {
          console.error('获取按钮路径失败:', res.data.msg);
        }
      } catch (error) {
        console.error('请求错误:', error);
      }
    },

    handleButtonClick(buttonId) {
      const path = this.paths[buttonId];
      if (path) {
        this.$router.push(path);
      } else {
        console.error('未找到按钮路径:', buttonId);
      }
    }
  },

  created() {
    this.fetchButtonPaths(); // 在组件创建时获取按钮路径
  }
};
</script>

上述写法首先定义了需要接收返回数据的集对象,paths:{}是核心对象,那两个id不重要。

下面两个方法fetchButtonPaths代表了异步请求后台的操作,其中

const buttonPaths = res.data.data.buttonPaths;
          buttonPaths.forEach(path => {
            this.paths[path.id] = path.path; // 动态添加属性
          });

这里是核心赋值代码,这里实现了 JavaScript中动态属性的赋值。其中后台返回的值res的内容结构格式如下:

{
  "msg": "success",
  "data": {
    "buttonPaths": [
      { "id": "button1", "path": "/exam/applys/confirmnotpayexaminee1" },
      { "id": "button2", "path": "/exam/applys/confirmnotpayexaminee2" },
      { "id": "button3", "path": "/exam/applys/confirmnotpayexaminee3" }
    ]
  }
}

上述操作后,在data属性中定义的paths:{}对象就有了内容, 拆开来看这里的操作实现了下面的转变:

const paths = {}; // 初始化为空对象
const path = { id: "button1", path: "/exam/applys/confirmnotpayexaminee1" };

// 动态添加属性
paths[path.id] = path.path;

console.log(paths); // 输出: { button1: "/exam/applys/confirmnotpayexaminee1" }

然后使用点击方法便可根据id的值获取到对应的跳转地址实现跳转,功能到这就完成了, JavaScript 动态属性赋值一般也是这么操作的,但是我有了一个疑问:

最初在data中创建的paths:{}集合是空的,里面没有事先写好的属性名,但是在动态赋值的时候,
paths[path.id] = path.path;却可以正确的添加id:path格式的键值对,这是怎么实现的,按照传统后端思维,paths集合中获取没有的key进行操作,一定会报错的。

然后我便查阅了一些资料,在这同时做个小小的原理总结:

首先说结论,

  • JavaScript动态属性赋值:在 JavaScript 中,对象的属性可以动态添加。即使对象初始为空,你也可以随时为其添加新的属性。
  • this.paths[path.id] = path.path:这行代码实际上是将 path.path 的值赋给 this.paths 对象中名为 path.id 的属性。如果该属性不存在,JavaScript 会自动创建它。

依据上述案例解释下流程详细步骤:

  1. 初始化对象

    • const paths = {}; 初始化一个空对象 paths
  2. 路径对象

    • const path = { id: "button1", path: "/exam/applys/confirmnotpayexaminee1" }; 定义一个路径对象 path,包含 id 和 path 属性。
  3. 动态添加属性

    paths[path.id] = path.path; 这行代码做了以下几件事:
    • 将 path.path 的值(即 "/exam/applys/confirmnotpayexaminee1")赋值给 paths 对象的 "button1" 属性。
    • 如果 paths 对象中不存在 "button1" 属性,JavaScript 会自动创建这个属性。
    • paths 对象中查找名为 "button1" 的属性。
    • path.id 是字符串 "button1"

上述就是大面上的解释,但是我觉得还不够,本着希望从源码中得到解答的精神,继续查阅资料,终于触碰到了我的知识壁垒,下面我就做个简单的原理概述,结束这次的菜狗验证:

JavaScript 引擎在处理对象属性赋值时,会执行以下步骤:

  1. 检查对象是否存在

    • 确认对象是否存在。如果对象不存在,会抛出错误。
  2. 查找属性

    • 检查对象中是否已经存在指定的属性。
  3. 属性不存在时创建属性

    • 如果属性不存在,引擎会创建一个新的属性,并将其值设置为指定的值。

伪代码实现

为了更好地理解这一过程,我们可以用伪代码来模拟 JavaScript 引擎的行为:

function assignProperty(obj, key, value) {
  // 检查对象是否存在
  if (typeof obj !== 'object' || obj === null) {
    throw new TypeError('Cannot set property of non-object');
  }

  // 检查对象中是否已经存在指定的属性
  if (!(key in obj)) {
    // 属性不存在,创建新的属性
    obj[key] = value;
  } else {
    // 属性已存在,更新属性值
    obj[key] = value;
  }
}

// 测试
const paths = {};
const path = { id: "button1", path: "/examn/applys/confirmnotpayexaminee1" };

assignProperty(paths, path.id, path.path);

console.log(paths); // 输出: { button1: "/exam/applys/confirmnotpayexaminee1" }

实际的 JavaScript 引擎实现

实际的 JavaScript 引擎(如谷歌的 V8)的实现要复杂得多,涉及到许多优化技术,如内联缓存、隐藏类等。以下是一个简化的 V8 引擎的实现步骤:

  1. 对象表示

    • 在 V8 中,对象通常由一个哈希表或数组来表示,每个属性对应一个键值对。
  2. 属性查找

    • 使用哈希表查找属性。如果属性存在,直接返回属性值。
    • 如果属性不存在,继续下一步。
  3. 属性创建

    • 创建一个新的属性,并将其值设置为指定的值。
    • 更新对象的内部结构(如哈希表或数组)。

V8 引擎的内部结构

V8 引擎使用了一些高级的数据结构和优化技术来提高性能。以下是简化的内部结构:

  1. 哈希表

    • 使用哈希表来存储对象的属性,以便快速查找和插入。
  2. 隐藏类

    • 使用隐藏类来优化属性访问。隐藏类是一种内部表示,用于描述对象的结构。
  3. 内联缓存

    • 使用内联缓存来加速属性访问和修改操作。

示例代码

为了更直观地理解,我们可以用一个简单的 JavaScript 对象来模拟这一过程:

class SimpleObject {
  constructor() {
    this.properties = {};
  }

  hasProperty(key) {
    return key in this.properties;
  }

  getProperty(key) {
    return this.properties[key];
  }

  setProperty(key, value) {
    this.properties[key] = value;
  }
}

// 测试
const paths = new SimpleObject();
const path = { id: "button1", path: "/exam/applys/confirmnotpayexaminee1" };

if (!paths.hasProperty(path.id)) {
  paths.setProperty(path.id, path.path);
} else {
  paths.setProperty(path.id, path.path);
}

console.log(paths.getProperty(path.id)); // 输出: /exam/applys/confirmnotpayexaminee1

总结

  • 动态属性赋值:在 JavaScript 中,对象的属性可以动态添加。如果属性不存在,JavaScript 引擎会自动创建这个属性。
  • 内部实现:JavaScript 引擎(如 V8)使用哈希表、隐藏类和内联缓存等技术来优化属性的查找和创建过程。

 前端菜狗,欢迎指导。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值