面试回答思路:
基础和核心 Object.defineProperty()的作用、数据页面响应式实现+es5/vue2.x缺点➕+vue3.0/es6/Proxy替换➕ 主要靠内部来实现:如图框框名字+依次说出每个是干嘛的+他们之间的关系➕双绑缺点(失效)
object.deineProperty()相关知识必备⛽️
优点👍:
能够实现对象的属性监听(引申问题👇)
兼容性好,IE9+
可通过改变自身的代码(递归)来丰富自身功能,向Proxy看齐
缺点❎:
1.页面只使用了obj的其中a属性,当修改obj中的b属性时,也是执行。----无脑执行工作,性能低,类似于React Hooks中useMemo方法
2: 只能监听到基本数据类型的变化,无法监听到引用数据类型的变化-- 功能太片面 验证👇
3: 像push、length、pop 这些特殊方法确实不能触发setter,这跟obd()其内部实现有关--八字不合
4: 不想暴露...忘了➕--所以被vue3.x的proxy和Reflect代替
5: 没有拦截方法
如何封装一个监听属性的方法?
答:外层再套个函数即可https://blog.csdn.net/mapbar_front/article/details/80472395
层级深的obj如何解决?
递归遍历:引用数据类型->处理成基本数据类型
---------------------------------------
想通过object.deineProperty()实现一个obj对象里所有属性值的数据变化,如何实现?
答:遍历👇 详情🔎https://blog.csdn.net/lyh6665/article/details/107929324
function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
for (const key in obj) {
// 给对象中的每一个方法都设置响应式
defineProperty(obj, key, obj[key])
}
}
---------------------------------------
相关属性了解下:
let obj = {}
Object.defineProperty(obj, 'name', {
configurable: true, // 可删除
enumerable: true, //可枚举
writable: true, //可修改
value: '码不停息'
});
需要注意的是:value,writable 和get,set不能同时进行配置
---------------------------------------------------------------------------
#缺点1-代码实现¥:
<body>
<span id="name"></span>
<body>
<script>
var data = {
name: '小红',
age:18
};
Object.defineProperty(data, 'name', {
get: function(){},
set: function(newValue){ // 页面响应处理
document.getElementById('name').innerText = newValue
data.name = value
},
enumerable: true,
configurable: true
});
// 页面DOM listener
document.getElementById('name').onchange = function(e) {
data.name = e.target.value;
}
</script>
-------------------------------------------------------------------------------
#缺点2👆
验证🤔️: 在控制台把初始data中,name:'小明' -->改成引用类型:name: {}-->再给{}中添加元素,此时object.deineProperty()中set就监听不到此变化了。
解决方案🌹:typeof验证值的类型+引用类型:先递归遍历成基本类型
新问题🤔️: 下面几种对数组的操作方法,均没有触发数组的setter,页面没有变化响应。
1.操作数组的第2个下标
2.改变list的length
3.push()
解决方案🌹:结论:
1.直接修改数组中已有的元素是可以被object.deineProperty()监听到的。
2.像push、length、pop 这些特殊方法确实不能触发setter,这跟object.deineProper的内部实现有关
3.改变超过数组长度的下标的值时,值变化是不能监听到的。
综上:Object.defineProperty监听不存在的数组元素,并且通过一些能造成数组的方法造成数组改变也不能监听到。
vue2.x源码结论🐈:
Vue2.x中并没有实现将已存在的数组元素做监听,而是去监听造成数组变化的方法,触发这个方法的同时去调用挂载好的响应页面方法,达到页面响应式的效果。
接👆引申问题🤔️:为什么不用Object.defineProperty去监听数组中已存在的元素变化?
尤雨溪答🌹:不建议给每一个数组元素都绑定上监听,受益和消耗很小,不成正比。
详情见🔎:CSDN关注:全栈者/博文链接🔗:
https://blog.csdn.net/qq_27053493/article/details/105355319?spm=1001.2014.3001.5501
---------------------------接上文👆-------------------------------
Proxy优点👍:
1.可以直接监听对象而非属性;明确表示可以监听到数组以及各种数据类型的变化;
2.可满足此场景要求:
有个obj对象,不想将自身所有属性完全对外暴露出去,想做一层在原对象操作前的拦截、检查、代理。
3.可满足此场景要求:异步触发 初始data里的数据变化
4.有多达13种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等。
Object.defineProperty 就没有拦截方法...
5.Proxy 返回的是一个新对象,(类似map)我们可以只操作新的对象达到目的,
而 Object.defineProperty 只能在遍历对象时,操作原属性;
6.Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Proxy缺点❎: 存在浏览器兼容性问题,而且无法用 polyfill 磨平。所以只能在Vue3.0中才能用。
Proxy&生命周期的交集:
beforeCreate
created
beforeMount
render:会生成一个Watcher实例: 就是初始化一个模版地方,用来展示data/computed实例的数据。(vue中的组件在挂载前,都会基于组件的render函数,生成watcher并执行render函数 来进行渲染)
mounted(挂载)
--------Proxy和defineProperty一比,defineProperty功能弱爆了。哈哈哈😂----------------
const myObj = {
_id: '我是myObj的ID',
name: 'mvvm',
age: 25
}
const myProxy = new Proxy(myObj, {
get(target, propKey) {
if (propKey === 'age') {
console.log('年龄很私密,禁止访问');
return '*';
}
return target[propKey];
},
set(target, propKey, value, receiver) {
if (propKey === '_id') {
console.log('id无权修改');
return;
}
target[propKey] = value + (receiver.time || '');
},
});
myProxy._id = 34;
// id无权修改
console.log(`age is: ${myProxy.age}`);
//年龄很私密,禁止访问
// age is: *
myProxy.name = 'my name is Proxy';
console.log(myProxy);
// { _id: '我是myObj的ID', name: 'my name is Proxy', age: 25}
const newObj = {
time: ` [${new Date()}]`,
};
// 原对象原型链赋值
Object.setPrototypeOf(myProxy, newObj);
myProxy.name = 'my name is newObj';
console.log(myProxy.name);
//my name is newObj [Thu Mar 19 2020 18:33:22 GMT+0800 (GMT+08:00)]
Observer是观察者
Dep角色🎭:订阅收集和发布者。
Dep相关的用法的作用?
new Dep() ---实例化
dep.notity() ---- 当数据发生变化时调用,会去通知订阅的watcher进行更新
if(Dep.target){ dep.depend() } -------- 在此处将当前watcher加入到dep中
dep.addSub(new Watcher()) ------
dep.subs.push(watcher) ------dep实例内有个subs数组,专门按依赖分类,分别存储数据
Dep.target = target; --- 将targetwatcher实例存进Dep
Dep.target ===Watcher实例化对象: 通知订阅的watcher更新
Dep.prototype.notify = function notify () {}
Dep.prototype.removeSub = function removeSub (sub) {};// 取消订阅
// 依据MVVM双绑原理图👇,Dep和Watcher互相可以改变的。
Dep通知Watcher进行变化
Warcher通知Dep新增订阅者
watcher是作为订阅者的角色(即new Vue实例data/computed数据展示的地方)
Watcher相关的用法的作用?
pushTarget(this); 设置当前watcher到全局变量上去
watcher.addDep(); // 订阅者想新增一块地方来展示数据,告诉dep来add
Watch
1.是什么?
是一个.{ 键名:键值}键名:被观察的表达式; 键值:回调函数/方法名/对象
2.分类?(3种)
渲染watcher、computed watcher、➕
3.干什么的?
用于观察和监听页面上的vue实例
4.啥时候用?
适用于在数据变化的同时执行异步操作(并发)
适用于比较大的开销
5.代码实现?
...
2张图简单要记得
例子: new Vue()实例,data(){}有数据并应用在了dom上,computed:{return a*b}也应用在了vue模版上,
很常见:脑海中必须反应出的知识点#:
思路1: 监听数据变化->watcher->共3种、并判断出有哪几种(render watcher和computed watcher)->管理watcher->任务队列queenWatcher->func queueWatcher(){} -->特点:重复的 watcher 知会被压入一次;在一个事件循环中 触发了多次的 watcher 只会被压入队列一次;—【内部真正参与者】
思路2: 监听数据变化->Object.defineProperty() —是【基础核心】MVVM/vue原理
为啥要挂在原型上?
Dep.prototype.notify = function notify () {}
Watcher.prototype.update = function update () {}、
Vue2.x: Object.defineProperty()
Vue3.x: 被Polify替换