Object.defineProperty()
**Object.defineProperty()** 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
```plain const object1 = {};Object.defineProperty(object1, ‘property1’, {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
<h2 id="ynhZJ">[语法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E8%AF%AD%E6%B3%95)</h2>
<font style="color:rgb(33, 33, 33);">Object.defineProperty(</font><font style="color:rgb(33, 33, 33);">obj</font><font style="color:rgb(33, 33, 33);">, </font><font style="color:rgb(33, 33, 33);">prop</font><font style="color:rgb(33, 33, 33);">, </font><font style="color:rgb(33, 33, 33);">descriptor</font><font style="color:rgb(33, 33, 33);">)</font><h3 id="eYu9q">[参数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E5%8F%82%E6%95%B0)</h3>
**<font style="color:rgb(33, 33, 33);background-color:rgb(238, 238, 238);">obj</font>**<font style="color:rgb(33, 33, 33);">要定义属性的对象。</font>**<font style="color:rgb(33, 33, 33);background-color:rgb(238, 238, 238);">prop</font>**<font style="color:rgb(33, 33, 33);">要定义或修改的属性的名称或</font><font style="color:rgb(33, 33, 33);"> </font>[Symbol](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol)<font style="color:rgb(33, 33, 33);"> </font><font style="color:rgb(33, 33, 33);">。</font>**<font style="color:rgb(33, 33, 33);background-color:rgb(238, 238, 238);">descriptor</font>**<font style="color:rgb(33, 33, 33);">要定义或修改的属性描述符。</font><h3 id="Zm8T1">[返回值](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#%E8%BF%94%E5%9B%9E%E5%80%BC)</h3>
<font style="color:rgb(33, 33, 33);">被传递给函数的对象。</font><h1 id="yxeRT">面试官: 实现双向绑定Proxy比defineproperty优劣如何?</h1>
<h5 id="Iu8LC">文章链接地址:[https://juejin.cn/post/6844903601416978439](https://juejin.cn/post/6844903601416978439)</h5>
<h2 id="GKZvU">往期</h2>
+ [面试官系列(1): 如何实现深克隆](https://juejin.im/post/6844903584023183368)
+ [面试官系列(2): Event Bus的实现](https://juejin.im/post/6844903587043082247)
+ [面试官系列(3): 前端路由的实现](https://juejin.im/post/6844903589123457031)<h2 id="FNOP3">前言</h2>
**双向绑定**其实已经是一个老掉牙的问题了,只要涉及到MVVM框架就不得不谈的知识点,但它毕竟是Vue的三要素之一,**Vue三要素**+ 响应式: 例如如何监听数据变化,其中的实现方法就是我们提到的双向绑定
+ 模板引擎: 如何解析模板
+ 渲染: Vue如何将监听到的数据变化和解析后的HTML进行渲染可以实现双向绑定的方法有很多,KnockoutJS基于观察者模式的双向绑定,Ember基于数据模型的双向绑定,Angular基于脏检查的双向绑定,本篇文章我们重点讲面试中常见的基于**数据劫持**的双向绑定。常见的基于数据劫持的双向绑定有两种实现,一个是目前Vue在用的Object.defineProperty,另一个是ES2015中新增的Proxy,而Vue的作者宣称将在Vue3.0版本后加入Proxy从而代替Object.defineProperty,通过本文你也可以知道为什么Vue未来会选择Proxy。严格来讲Proxy应该被称为『代理』而非『劫持』,不过由于作用有很多相似之处,我们在下文中就不再做区分,统一叫『劫持』。我们可以通过下图清楚看到以上两种方法在**双向绑定**体系中的关系. 基于数据劫持的当然还有已经凉透的Object.observe方法,已被废弃。**提前声明:** 我们没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现.---<h2 id="y2FEM">文章目录</h2>
1. 基于数据劫持实现的双向绑定的特点
2. 基于Object.defineProperty双向绑定的特点
3. 基于Proxy双向绑定的特点---<h2 id="h9BaU">1.基于数据劫持实现的双向绑定的特点</h2>
<h3 id="eab5o">1.1 什么是数据劫持</h3>
数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。// 这是将要被劫持的对象 const data = { name: '', }; function say(name) { if (name === '古天乐') { console.log('给大家推荐一款超好玩的游戏'); } else if (name === '渣渣辉') { console.log('戏我演过很多,可游戏我只玩贪玩懒月'); } else { console.log('来做我的兄弟'); } } // 遍历对象,对其属性值进行劫持 Object.keys(data).forEach(function(key) { Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { console.log('get'); }, set: function(newVal) { // 当属性值发生变化时我们可以进行额外操作 console.log(`大家好,我系${newVal}`); say(newVal); }, }); }); data.name = '渣渣辉'; //大家好,我系渣渣辉 //戏我演过很多,可游戏我只玩贪玩懒月 复制代码<h3 id="gHYQL">1.2 数据劫持的优势</h3>
目前业界分为两个大的流派,一个是以React为首的单向数据绑定,另一个是以Angular、Vue为主的双向数据绑定。其实三大框架都是既可以双向绑定也可以单向绑定,比如React可以手动绑定onChange和value实现双向绑定,也可以调用一些双向绑定库,Vue也加入了props这种单向流的api,不过都并非主流卖点。单向或者双向的优劣不在我们的讨论范围,我们需要讨论一下对比其他双向绑定的实现方法,数据劫持的优势所在。1. 无需显示调用: 例如Vue运用数据劫持+发布订阅,直接可以通知变化并驱动视图,上面的例子也是比较简单的实现data.name = '渣渣辉'后直接触发变更,而比如Angular的脏检测则需要显示调用markForCheck(可以用zone.js避免显示调用,不展开),react需要显示调用setState。
2. 可精确得知变化数据:还是上面的小例子,我们劫持了属性的setter,当属性值改变,我们可以精确获知变化的内容newVal,因此在这部分不需要额外的diff操作,否则我们只知道数据发生了变化而不知道具体哪些数据变化了,这个时候需要大量diff来找出变化值,这是额外性能损耗。<h3 id="Igu5Z">1.3 基于数据劫持双向绑定的实现思路</h3>
**数据劫持**是双向绑定各种方案中比较流行的一种,最著名的实现就是Vue。基于数据劫持的双向绑定离不开Proxy与Object.defineProperty等方法对对象/对象属性的"劫持",我们要实现一个完整的双向绑定需要以下几个要点。1. 利用Proxy或Object.defineProperty生成的Observer针对对象/对象的属性进行"劫持",在属性发生变化后通知订阅者
2. 解析器Compile解析模板中的Directive(指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染
3. Watcher属于Observer和Compile桥梁,它将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化我们看到,虽然Vue运用了数据劫持,但是依然离不开**发布订阅**的模式,之所以在系列2做了[Event Bus的实现](https://juejin.im/post/6844903587043082247),就是因为我们不管在学习一些框架的原理还是一些流行库(例如Redux、Vuex),基本上都离不开**发布订阅**模式,而_Event_模块则是此模式的经典实现,所以如果不熟悉**发布订阅**模式,建议读一下系列2的文章。---<h2 id="l8xZR">2.基于Object.defineProperty双向绑定的特点</h2>
关于Object.defineProperty的文章在网络上已经汗牛充栋,我们不想花过多时间在Object.defineProperty上面,本节我们主要讲解Object.defineProperty的特点,方便接下来与Proxy进行对比。对Object.defineProperty还不了解的请阅读[文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)两年前就有人写过基于Object.defineProperty实现的[文章](https://segmentfault.com/a/1190000006599500),想深入理解Object.defineProperty实现的推荐阅读,本文也做了相关参考。上面我们推荐的文章为比较完整的实现(400行代码),我们在本节只提供一个极简版(20行)和一个简化版(150行)的实现,读者可以循序渐进地阅读。<h3 id="kUwz4">2.1 极简版的双向绑定</h3>
我们都知道,Object.defineProperty的作用就是劫持一个对象的属性,通常我们对属性的getter和setter方法进行劫持,在对象的属性发生变化时进行特定的操作。我们就对对象obj的text属性进行劫持,在获取此属性的值时打印'get val',在更改属性值的时候对DOM进行操作,这就是一个极简的双向绑定。const obj = {}; Object.defineProperty(obj, 'text', { get: function() { console.log('get val');  }, set: function(newVal) { console.log('set val:' + newVal); document.getElementById('input').value = newVal; document.getElementById('span').innerHTML = newVal; } }); const input = document.getElementById('input'); input.addEventListener('keyup', function(e){ obj.text = e.target.value; }) 复制代码在线示例 [极简版双向绑定](https://codepen.io/xiaomuzhu/pen/gzmEab/) by Iwobi ([@xiaomuzhu](https://codepen.io/xiaomuzhu)) on [CodePen](https://codepen.io).<h3 id="UHi3k">2.2 升级改造</h3>
我们很快会发现,这个所谓的_双向绑定_貌似并没有什么乱用。。。原因如下:1. 我们只监听了一个属性,一个对象不可能只有一个属性,我们需要对对象每个属性进行监听。
2. 违反开放封闭原则,我们如果了解[开放封闭原则](https://zh.wikipedia.org/zh-hans/%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99)的话,上述代码是明显违反此原则,我们每次修改都需要进入方法内部,这是需要坚决杜绝的。
3. 代码耦合严重,我们的数据、方法和DOM都是耦合在一起的,就是传说中的面条代码。那么如何解决上述问题?Vue的操作就是加入了**发布订阅**模式,结合Object.defineProperty的劫持能力,实现了可用性很高的双向绑定。首先,我们以**发布订阅**的角度看我们第一部分写的那一坨代码,会发现它的_监听_、_发布_和_订阅_都是写在一起的,我们首先要做的就是解耦。我们先实现一个订阅发布中心,即消息管理员(Dep),它负责储存订阅者和消息的分发,不管是订阅者还是发布者都需要依赖于它。let uid = 0; // 用于储存订阅者并发布消息 class Dep { constructor() { // 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher this.id = uid++; // 储存订阅者的数组 this.subs = []; } // 触发target上的Watcher中的addDep方法,参数为dep的实例本身 depend() { Dep.target.addDep(this); } // 添加订阅者 addSub(sub) { this.subs.push(sub); } notify() { // 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理 this.subs.forEach(sub => sub.update()); } } // 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher Dep.target = null; 复制代码现在我们需要实现监听者(Observer),用于监听属性值的变化。// 监听者,监听对象属性值的变化 class Observer { constructor(value) { this.value = value; this.walk(value); } // 遍历属性值并监听 walk(value) { Object.keys(value).forEach(key => this.convert(key, value[key])); } // 执行监听的具体方法 convert(key, val) { defineReactive(this.value, key, val); } } function defineReactive(obj, key, val) { const dep = new Dep(); // 给当前属性的值添加监听 let chlidOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { // 如果Dep类存在target属性,将其添加到dep实例的subs数组中 // target指向一个Watcher实例,每个Watcher都是一个订阅者 // Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法 if (Dep.target) { dep.depend(); } return val; }, set: newVal => { if (val === newVal) return; val = newVal; // 对新值进行监听 chlidOb = observe(newVal); // 通知所有订阅者,数值被改变了 dep.notify(); }, }); } function observe(value) { // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听 if (!value || typeof value !== 'object') { return; } return new Observer(value); } 复制代码那么接下来就简单了,我们需要实现一个订阅者(Watcher)。class Watcher { constructor(vm, expOrFn, cb) { this.depIds = {}; // hash储存订阅者的id,避免重复的订阅者 this.vm = vm; // 被订阅的数据一定来自于当前Vue实例 this.cb = cb; // 当数据更新时想要做的事情 this.expOrFn = expOrFn; // 被订阅的数据 this.val = this.get(); // 维护更新之前的数据 } // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用 update() { this.run(); } addDep(dep) { // 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存 // 此判断是避免同id的Watcher被多次储存 if (!this.depIds.hasOwnProperty(dep.id)) { dep.addSub(this); this.depIds[dep.id] = dep; } } run() { const val = this.get(); console.log(val); if (val !== this.val) { this.val = val; this.cb.call(this.vm, val); } } get() { // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者 Dep.target = this; const val = this.vm._data[this.expOrFn]; // 置空,用于下一个Watcher使用 Dep.target = null; return val; } } 复制代码那么我们最后完成Vue,将上述方法挂载在Vue上。class Vue { constructor(options = {}) { // 简化了$options的处理 this.$options = options; // 简化了对data的处理 let data = (this._data = this.$options.data); // 将所有data最外层属性代理到Vue实例上 Object.keys(data).forEach(key => this._proxy(key)); // 监听数据 observe(data); } // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者 $watch(expOrFn, cb) { new Watcher(this, expOrFn, cb); } _proxy(key) { Object.defineProperty(this, key, { configurable: true, enumerable: true, get: () => this._data[key], set: val => { this._data[key] = val; }, }); } } 复制代码看下效果:在线示例 [双向绑定实现---无漏洞版](https://codepen.io/xiaomuzhu/pen/jxBRgj/) by Iwobi ([@xiaomuzhu](https://codepen.io/xiaomuzhu)) on [CodePen](https://codepen.io).至此,一个简单的双向绑定算是被我们实现了。<h3 id="fyJCB">2.3 Object.defineProperty的缺陷</h3>
其实我们升级版的双向绑定依然存在漏洞,比如我们将属性值改为数组。let demo = new Vue({ data: { list: [1], }, }); const list = document.getElementById('list'); const btn = document.getElementById('btn'); btn.addEventListener('click', function() { demo.list.push(1); }); const render = arr => { const fragment = document.createDocumentFragment(); for (let i = 0; i < arr.length; i++) { const li = document.createElement('li'); li.textContent = arr[i]; fragment.appendChild(li); } list.appendChild(fragment); }; // 监听数组,每次数组变化则触发渲染函数,然而...无法监听 demo.$watch('list', list => render(list)); setTimeout( function() { alert(demo.list); }, 5000, ); 复制代码在线示例 [双向绑定-数组漏洞](https://codepen.io/xiaomuzhu/pen/NMjKxV/) by Iwobi ([@xiaomuzhu](https://codepen.io/xiaomuzhu)) on [CodePen](https://codepen.io).是的,Object.defineProperty的第一个缺陷,无法监听数组变化。 然而[Vue的文档](https://cn.vuejs.org/v2/guide/list.html#%E6%95%B0%E7%BB%84%E6%9B%B4%E6%96%B0%E6%A3%80%E6%B5%8B)提到了Vue是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue这种是无法检测的。push() pop() shift() unshift() splice() sort() reverse() 复制代码其实作者在这里用了一些奇技淫巧,把无法监听数组的情况hack掉了,以下是方法示例。const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; const arrayAugmentations = []; aryMethods.forEach((method)=> { // 这里是原生Array的原型方法 let original = Array.prototype[method]; // 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上 // 注意:是属性而非原型属性 arrayAugmentations[method] = function () { console.log('我被改变啦!'); // 调用对应的原生方法并返回结果 return original.apply(this, arguments); }; }); let list = ['a', 'b', 'c']; // 将我们要监听的数组的原型指针指向上面定义的空数组对象 // 别忘了这个空数组的属性上定义了我们封装好的push等方法 list.__proto__ = arrayAugmentations; list.push('d'); // 我被改变啦! 4 // 这里的list2没有被重新定义原型指针,所以就正常输出 let list2 = ['a', 'b', 'c']; list2.push('d'); // 4 复制代码由于只针对了八种方法进行了hack,所以其他数组的属性也是检测不到的,其中的坑很多,可以阅读上面提到的文档。我们应该注意到在上文中的实现里,我们多次用遍历方法遍历对象的属性,这就引出了Object.defineProperty的第二个缺陷,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择。Object.keys(value).forEach(key => this.convert(key, value[key])); 复制代码---<h2 id="AbmPZ">3.Proxy实现的双向绑定的特点</h2>
Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty的全方位加强版,具体的文档可以查看[此处](http://es6.ruanyifeng.com/#docs/proxy);<h3 id="CpEyD">3.1 Proxy可以直接监听对象而非属性</h3>
我们还是以上文中用Object.defineProperty实现的极简版双向绑定为例,用Proxy进行改写。const input = document.getElementById('input'); const p = document.getElementById('p'); const obj = {}; const newObj = new Proxy(obj, { get: function(target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { console.log(target, key, value, receiver); if (key === 'text') { input.value = value; p.innerHTML = value; } return Reflect.set(target, key, value, receiver); }, }); input.addEventListener('keyup', function(e) { newObj.text = e.target.value; }); 复制代码在线示例 [Proxy版](https://codepen.io/xiaomuzhu/pen/KRmwRE/) by Iwobi ([@xiaomuzhu](https://codepen.io/xiaomuzhu)) on [CodePen](https://codepen.io).我们可以看到,Proxy直接可以劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty。<h3 id="OdEHW">3.2 Proxy可以直接监听数组的变化</h3>
当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和_length_的变化,我们可以借此进行操作,以上文中Object.defineProperty无法生效的列表渲染为例。const list = document.getElementById('list'); const btn = document.getElementById('btn'); // 渲染列表 const Render = { // 初始化 init: function(arr) { const fragment = document.createDocumentFragment(); for (let i = 0; i < arr.length; i++) { const li = document.createElement('li'); li.textContent = arr[i]; fragment.appendChild(li); } list.appendChild(fragment); }, // 我们只考虑了增加的情况,仅作为示例 change: function(val) { const li = document.createElement('li'); li.textContent = val; list.appendChild(li); }, }; // 初始数组 const arr = [1, 2, 3, 4]; // 监听数组 const newArr = new Proxy(arr, { get: function(target, key, receiver) { console.log(key); return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { console.log(target, key, value, receiver); if (key !== 'length') { Render.change(value); } return Reflect.set(target, key, value, receiver); }, }); // 初始化 window.onload = function() { Render.init(arr); } // push数字 btn.addEventListener('click', function() { newArr.push(6); }); 复制代码在线示例 [Proxy列表渲染](https://codepen.io/xiaomuzhu/pen/zjwGoN/) by Iwobi ([@xiaomuzhu](https://codepen.io/xiaomuzhu)) on [CodePen](https://codepen.io).很显然,Proxy不需要那么多hack(即使hack也无法完美实现监听)就可以无压力监听数组的变化,我们都知道,标准永远优先于hack。<h3 id="eX09V">3.3 Proxy的其他优势</h3>
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。<h2 id="ltLDN">下期预告</h2>
下期准备一篇我们主要讲为什么我们需要前端框架,或者换几种问法,对于此项目你为什么选择Angular、Vue、React等框架,而不是直接JQuery或者js?不使用框架可能遇到什么问题?使用框架的优势在哪里?框架解决了JQuery解决不了的什么问题?这个问题是电面神器,问题开放性很好,也不需要面对面抠一些细节,同时有功底有思考的同学与跟风学框架的同学差距很容易暴露出来。我们会边解答这个问题边用Proxy构建一个Mini版Vue,构建Vue的过程就是我们不断解决不使用框架的情况下遇到的各种问题的过程。
链接:https://juejin.cn/post/6844903601416978439<h1 id="z0Mtt"><font style="color:rgb(51, 51, 51);">为什么Proxy可以优化vue的数据监听机制</font></h1>
<h6 id="YDyBq">链接地址:[https://juejin.cn/post/6844903726344306696](https://juejin.cn/post/6844903726344306696)</h6>
我们首先来看vue2.x中的实现,为简单起见,我们这里不考虑多级嵌套,也不考虑数组<h4 id="NVe5N">vue2.x中的实现</h4>
其本质是new Watcher(data, key, callback)的方式,而在调用之前是先将data中的所有属性转化成可监听的对象, 其主要就是利用Object.defineProperty,。class Watcher{ constructor(data, key, cb){ } } //转换成可监听对象 function observe(data){ new Observer(data) } //修改数据的getter和setter function defineReactive(obj, key){ let value = obj[key]; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get(){ return value; }, set(newVal){ value = newVal } }) } 复制代码Observer的实现很简单class Observer { constructor(data){ this.walk(data); } walk(data){ for(var key in data) { // 这里不考虑嵌套的问题,否则的话需要递归调用walk defineReactive(data, key) } } } 复制代码现在怎么将watcher和getter/setter联系起来,vue的方法是添加一个依赖类:Depclass Watcher{ constructor(data, key, cb){ this.cb = cb; Dep.target = this; //每次新建watcher的时候讲给target赋值,对target的管理这里简化了vue的实现 data[key];//调用getter,执行addSub, 将target传入对应的dep; vue的实现本质就是如此 } } class Dep { constructor(){ this.subs = []; } addSub(sub){ this.subs.push(sub); } notify(){ this.subs.forEach(sub => sub.cb()) } } function defineReactive(obj, key){ let value = obj[key]; let dep = new Dep(); //每一个属性都有一个对应的dep,作为闭包保存 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get(){ dep.addSub(Dep.target) Dep.target = null; return value; }, set(newVal){ value = newVal dep.notify(); } }) } 复制代码以上就是vue的思路,vue3之所以要从新实现,主要有这几个原因:1. Object.defineProperty的性能开销。
2. defineReactive一开始就要对要监听的对象所有属性都执行一遍,因为传统方法要将一个对象转换成可监听对象,只能如此。
3. 添加删除属性的问题。
4. 还有一点就是这个模块被耦合到了vue里面,新版本可以单独作为一个库来使用。然后我们来看看同样的功能采用Proxy会怎样实现。<h4 id="YOrVd">Proxy的实现</h4>
将一个对象转换成Proxy的方式很简单,只需要作为参数传给proxy即可;class Watcher { constructor(proxy, key, cb) { this.cb = cb; Dep.target = this; this.value = proxy[key]; } } class Dep { constructor(){ this.subs = [] } addSub(sub){ this.subs.push(sub); } notify(newVal){ this.subs.forEach(sub => { sub.cb(newVal, sub.value); sub.value = newVal; }) } } const observe = (obj) => { const deps = {}; return new Proxy(obj, { get: function (target, key, receiver) { const dep = (deps[key] = deps[key] || new Dep); Dep.target && dep.addSub(Dep.target) Dep.target = null; return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { const dep = (deps[key] = deps[key] || new Dep); Promise.resolve().then(() => { dep.notify(value); }) return Reflect.set(target, key, value, receiver); } }); } var state = observe({x:0}) new Watcher(state, 'x', function(n, o){ console.log(n, o) }); new Watcher(state, 'y', function(n, o){ console.log(n, o) }); state.x = 3; state.y = 3; 复制代码也许一开始我们只关心x和y,那么就不会对其他的属性做相应的处理,除非添加watcher,其他时间target都是null如果有什么错误请指正,谢谢。
相关文章:
Object.defineProperty()
**Object.defineProperty()** 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 plain const object1 {}; Object.defineProperty(object1, ‘property1’, { value: 42, writable: false }); object1.property1 77…...
大模型+知识图谱:重塑企业制度标准管理
在数字化转型的浪潮中,制度标准管理领域正迎来一场革命性的变革。借助大模型和知识图谱等前沿人工智能技术,制度标准管理不再仅仅是简单的文档存储和检索,而是演变为一个智能化、高效化、精准化的管理体系。 1.关键技术 我们的制度标准管理…...
ubuntu20系统下conda虚拟环境下安装文件存储位置
在 Conda 虚拟环境中执行 pip install 安装软件后,安装的文件会存储在该虚拟环境专属的 site-packages 目录中。具体路径取决于你激活的 Conda 环境路径。以下是定位步骤: 1. 确认 Conda 虚拟环境的安装路径 查看所有环境: conda info --env…...
深度学习编译器(整理某survey)
一、深度学习框架 TensorFlow PyTorch MXNet ONNX:定义了一个统一的表示,DL models的格式方便不同框架之间的转换模型 二、深度学习硬件 通用硬件(CPU、GPU):通过硬件和软件优化支持深度学习工作负载 GPU:通过多核架构实现高…...
Python学习第八天
查看函数参数 操作之前给大家讲一个小技巧:如何查看函数的参数(因为python的底层源码是C语言并且不是开放的,也一直困扰着刚学习的我,这个参数叫什么名之类的看doc又总是需要翻译挺麻烦的)。 比如我们下面要说到的op…...
SpringBoot为什么默认使用CGLIB?
大家好,我是锋哥。今天分享关于【SpringBoot为什么默认使用CGLIB?】面试题。希望对大家有帮助; SpringBoot为什么默认使用CGLIB? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot 默认使用 CGLIB(Code Generation Li…...
【消息队列】数据库的数据管理
1. 数据库的选择 对于当前实现消息队列这样的一个中间件来说,具体要使用哪个数据库,是需要稍作考虑的,如果直接使用 MySQL 数据库也是能实现正常的功能,但是 MySQL 也是一个客户端服务器程序,也就意味着如果想在其他服…...
pytest中pytest.ini文件的使用
pytest.ini 是 pytest 测试框架的配置文件,它允许你自定义 pytest 的行为。通过在 pytest.ini 中设置各种选项,可以改变测试用例的发现规则、输出格式、插件行为等。以下详细介绍 pytest.ini 文件的使用。 1. 文件位置 pytest.ini 文件通常位于项目的根目录下,pytest 在运…...
docker学习笔记(1)从安装docker到使用Portainer部署容器
docker学习笔记第一课 先交代背景 docker宿主机系统:阿里云ubuntu22.04 开发机系统:win11 docker镜像仓库:阿里云,此阿里云与宿主机系统没有关系,是阿里云提供的一个免费的docker仓库 代码托管平台:github&…...
Vue.js侦听器
侦听器 基本示例 计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。 在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数: …...
【C++学习篇】智能指针
目录 1. 智能指针的使用场景分析 2. RAII和智能指针的设计思路 3. C标准库智能指针的使用 4.shared_ptr和weak_ptr 4.1shared_ptr的循环引用问题 4.2 weak_ptr 1. 智能指针的使用场景分析 下⾯程序中我们可以看到,new了以后,我们也delete了,…...
数字电子技术基础(二十四)——TTL门电路的高、低电平的输出特性曲线
目录 1 TTL门电路的特性曲线 1.1 高电平输出特性 1.1.2 高电平输出特性的实验过程 1.1.2 TTL门电路的输出特性的实验结果 1.2 低电平的输出特性 1 TTL门电路的特性曲线 1.1 高电平输出特性 1.1.2 高电平输出特性的实验过程 现在想要测试TTL门电路的输出特性,…...
linux进程通信之共享内存
在 Linux 系统中,共享内存(Shared Memory) 是一种高效的进程间通信(IPC)方式,允许多个进程直接访问同一块物理内存区域。以下是关于 Linux 共享内存的详细讲解: 一、共享内存的核心特点 高速通信…...
学习第十一天-树
一、树的基础概念 1. 定义 树是一种非线性数据结构,由 n 个有限节点组成层次关系集合。特点: 有且仅有一个根节点其余节点分为若干互不相交的子树节点间通过父子关系连接 2. 关键术语 术语定义节点包含数据和子节点引用的单元根节点树的起始节点&#…...
场景题:10亿QQ用户,如何统计在线人数?
现在卷的环境下,面试除了八股文算法项目外,场景题也是问的越来越多了。一方面是就业市场竞争者较多所带来的必然结果;另一方面是公司对于应聘者的技术要求也越来越高了。 今天继续介绍Java面试常见的场景题:在线人数统计 现在用户…...
学习工具的一天之(burp)
第一呢一定是先下载 【Java环境】:Java Downloads | Oracle 下来是burp的下载 Download Burp Suite Community Edition - PortSwigger 【下载方法二】关注的一个博主 【BurpSuite 安装激活使用详细上手教程 web安全测试工具】https://www.bilibili.com/video/BV…...
归并排序:分治哲学的完美演绎与时空平衡的艺术
引言:跨越世纪的算法明珠 在计算机科学的璀璨星河中,归并排序犹如一颗恒久闪耀的明星。1945年,现代计算机之父冯诺伊曼在EDVAC计算机的研发过程中首次系统性地提出了这一算法,其精妙的分治思想不仅奠定了现代排序算法的理论基础&…...
蓝桥杯4T平台(串口打印电压值)
知识点:串口(单片机发送数据)按键ADC 题目 配置 代码 adc.c uint16_t getadc2(void) {uint16_t adc0;HAL_ADC_Start(&hadc2);adcHAL_ADC_GetValue(&hadc2);return adc; } adc.h uint16_t getadc2(void); main.c #include "lcd.h" #include…...
Stable Diffusion Prompt编写规范详解
Stable Diffusion Prompt编写规范详解 一、语法结构规范 (一)基础模板框架 [质量强化] [主体特征] [环境氛围] [风格控制] [镜头参数]质量强化:best quality, ultra detailed, 8k resolution主体特征:(1girl:1.3), long …...
es6常见知识点
官方文档:[https://es6.ruanyifeng.com/](https://es6.ruanyifeng.com/) 一、Class 1、Class Class只是一个语法糖,其功能用es5也能实现,但是比es5更符合类的期待 定义: constructor代表构造方法,而this指向new 生成的实例 定义类方法时,可以不使用function 注…...
leetcode1 两数之和 哈希表
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。 242. 有效的字母异位词 (opens new window)这道题目是用数组作为哈希表来解决哈希问题,349. 两个数组的交集 (o…...
Java中lombok的@Data注解【布尔类型】字段定义方式
文章目录 背景第一步、场景复现第二步、分析问题第三步、实现方案总结 背景 在Data注解的bean中添加Boolean字段时,set方法正常,get方法无法获取。 第一步、场景复现 在OrderInfo的实体中,新增布尔类型的字段:支付过【hasPaid】…...
理解数学概念——稠密性(density)
目录 1. 定义 2. 等价定义 3. 直观理解 1. 定义 在拓扑学(topology)和数学相关领域中,对于一个拓扑空间 X 的一个子集 A,若 X的每一个点要么属于A ,要么无限“接近”X的某个成员,则称这个子集 A 是稠密的(dense)或称A具有稠密性…...
【Spring AOP】_切点类的切点表达式
目录 1. 根据方法签名匹配编写切点表达式 1.1 具体语法 1.2 通配符表达规范 2. 根据注解匹配编写切点表达式 2.1 实现步骤 2.2 元注解及其常用取值含义 2.3 使用自定义注解 2.3.1 编写自定义注解MyAspect 2.3.2 编写切面类MyAspectDemo 2.3.3 编写测试类及测试方法 在…...
通过多线程获取RV1126的AAC码流
目录 一RV1126多线程获取音频编码AAC码流的流程 1.1AI模块的初始化并使能 1.2AENC模块的初始化 1.3绑定AI模块和AENC模块 1.4多线程获取每一帧AAC码流 1.5每个AAC码流添加ADTSHeader头部 1.6写入具体每一帧AAC的…...
HDFS 为什么不适合处理小文件?
目录 一、HDFS 是什么? 1. 核心目标 2. 基本架构 二、HDFS 为什么不适合处理小文件? 1. 元数据管理问题 2. 存储效率低下 3. 访问性能问题 4. 计算框架效率问题 5. 其他限制 一、HDFS 是什么? HDFS(Hadoop 分布式文件系统…...
网络空间安全(14)编辑器漏洞
一、概述 网页在线编辑器允许用户在网页上进行文本的编辑,并设置字体样式、段落行间距等,类似于使用Word进行编辑。然而,由于编辑器在处理用户输入、文件上传、权限控制等方面可能存在安全缺陷,因此容易成为攻击者利用的目标。 二…...
SpringMvc与Struts2
一、Spring MVC 1.1 概述 Spring MVC 是 Spring 框架的一部分,是一个基于 MVC 设计模式的轻量级 Web 框架。它提供了灵活的配置和强大的扩展能力,适合构建复杂的 Web 应用程序。 1.2 特点 轻量级:与 Spring 框架无缝集成,依赖…...
Avalonia 打包成deb
参考 https://www.cnblogs.com/Fengyinyong/p/13346642.html 安装工具 dotnet tool install --global dotnet-deb 还原包 dotnet restore -r linux-x64 dotnet deb install 打包,其中/p:SelfContainedtrue是独立运行 dotnet msbuild XXXCore.csproj /t:Creat…...
服务器数据恢复—raid5阵列中硬盘掉线导致上层应用不可用的数据恢复案例
服务器数据恢复环境&故障: 某公司一台服务器,服务器上有一组由8块硬盘组建的raid5磁盘阵列。 磁盘阵列中2块硬盘的指示灯显示异常,其他硬盘指示灯显示正常。上层应用不可用。 服务器数据恢复过程: 1、将服务器中所有硬盘编号…...
除了合并接口,还有哪些优化 Flask API 的方法?
除了合并接口,还有许多其他方法可以优化 Flask API,以下从性能优化、代码结构优化、安全性优化、错误处理优化等方面详细介绍: 性能优化 1. 使用缓存 内存缓存:可以使用 Flask-Caching 扩展来实现内存缓存,减少对数…...
制服小程序的“滑手”:禁用页面左右滑动全攻略
哈哈,看来你已经很聪明地发现了小程序中左右滑动的“顽皮”行为!😄 没错,我们可以通过设置 disableScroll 属性来“管教”它,同时结合 CSS 样式让页面既禁得住横向“乱跑”,又能顺畅地上下滚动。你的方案已…...
学习日记-250305
阅读论文:Leveraging Pedagogical Theories to Understand Student Learning Process with Graph-based Reasonable Knowledge Tracing ps:代码逻辑最后一点还没理顺,明天继续 4.2 Knowledge Memory & Knowledge Tracing 代码研究: 一般…...
DeepSeek R1模型医疗机构本地化部署评估分析(Discuss V1版上)
为了确保医疗机构在部署和应用DeepSeek R1模型时的成功,可以根据各个步骤设计一套综合的评估和评测体系。该体系将帮助医疗机构在实施过程中持续跟踪效果、识别潜在问题并进行优化调整。以下是对各步骤的详细评估和评测体系设计。 1. 确定模型需求 在医疗机构上线DeepSeek R…...
java 查找连个 集合的交集部分数据
利用了Java 8的Stream API,代码简洁且效率高 import java.util.stream.Collectors; import java.util.List; import java.util.HashSet; import java.util.Set;public class ListIntersection {public static List<Long> findIntersection(List<Long> …...
Hadoop管理页看不到任务的问题
这个yarn分配任务了但是为空 在$HADOOP_HOME/conf/mapred-site.xml 原来的配置文件基础之上添加: <property><name>mapreduce.framework.name</name><value>yarn</value></property> 重启之后就好了...
cmake、CMakeLists.txt、make、ninja
文章目录 一、概念0.cmake官网1.什么是cmake2.为什么使用cmake3.CMakeLists.txt 二、CMakeLists.txt语法:如何编写CMakeLists.txt,语法详解(0)语法基本原则(1)project关键字(2)set关键字(3)message关键字(4)add_executable关键字(5)add_subdirectory关键…...
PHP之Cookie和Session
在你有别的编程语言的基础下,你想学习PHP,可能要了解的一些关于cookie和session的信息。 Cookie 参数信息 setcookie(name,value,expire, path, domain); name : Cookie的名称。 value : Cookie的值。 expire : Cookie的过期时间,可以是一…...
学习记录-用例设计编写
黑马测试视频记录 目录 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法编辑 5、错误推荐法 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法 5、错误推荐法 时间紧任务重…...
【Docker】容器安全之非root用户运行
【Docker】容器安全之非root用户运行 1. 场景2. 原 Dockerfile 内容3. 整改结果4. 非 root 用户带来的潜在问题4.1 文件夹读写权限异常4.2 验证文件夹权限 1. 场景 最近有个项目要交付,第三方测试对项目源码扫描后发现一个问题,服务的 Dockerfile 都未指…...
CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现
文章目录 CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.构造POC2.复现CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现 0x01 前言 免责声明:请勿利用文章内的相…...
如何使用 Python+Flask+win32print 实现简易网络打印服务1
Python 实现网络打印机:Flask win32print 在工作场景中,我们可能需要一个简单的网页接口,供他人上传文档并自动打印到指定打印机。 本文将演示如何使用 Python Flask win32print 库来实现这一需求。 代码详见:https://github.…...
Ubuntu20.04双系统安装及软件安装(十一):向日葵远程软件
Ubuntu20.04双系统安装及软件安装(十一):向日葵远程软件 打开向日葵远程官网,下载图形版本: 在下载目录下打开终端,执行: sudo dpkg -i SunloginClient(按tab键自动补全)出现报错: …...
鸿蒙启动页开发
鸿蒙启动页开发 1.1 更改应用名称和图标 1.更改应用图标 找到moudle.json5文件,找到应用启动的EntryAbility下面的icon,将原来的图标改成自己设置的即可 2.更改应用名称 3.效果展示 2.1 广告页面开发 3.1 详细介绍 3.1.1 启动页面 import { PrivacyDialog } fr…...
认知动力学视角下的生命优化系统:多模态机器学习框架的哲学重构
认知动力学视角下的生命优化系统:多模态机器学习框架的哲学重构 一、信息熵与生命系统的耗散结构 在热力学第二定律框架下,生命系统可视为负熵流的耗散结构: d S d i S d e S dS d_iS d_eS dSdiSdeS 其中 d i S d_iS diS为内部熵…...
【Python编程】高性能Python Web服务部署架构解析
一、FastAPI 与 Uvicorn/Gunicorn 的协同 1. 开发环境:Uvicorn 直接驱动 作用:Uvicorn 作为 ASGI 服务器,原生支持 FastAPI 的异步特性,提供热重载(--reload)和高效异步请求处理。 启动命令: u…...
仿mudou库one thread oneloop式并发服务器
项目gitee:仿muduo: 仿muduo 一:项目目的 1.1项目简介 通过咱们实现的⾼并发服务器组件,可以简洁快速的完成⼀个⾼性能的服务器搭建。 并且,通过组件内提供的不同应⽤层协议⽀持,也可以快速完成⼀个⾼性能应⽤服务器…...
AI推理模型竞赛:从DeepSeek R1到Claude 3.7的关键进展
摘要 在Reasoning Model首轮竞赛中,从R1到Sonnet 3.7,AI领域取得了显著进展。DeepSeek R1的发布激发了推理模型的竞争。过去一个月内,顶尖AI实验室相继推出了三款最新的SOTA推理模型:OpenAI的o3-mini和deep research,x…...
AORO P9000 PRO三防平板携手RTK高精度定位,电力巡检效率倍增
电网系统覆盖幅员辽阔,每年因设备故障导致的巡检耗时超过百万工日。传统巡检模式受限于定位误差、设备防护不足和作业效率低下三大核心痛点,亟需智能化工具的突破性革新。为了满足这一需求,遨游通讯推出AORO P9000 PRO三防平板,以…...
【Linux———信号精讲】
你是怎么做到的,给了她想要的爱............................................................................................ 文章目录 前言 一、【信号入门】 1.1、【生活角度的信号】 1.2、【ctrl c与z】 1.3、【信号的发送与记录】 1.4、【信号处理常见方式…...