关于数组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操作,会严重影响前端的性能。

2. 请设计一个程序实现图书库存的管理(动态数组类) 【问题描述】 请设计一个程序实现图书库存的管理。请根据给定的main函数及程序输出,完成设计。具体要求如下。 一、请设计一个Book类: 1、包括私有成员: unsigned int m_ID;//编号 string m_Name;//书名 string m_Introductio//简介 string m_Author;//作者 string m_Date;//日期 unsigned int m_Page;//页数 2、设计所有成员变量的gettersetter函数,关于gettersetter,我们在多文件视频课程中已经进行了介绍,同学们也可以百度了解。 3、设计构造与析构函数,不要求输出信息,但各位同学可以自己输出并分析各个对象的创建与删除的情况: Book();//将m_ID初始化为0,表示这个一个未赋值对象 virtual ~Book();//无具体的工作 Book(const Book& other);//实现所有成员变量的拷贝 二、请设计一个Store类,这是一个动态数组类,用于实现图书的管理: 1、包括私有成员: Book *m_pBook;//指向利用new操作动态创建的Book数组 unsigned int m_Count;//表示库存中图书的数量 2、设计m_Count成员变量的gettersetter函数。 3、设计构造与析构函数 1) Store(); 将 m_Count置为0,m_pBook置为空指针;并输出"Store default constructor called!" 2)Store(int n); 将m_Count置为n;利用new创建大小为n的数组,令m_pBook指向数组;并输出"Store constructor with (int n) called!"; 3)virtual ~Store(); 将m_Count置为0;判断如果m_pBook不为空指针,释放m_pBook指向空间;并输出"Store destructor called!"; 4)Store(const Store& other); 实现对象数组的深拷贝,并输出"Store copy constructor called!"; 4、设计入库操作 入库操作的主要功能是在数组中添加一本新书。 函数声明为:void in(Book &b) 注意因为入库了一本新书,所以需要增加一个存储空间。提示:可以通过新申请一个空间,并将原有数据拷贝进新空间,同时将新增的书放在数组最后一个元素,再释放原有空间,从而实现数组大小的动态调整。 5、设计出库操作 出库操作的主要功能是根据指定的书名,在数组中删除这本书。 函数声明为:void out(string name) 注意因为删除了一本书,所以需要减少一个存储空间。提示:可以通过新申请一个空间,并将未被删除的部分拷贝进新空间,再释放原有空间,从而实现数组大小的动态调整。 6、根据ID查找图书 要求根据给定的ID查找图书,如果找到,则返回一个Book对象,Book对象中存储了对应书本的信息;如果找不到,则返回一个Book对象,Book对象的m_ID为0,表示未被初始化。 函数声明为:Book findbyID(int ID) 7、根据name查找图书 要求根据给定的书名查找图书,如果找到,则返回一个Book对象,Book对象中存储了对应书本的信息;如果找不到,则返回一个Book对象,Book对象的m_ID为0,表示未被初始化。 函数声明为:Book findbyName(string name) 8、设计一个函数打印所有的图书的信息 函数声明为:void printList() 【输入形式】 无 【输出形式】 见样例输出 【样例输出】 Store default constructor called! 第一本书入库 第二本书入库 第三本书入库 现有库存书籍数量:3 查找并出库图书:离散数学 离散数学 已成功出库 现有库存书籍数量:2 查找图书 ID:3 找到ID为3的书,书名:c程序设计 查找图书 name:离散数学 没有找到name为离散数学的书 输出所有库存图书的信息 There are totally 2 Books: ID=1; Name:C++ 语言程序设计(第4版); Author:郑莉; Date:201007; ID=3; Name:c程序设计; Author:谭浩强; Date:201006; 程序运行结束Store destructor called!
04-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值