一、vue2.0 响应式
1. 对象的响应式
1.1 Object.defineProperty
Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
obj——要定义属性的对象 prop——要定义或修改的属性的名称或Symbol descriptor——对象,要定义或修改的属性描述符
{
value: undefined,
get: undefined,
set: undefined,
writable: false,
enumerable: false,
configurable: false
}
通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。 而默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。 示例:
const a = {b : 1}
console.log(Object.getOwnPropertyDescriptor(a, 'b'))
Object.defineProperty(a, 'c', {value: '2'})
console.log(Object.getOwnPropertyDescriptor(a, 'c'))
a.c = 3
console.log(a.c)
Object.defineProperty(a, 'c', {value: '4'})
console.log(a.c)
1.2 set和get
const app = document.getElementById('app')
const data = {
a: {
b: {
c: 1
}
}
}
function render () {
const virtualDom = `这是我的内容${data.a.b.c}`
app.innerHTML = virtualDom
}
function observer (obj) {
let value
for (const key in obj) {
value = obj[key]
if (typeof value === 'object'){
arguments.callee(value)
} else {
Object.defineProperty(obj, key, {
get: function(){
return value
},
set: function(newValue){
value = newValue
render()
}
})
}
}
}
render()
observer(data)
setTimeout(() => {
data.a.b.c = 22
}, 2000)
setTimeout(() => {
data.a.b.c = 88
}, 5000)
上述方法实现了数据的响应,但存在很大的问题,我们触发一次set,就需要整个页面重新渲染,然而这个值可能只在某一个组件中使用了。
所以将get和set优化:
Object.defineProperty(data, key, {
get: function(){
dep.depend()
return value
},
set: function(newValue){
value = newValue
dep.notify()
}
});
dep是Vue负责管理依赖的一个类
补充: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
const vm = new Vue({
data: {
a: 1
}
})
vm.b = 2
2. 数组的响应式
vue 中处理数组的变化,直接通过下标触发视图的更改,只能使用push、shift等方法,而数组不能使用Object.defineProperty() 其实 Vue用装饰者模式来重写了数组这些方法
Object.create(proto,[propertiesObject]) 方法是创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
proto ——新创建对象的原型对象; propertiesObject ——选填,类型是对象,如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符
const a = {}
const a = Object.create(Object.prototype)
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = 'Matthew';
me.isHuman = true;
me.printIntroduction();
const o = Object.create(Object.prototype, {
foo: {
writable:true,
configurable:true,
value: "hello"
},
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
console.log(o)
vue中的装饰者模式
const arraypro = Array.prototype
const arrob = Object.create(arraypro)
const arr=['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
arr.forEach(function(method) {
arrob[method] = function () {
arraypro[method].apply(this, arguments)
dep.notify()
}
})
const a = [1, 2, 3]
a.__proto__ = arrob
上面对于新对象arrob的方法,我们是直接赋值的,这样会有一个问题,就是用户可能会不小心改掉我们的对象,所以我们可以用到我们前面讲到的Object.defineProperty来规避这个问题,我们创建一个公用方法def专门来设置不能修改值的属性
function def (obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
value: value,
});
}
arr.forEach(function(method){
def(arrob, method, function () {
arraypro[method].apply(this, arguments)
dep.notify()
})
})
3. 补充:对象的数据属性和访问器属性
数据属性: 它包含的是一个数据值的位置,在这可以对数据值进行读写
数据属性的四个描述符:
| 含义 |
---|
configurable | 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为true | enumerable | 表示能否通过for-in循环返回属性,默认为true | writable | 表示能否修改属性的值,默认为true | value | 包含该属性的数据值,默认为undefined |
访问器属性: 这个属性不包含数据值,包含的是一对get和set方法,在读写访问器属性时,就是通过这两个方法来进行操作处理的。
访问器属性的四个描述符:
| 含义 |
---|
configurable | 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为false | enumerable | 表示能否通过for-in循环返回属性,默认为false | get | 在读取属性时调用的函数,默认值为undefined | set | 在写入属性时调用的函数,默认值为undefined |
二、vue3.0数据响应
vue3.0的响应式和vue2.0响应式原理类似,都是在get中收集依赖,在set中通知依赖更新视图,但vue3.0使用了es6新增的proxy来代替Object.defineProperty()
proxy相对于Object.defineProperty()的好处:
- Object.defineProperty需要指定对象和属性,对于多层嵌套的对象需要递归监听,Proxy可以直接监听整个对象,不需要递归;
- Object.defineProperty的get方法没有传入参数,如果我们需要返回原值,需要在外部缓存一遍之前的值,Proxy的get方法会传入对象和属性,可以直接在函数内部操作,不需要外部变量;
- set方法也有类似的问题,Object.defineProperty的set方法传入参数只有newValue,也需要手动将newValue赋给外部变量,Proxy的set也会传入对象和属性,可以直接在函数内部操作;
- new Proxy()会返回一个新对象,不会污染源原对象
- Proxy可以监听数组,不用单独处理数组
proxy劣势: vue3.0将放弃对低版本浏览器的兼容(兼容版本ie11以上)
这样上边的observe方法就可以优化成:
function observer () {
var self = this;
data = new Proxy(data, {
get: function(target, key){
dep.depend()
return target[key]
},
set: function(target, key, newValue){
target[key] = newValue;
dep.notify()
}
});
}
补充:
- new proxy(target, handler)中的handler不仅可以在get和set时触发,还可以在下列方法时触发
getPrototypeOf() setPrototypeOf() isExtensible() preventExtensions() getOwnPropertyDescriptor() defineProperty() has() get() set() deleteProperty() ownKeys() apply() construct() - 可以利用proxy的特性,优化表单的校验
|