首页 > 其他分享 >【JS】Object.defineProperty与Proxy的对比并通过Vue2、3的实现证明Proxy性能优于Object.defineProperty

【JS】Object.defineProperty与Proxy的对比并通过Vue2、3的实现证明Proxy性能优于Object.defineProperty

时间:2024-09-24 09:52:34浏览次数:9  
标签:obj target Object Proxy defineProperty 属性

一、Object.defineProperty

这里只是简单描述,具体请看另一篇文章:Object.defineProperty。

Object.defineProperty 是 JavaScript 中用于定义或修改对象属性的功能强大的方法。它可以精确地控制属性的行为,如是否可枚举、可配置、可写等。

基本用法

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty 方法接受三个参数:

  1. 『目标对象』:要在其上定义属性的对象。
  2. 『属性名称』:要定义或修改的属性名称。
  3. 『描述符对象』:属性描述符对象,用于描述该属性的行为。
    属性描述符对象可以包含以下键:
    value:属性的值。默认为 undefined。
    writable:属性是否可写。默认为 false。
    configurable:属性是否可配置。默认为 false。
    enumerable:属性是否可枚举。默认为 false。
    get:属性的 getter 函数。如果没有 getter,值为 undefined。
    set:属性的 setter 函数。如果没有 setter,值为 undefined。

用例

1. 定义一个只读属性

let obj = {};

Object.defineProperty(obj, 'message', {
    value: 'Hello, world!',
    writable: false
});

console.log(obj.message); // 输出 "Hello, world!"
obj.message = 'Hi!'; // 无效,因为属性是只读的
console.log(obj.message); // 仍然输出 "Hello, world!"

2. 定义一个不可枚举属性

let obj = {};

Object.defineProperty(obj, 'message', {
    value: 'Hello, world!',
    enumerable: false
});

console.log(obj.message); // 输出 "Hello, world!"
console.log(Object.keys(obj)); // 输出 [], 因为属性不可枚举

3. getter与setter

let obj = {};
let value = 'Hello, world!';

Object.defineProperty(obj, 'message', {
    get() {
        return value;
    },
    set(newValue) {
        value = newValue;
    },
    enumerable: true,
    configurable: true
});

console.log(obj.message); // 输出 "Hello, world!"
obj.message = 'Hi!';
console.log(obj.message); // 输出 "Hi!"

4. 定义不可配置属性

let obj = {};

Object.defineProperty(obj, 'message', {
    value: 'Hello, world!',
    configurable: false
});

console.log(obj.message); // 输出 "Hello, world!"

Object.defineProperty(obj, 'message', {
    value: 'Hi!'
}); // 抛出错误,因为属性不可配置

二、Proxy

Proxy 是 ES6 引入的一项功能,用于定义自定义行为来拦截并改变对某个对象的基本操作(例如属性读取、赋值、枚举、函数调用等)。

基本语法

let proxy = new Proxy(target, handler);

Proxy 构造函数接受两个参数:

  1. target:要包装的目标对象(可以是任何类型的对象,包括数组、函数等)。
  2. handler:一个对象,其中包含一组捕捉器(traps)。这些捕捉器定义了在执行各种操作时,代理对象如何处理这些操作。
    捕捉器(Traps)包含:
    get(target, prop, receiver):拦截对象属性的读取。
    set(target, prop, value, receiver):拦截对象属性的设置。
    has(target, prop):拦截 in 操作符。
    deleteProperty(target, prop):拦截 delete 操作符。
    ownKeys(target):拦截 Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols 方法。
    apply(target, thisArg, argumentsList):拦截函数调用。
    construct(target, args):拦截 new 操作符。

用例

1. 基本使用

let target = {
    message: "Hello, world!"
};

let handler = {
    get(target, prop) {
        return prop in target ? target[prop] : `Property ${prop} does not exist.`;
    },
    set(target, prop, value) {
        console.log(`Setting ${prop} to ${value}`);
        target[prop] = value;
        return true;
    }
};

let proxy = new Proxy(target, handler);

console.log(proxy.message); // 输出 "Hello, world!"
console.log(proxy.nonExistent); // 输出 "Property nonExistent does not exist."
proxy.message = "Hi!"; // 输出 "Setting message to Hi!"

2. 拦截函数调用

let target = function() {
    return "I am the target";
};

let handler = {
    apply(target, thisArg, argumentsList) {
        return "I am the proxy";
    }
};

let proxy = new Proxy(target, handler);

console.log(proxy()); // 输出 "I am the proxy"

3. 拦截属性删除

let target = {
    message: "Hello, world!"
};

let handler = {
    deleteProperty(target, prop) {
        if (prop in target) {
            delete target[prop];
            console.log(`Property ${prop} deleted`);
            return true;
        } else {
            console.log(`Property ${prop} does not exist`);
            return false;
        }
    }
};

let proxy = new Proxy(target, handler);

delete proxy.message; // 输出 "Property message deleted"
delete proxy.nonExistent; // 输出 "Property nonExistent does not exist"

三、与Vue2、Vue3的关系

vue2响应式数据原理是Object.defineProperty

Vue3响应式数据原理是Proxy

四、为什么Proxy性能优于Object.defineProperty

Object.defineProperty只能监听Object的某个属性,所以只能通过遍历才可以深度监听整个对象。

只监听obj对象的a属性:

const obj = {
  a: 1,
  b: 2,
  c: {
    aa: 11,
    bb: 11,
    cc: {
      aaa: 111
    }
  }
}

let val = obj.a
Object.defineProperty(obj, 'a', {
  get: () => {
    console.log("读值:", val);
    return val
  },
  set: (newVal) => {
    if (newVal !== val) {
      console.log("改值:", newVal);
      val = newVal
    }
  }
})
obj.a // 读
obj.a = 10 // 改

但若想深度监听整个obj,则需要遍历对象的全部属性,这里封装函数observe:

const obj = {
  a: 1,
  b: 2,
  c: {
    aa: 11,
    bb: 11,
    cc: {
      aaa: 111
    }
  }
}

function _isObject(val) {
  return typeof val === 'object' && val !== null
}
function observe(obj) {
  for (const k in obj) {
    let val = obj[k]
    if (_isObject(val)) {
      observe(val)
    }
    Object.defineProperty(obj, k, {
      get: () => {
        console.log(`读${k}的值:`, val);
        return val
      },
      set: (newVal) => {
        if (newVal !== val) {
          console.log("改值:", newVal);
          val = newVal
        }
      }
    })
  }
}

observe(obj) // 启动监听
obj.c.aa
obj.a = 10
obj.c.aa = "aaa"

vue2通过该种方式实现响应式数据,由于getter和setter中监听不到属性的新增与删除,所以导致vue2的响应式数据也无法直接监听到属性的新增与删除,需要通过$set$delete

但Proxy不会改变原始对象,而是生成一个代理对象,所以性能由于Object.defineProperty

const obj = {
  a: 1,
  b: 2,
  c: {
    aa: 11,
    bb: 11,
    cc: {
      aaa: 111
    }
  }
}

function createReactiveObject(target) {
  return new Proxy(target, {
    get: (target, k) => {
      let v = target[k]
      console.log(`读${k}的值:`, v);
      if (typeof v === 'object' && v !== null) {
        return createReactiveObject(v)
      }
      return v
    },
    set: (target, k, val) => {
      let v = target[k]
      if (v !== val) {
        console.log("改值:", val);
        target[k] = val
      }
      return true
    }
  })
}

const proxy = createReactiveObject(obj)

// 测试读取和修改属性
proxy.c.aa
proxy.a = 10
proxy.c.aa = "aaa" 

vue2使用Object.defineProperty小结

  • Object.defineProperty只针对对象的某个属性,所以vue2中想实现深度监听只能遍历。
  • Object.defineProperty会改动原始对象。
  • 由于新增与删除属性不会进入getter和setter,所以vue2中响应式数据新增与删除属性时不会被监听,需要使用$set$delete

vue3使用Proxy小结

  • Proxy针对整个对象,所以无需遍历。
  • Proxy生成一个代理对象,所以不会改动原始对象。

综上,vue3使用Proxy性能优于vue2的Object.defineProperty。

五、结合文档理解Proxy与Object.defineProperty

1. Proxy

拦截和重新定义对象的基本操作
在这里插入图片描述

什么是对象的基本操作
访问/修改属性值、in、for in本质上会被转为内部f函数调用,如GET、SET、HAS、ownKeys、DELETE。通过官方文档查看详细内容。

在这里插入图片描述

函数对象额外多两个内部方法,函数调用时触发Call,new生成实例调用Construct方法,至此也能理解为什么函数也是对象。
在这里插入图片描述
Proxy 会将这些对象基本操作暴露出来,开发者可以更改基本操作行为。

在这里插入图片描述

2. Object.defineProperty

观察官方文档中对象基本操作的[[DefineOwnProperty]],Object.defineProperty就是它,所以 Object.defineProperty 只是对象中的某个基本操作。

在这里插入图片描述

六、二者对比

代理的粒度不同

defineProperty 只能代理属性,Proxy 代理的是对象。
defineProperty 如果想代理对象的所有属性,需要遍历并为每个属性添加 setter 和 getter。
Proxy 只需要配置一个可以获取属性名参数的函数即可。

是否破坏原对象

defineProperty 的代理行为会破坏原对象,它会将原本的 value 变成了 setter 和 getter。
Proxy 则不会破坏原对象,只是在原对象上覆盖了一层。当新增属性时,希望属性被代理,defineProperty 需要显式调用该 API,而 Proxy 则可以直接用 obj.key = val的形式

Object.definePropertyProxy
拦截范围只能拦截对象的单个属性操作,即只能定义特定属性的getter和setter可以拦截对对象的所有操作,包括属性访问、赋值、删除、函数调用等,可以使用get、set、deleteProperty等捕捉器来拦截这些操作
动态属性不可以处理动态属性可以处理对象的动态添加和删除属性
是否破坏原对象
性能性能逊于Proxy对于处理嵌套对象和大量属性的情况,性能好
兼容性兼容性更好由于ES6中才引入Proxy,所以兼容性略差
本质对象基本操作之一,[[DefineOwnProperty]]针对整个对象所有基本方法的拦截器
属性不存在时的行为无法拦截不存在的属性可以拦截并处理

标签:obj,target,Object,Proxy,defineProperty,属性
From: https://blog.csdn.net/owo_ovo/article/details/142462523

相关文章

  • 收藏:加不加「/」?Nginx location 路径与 proxy_pass 的规律
    从一张梗图开始起源于在TG某个频道看到的一张图:图下面的评价是:Nginxissohard!实际上这张图描述的是nginxlocation的路径配置,及location代码块中proxy_pass的路径关系,属于nginx应用中路径转发的知识。例如图中Case1对应的代码块应该为:location/test1{......
  • Keepalived 和 HAProxy的主要区别对比
    Keepalived和HAProxy(HighAvailabilityProxy)都是用于构建高可用性和负载均衡服务的重要工具,但它们的设计目标和主要功能有所不同。主要区别1.功能定位HAProxy:主要用于负载均衡,可以将客户端的请求分发到不同的后端服务器,同时提供健康检查等功能。HAProxy支持多种负载均衡算法,如......
  • dmpushproxy.dll文件丢失导致程序无法运行问题
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个dmpushproxy.dll文件(挑选合适的版本文件)把......
  • JS输出为[object object] 如何解决以及原因
    参考文档:https://blog.csdn.net/weixin_48141487/article/details/121758541 问题描述项目中,欲在控制台输出变量res(自定义对象)查看数据,代码为:console.log('res:'+res);但控制台显示结果为res:[objectObject],并非想要查看的数据。最基本的要求是先去掉+号,试试看问题原因1......
  • WPF Unable to cast object of type 'System.Windows.Controls.SelectedItemCollectio
    SelectedItemsconverttoIListasbelowfailed;IList<Book>collection2=(IList<Book>)obj; System.InvalidCastExceptionHResult=0x80004002Message=Unabletocastobjectoftype'System.Windows.Controls.SelectedItemCollection'......
  • Nginx 中 proxy_pass 末尾斜杠的奥秘
    一、proxy_pass的类型概述Nginx的官网将proxy_pass分为两种类型:不带URI方式和带URI方式。不带URI方式只包含IP和端口号,例如proxy_passhttp://localhost:8080。而带URI方式在端口号之后有其他路径,包括只有单个“/”的,如proxy_passhttp://localhost:8080/,以及其......
  • WPF Combobox ObjectDataProvider MethodName ObjectType ObjectDataProvider.Metho
    <Window.Resources><ObjectDataProviderx:Key="kindEnum"MethodName="GetValues"ObjectType="{x:Typesys:Enum}"><ObjectDataProvider.MethodParameters><x:Type......
  • 类型转换 Cast a pandas object to a specified dtype ``dtype``.
    实践:修改列值分组、排序使用同一字段:整数--》区间名称字符串          FutureWarning:Settinganitemofincompatibledtypeisdeprecatedandwillraiseinafutureerrorofpandas.Value'[320,439)'hasdtypeincompatiblewithint64,pl......
  • 海外住宅IP|海外代理IP|海外IP代理|静态住宅IP|动态住宅IP|这些概念到底是什么,521proxy告诉
    一般情况下,海外ip指的就是国外ip,就是除本国之外的其他国家及地区的ip。一般情况下,海外住宅ip指的就是国外住宅ip。ip的概念及外延比较大,如机房ip属于ip,住宅ip也是属于ip。因此海外住宅ip或者国外住宅ip属于海外ip或者国外ip的一种。IP地址可以根据不同的分类方式分为不同......
  • 兼收并蓄 TypeScript - 进阶: proxy, reflect
    源码https://github.com/webabcd/TypeScriptDemo作者webabcd兼收并蓄TypeScript-进阶:proxy,reflect示例如下:advanced\proxy_reflect.ts{//Proxy-代理(拦截目标对象的属性操作和函数操作)lettarget={name:'webabcd',age:40,......