原生JS、Vue2、Vue3 处理数组方法 以及 响应式原理分析

197 阅读5分钟

1.原生JS处理数组的常用方法

  1. join()指定的分割符将数组拼接在一起
  2. push()像数组尾部添加一个元素
  3. pop() 删除数组组后一个元素
  4. shift() 删除数组的第一个元素
  5. unshit() 像数组的首部添加一个元素
  6. slice() 查找其中的部分元素 (数组截取)
  7. splice()对数组增删改(数组更新)
  8. fill() 使用特定值填充数组的元素
  9. concat()两/多个数组链接在一起
  10. filter()遍历筛选数组
  11. map()遍历数组
  12. forEach()遍历数组
  13. indexOf 引索方法从开头查找
  14. lastIndexof 伊索从后向前查找
  15. find() 返回匹配的值
  16. reduce()从前向后遍历数组
  17. reduceRight()从后向前遍历
  18. every()判断数组中每一项是否都满足条件,只有所有的数据都满足条件才返回true
  19. some() 判断数组中是否存在满足条件,只需要一个满足,就会满足返回true
  20. findeIndex() 返回匹配的引索
  21. include()判断数组中是否包含指定值,返回true/false
  22. reverse()数组反转
  23. sort()对数据元素排序
  24. copyWithin() target,start,end target 对象之前的不变,然后事开始位置,结束位置,关键数组的长度不会改变只是部分数据被替换
  25. toLocalString() Date对象的一个方法,用于根据本地时间把Date对象转换为字符串,用到new Date().toLocaleString() 转化成 2022-12-22T15:00:00
  26. toString() 直接把数组转化成字符串
  27. flat() 数组扁平化,去除一/多层数组 flat(num) 去除的层数
  28. flatMap()循环去除一层数组
  29. entries() 返回该对象的key和value 使用arr.entries().next().value
  30. keys()去数组对象的key值用Array.keys().next().value
  31. values()去数组对象的vaule值用Array.values().next().value
  32. Array.from() 从字符串生成数组
  33. Array.of() 与Array() 效果一样 区别在于单个参数 Array.of(7) => [7]Array(7) => 长度为7的空数组

2.vue2中的数组实现

vue2 种被尤大重写的数组 push()pop() shift()unshift()splice() sort()reverse() 源码如下:

image.png vue2 的响应式原理是通过数据劫持object.defineProperty,网上很多说是因为object.defineProperty 监听不到数组的变化,所以vue的响应式有了$set,这里我看了一下源码每次$set$delete 其实都是调用了一次splice()方法进行了数组更新

下面我们具体介绍一下object.defineProperty的基本使用和vue2是怎样实现响应式的。

object.defineProperty 基本使用方法
Object.defineProperty(obj, prop, descriptor)

参数是3个,obj 是对象,prop是属性 ,descriptor是目标属性持有的特性

具体介绍一下descriptor如下:

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
    Object.defineProperty(obj, key, {
      value: val, //设置属性的值
      enumerable: !!enumerable, //目标属性是否可以被枚举
      writable: true, // 值是否可以重写
      configurable: true, // 目标属性是否可以被删除或是否可以再次修改特性
      get:function (){} | undefined, // get/set 是存取器描述
      set:function (value){} | undefined // 当使用了getset方法,不允许使用writable和value这两个属性 get/set不是成对出现,可以单个调用
    })
  }

简单的例子如下:

//   是可以进行赋值的,可以正常监听到Object:
  let userInfo = {
      age: "11"
  };
  Object.defineProperty(obj, 'name', {
    configurable: true, // 是否可删除
    enumerable: true, // 是否可以枚举
    get(){
        return  val
    },
    set(val) {
        name = val
    }
  })
  console.log(obj.name) // hello world

如果对象是复杂数据,我们看一下是否还可以更新数据

//   复杂数据的例子
let obj = {
    name: 'hello world',
    vue: {
        name: 'hello vue'
    },
    options: ['watch','props']
}
// 这种复杂的数据显然一个简单函数是实现不了的,我们需要循环+递归才能监听到每个数据的属性
// 封装递归函数
function defineProperty(obj,key,val) {
    Object.defineProperty(obj, key, {
        enumerable:true,
        configurable: true,
        get() {
            console.log('读',val)
            return val
        },
        set(newval) {
            console.log('写',newval)
            // 这里做递归,直到val 和newval 相等时候,停止调用
            if (val === newval) return 
            observe(newval)
            val = newval
        }
    })
}
//调用的循环函数
function observe(obj) {
    if(typeof obj !== 'object' || obj == null) return 
    // 循环遍历所有的属性
    for (const key in obj) {
        defineProperty(obj,key,obj[key])
    }
}
observe(userInfo)
obj.age = '12'
options = [1,2,3]
observe(options)
console.log('---------push---------')
options.push(4)
console.log('---------pop---------')
options.pop()
console.log('---------shift---------')
options.unshift('0')
console.log('--------length----------')
options.length = 10

console.log(JSON.stringify(obj))

image.png

image.png 执行结果可以看出来object.defineProperty``push() pop() length 都没有监听到set 和 get 方法,unshift 方法只把

对数组进行了特殊处判断一下,如下:

const orginalProto = Array.prototype;
const arrayProto = Object.create(orginalProto); // 先克隆一份Array的原型出来
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(method => {
  arrayProto[method] = function () {
    // 执行原始操作
    orginalProto[method].apply(this, arguments)
    console.log('监听赋值成功', method)
  }
})

响应式结合使用的时候再进行一下observe一下,这里我理解就是把key值具象化,实现了劫持属性1. map()遍历数组,代码如下


function observe(obj) {
    if (typeof obj !== 'object' || obj == null) return 
    if (Array.isArray(obj)) {
        obj.__proto__ = arrayProto
        for (let i = 0; i < obj.length; i++) {
            observer(obj[i])
        }
    } else {
         // 循环遍历所有的属性
        for (const key in obj) {
            defineProperty(obj,key,obj[key])
        }
    }
}

3.vue3中的数组实现

vue3 种被尤大重写的数组源码如下: image.png vue3我们都知道的是响应式原理用Proxy,数据劫持的方式

function  reactive(obj) {
  if(typeof  Obj !== 'object' && obj!=null) {
    return obj
  }
  const observe = new Proxy(state, {
    get(target, key, receiver) {
      const res = Reflect.get(target, propertyKey,receiver)
      console.log('获取key:',key,'res',res)
      return res
    },
    set(target,key,val,receiver) {
      const res = Reflect.set(target,key,val,receiver)
      console.log('设置key:',key,'res',res)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log('删除key:',key,'res',res)
      return res
    }
  })
  return observe
}

Reflect 在这里这个简单介绍一下,这个和proxy是一样的都是ES6 的API