关于数组getter和setter的思考

本文探讨了Vue中针对数组的数据绑定实现方式,并对比了对数组使用getter和setter的方法。分析了Vue选择特定实现的原因,包括避免全局污染及提高前端性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  接触过Vue的人基本都知道,Vue的数据绑定是通过ES5的getter和setter实现的,查看Vue源码,目录结构如下
这里写图片描述
observer目录下面的模块实现Vue的数据绑定功能,从文件夹和文件的命名来看可以知道采用的是观察者设计模式,看来有必要对常见的几种设计模式补补课了。

  这里我不想详细分析整个数据绑定的实现过程,网上分析的很好的文章有很多,我只想说说我在看完这部分代码后的想法。Vue数据绑定其实就是通过对数据进行递归操作最后就是对两种类型的数据进行getter和setter,一种就是除Object外的基本数据类型,另外一种就是数组。对于第一类是通过ES5的 Object.defineProperty添加get和set属性。第二类就是通过自定义方法dependArray 专门对数组进行操作。打开看dependArray 的源码:

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

  代码看起来超级简洁,一点多余的都看不出来,看来我平时在写代码的时候应该多思考一下,尽量减少冗余的代码。其实作者就是对数组的push,pop,shift, unshift,splice, sort,reverse七种方法进行了重写,这里需要注意的是作者并没有重写Array累的原型方法,而是通过改变Array实例的__proto__属性,这样避免了对Array原始属性的破坏而造成的全局污染。但是如果我们操作数组的时候不用这几种方法,例如:

this.person.favorite[0] = "apple";

这样视图是不会发生出发视图改变的。居然这样可不可给素组对象里面的属性加上set和get属性呢?首先看看数组的结构:

这里写图片描述
看着是不是有点眼熟,他里面也是以键值对的形式存在的。那么以为着我们是不是也可以像普通对象那样去处理呢?身为程序猿,要有no zuo no die 的精神,试试为数组加上get和set属性:

function Observer(value) {
    this.value = value;
    this.walk(value);
  }
  Observer.prototype.walk = function(obj) {
    var keys = Object.keys(obj);
    for(var i = 0; i < keys.length; i++) {
      // 给所有属性添加 getter、setter
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  };
  
  var dep = [];
  
  function defineReactive(obj, key, val) {
    // 有自定义的 property,则用自定义的 property
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if(property && property.configurable === false) {
      return;
    }
    
    var getter = property && property.get;
    var setter = property && property.set;
  
    // 递归的方式实现给属性的属性添加 getter、setter
    var childOb = observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function() {
        var value = getter ? getter.call(obj) : val;
        dep.push(value);
        return value;
      },
      set: function(newVal) {
        var value = getter ? getter.call(obj) : val;
        // set 值与原值相同,则不更新
        if(newVal === value) {
          return;
        }
        if(setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
       
        // 给新赋值的属性值的属性添加 getter、setter
        childOb = observe(newVal);
        console.log("数组改变了位置["+key+"]的值改变了");
      }
    });
  }
  
  function observe(value) {
    if(!value || typeof value !== 'object') {
      return;
    }
    return new Observer(value);
  }

运行结果:
这里写图片描述

看来可以啊!但是作者为什么不这样干呢?

下面我们看看如果我们改变一下person.fav的值:
这里写图片描述

  这样之后我们会发现当我们再次修改person.fav[0]位置上的值的时候,发现我们之前设置的setter属性不起作用了。这个时候因为对象的因为在赋值为[]的时候后数组的已经被删除掉了,所以这个时候就不生效了,这是需要对新生成的数组重新进行definedProperty操作。但是当你重新定义后,你还是会遇见各种set无法生效的问题,比如我通过person.fav[100]=“xx”,这样数组的长度就会达到100,或者直接通过person.fav.length=xx,这样去增加或者减小数组的长度…当然我们可以打各种补丁去重新定义,但是这样一样每次数组变化我们都需要进行重新定义。这样会严重影响前端的性能。

总结:

   YY始终还是只能停留在YY上,这样是没有必要的,因js的数组操作形式太多了,而且经常会对数组中的元素进行增加删除操作,这样会导致之前定义的key值无效,相反普通的json对象就不会经常增删属性,只是修改属性的值。如果要对数组的key进行get和set操作,会严重影响前端的性能。

<think>好的,我现在要详细分析用户的问题:Java中数组对象String对象的序列化与反序列化操作。首先,我得回顾一下Java序列化的基本概念,确保自己理解正确。序列化是将对象转换为字节流的过程,以便存储或传输,反序列化则是将字节流恢复为对象的过程。 接下来,需要明确数组对象String对象在序列化中的特殊性。String是Java中的一个不可变类,实现了Serializable接口,所以它本身就可以被序列化。数组的话,如果是基本类型数组,比如int[],或者对象数组,比如String[],它们的序列化处理可能有所不同。需要确认数组是否自动支持序列化,或者是否需要额外处理。 然后,考虑具体的实现步骤。对于数组对象,可能需要确保数组中的每个元素都是可序列化的。例如,如果数组里包含自定义对象,这些对象必须实现Serializable接口,否则会抛出NotSerializableException。而String本身已经实现了Serializable,所以String数组应该可以直接序列化。 接下来是序列化反序列化的代码示例。需要用ObjectOutputStreamObjectInputStream来进行操作。对于数组,可能需要将其转换为字节数组输出流,再写入文件或网络。而String的处理类似,但要注意字符编码的问题吗?不过序列化可能已经处理了编码,所以不需要额外步骤。 还要注意异常处理,比如IOExceptionClassNotFoundException。在反序列化时,需要正确转换类型,可能需要强制类型转换,比如将反序列化后的对象转为String[]或者String。 另外,可能需要提到serialVersionUID的问题,但对于String数组,可能不需要显式声明,因为它们是Java标准库中的类,其serialVersionUID由JVM管理。不过如果用户自定义了数组中的元素类型,则需要注意这一点。 可能存在的误区是,用户可能认为所有数组都自动可序列化,但如果数组元素不可序列化,就会失败。需要提醒这一点。比如,如果有一个Object[]数组,里面包含非序列化的对象,序列化时会出错。 总结步骤:首先确保对象可序列化,然后使用ObjectOutputStream写入,ObjectInputStream读取,处理异常,类型转换。对于数组String,流程基本一致,但要注意元素的可序列化性。 最后,验证示例代码的正确性,确保语法正确,比如使用try-with-resources处理流,正确关闭资源,避免内存泄漏。同时,检查是否有中文注释或说明,符合用户的要求。 现在需要将以上思考整理成结构化的回答,分点说明,确保清晰易懂,符合用户提到的帮助用户逐步解决问题,并保证真实可靠。</think>在Java中,序列化是将对象转换为字节流的过程,反序列化则是将字节流恢复为对象。以下是针对数组对象`String`对象的操作详解: --- ### 一、序列化基础 - **前提条件**:对象必须实现`java.io.Serializable`接口(标记接口,无方法)。 - **核心类**: - `ObjectOutputStream`:将对象写入字节流(序列化)。 - `ObjectInputStream`:从字节流恢复对象(反序列化)。 --- ### 二、String对象的序列化与反序列化 `String`类已实现`Serializable`,可直接操作。 #### 示例代码 ```java import java.io.*; public class StringSerialization { public static void main(String[] args) { String original = "Hello, 世界!"; // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("string.ser"))) { oos.writeObject(original); // 写入String对象 } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("string.ser"))) { String deserialized = (String) ois.readObject(); // 强制类型转换 System.out.println(deserialized); // 输出: Hello, 世界! } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } ``` --- ### 三、数组对象的序列化与反序列化 数组本身隐式实现了`Serializable`,但需确保**数组元素可序列化**。 #### 1. 基本类型数组(如`int[]`) 可直接序列化: ```java int[] numbers = {1, 2, 3}; // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("array.ser"))) { oos.writeObject(numbers); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("array.ser"))) { int[] deserializedNumbers = (int[]) ois.readObject(); System.out.println(Arrays.toString(deserializedNumbers)); // 输出: [1, 2, 3] } ``` #### 2. 对象数组(如`String[]`) 若元素可序列化,则数组可序列化: ```java String[] names = {"Alice", "Bob"}; // 序列化与反序列化流程同上 // 反序列化后强制转换: String[] deserializedNames = (String[]) ois.readObject(); ``` #### 3. 自定义对象数组数组元素为自定义类,该类必须实现`Serializable`: ```java class Person implements Serializable { private String name; // 构造方法getter/setter省略 } Person[] people = {new Person("Alice"), new Person("Bob")}; // 序列化与反序列化流程同上 ``` --- ### 四、注意事项 1. **不可序列化的元素**:若数组元素未实现`Serializable`,会抛出`NotSerializableException`。 2. **版本一致性**:序列化与反序列化的类需兼容(`serialVersionUID`一致)。 3. **敏感数据**:避免序列化敏感字段,可用`transient`关键字标记。 --- ### 五、总结 - **String对象**:直接序列化,无额外要求。 - **数组对象**:需确保元素类型可序列化,其他操作与普通对象一致。 - **通用步骤**: 1. 使用`ObjectOutputStream`写入对象。 2. 使用`ObjectInputStream`读取并强制类型转换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值