Object.defineProperty(obj, prop, descriptor)
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
obj——要定义属性的对象
prop——要定义或修改的属性的名称或Symbol
descriptor——对象,要定义或修改的属性描述符
// descriptor
{
value: undefined, // 属性的值
get: undefined, // 获取属性值时触发的方法
set: undefined, // 设置属性值时触发的方法
writable: false, // 属性值是否可修改,false不可改
enumerable: false, // 属性是否可以用for...in 和 Object.keys()枚举
configurable: false // 该属性是否可以用delete删除,false不可删除,为false时也不能再修改该参数
}
通过赋值 *** 作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。
而默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
示例:
const a = {b : 1}
console.log(Object.getOwnPropertyDescriptor(a, 'b'))
// {value: 1, writable: true, enumerable: true, configurable: true}
Object.defineProperty(a, 'c', {value: '2'})
console.log(Object.getOwnPropertyDescriptor(a, 'c'))
// {value: '2', writable: false, enumerable: false, configurable: false}
a.c = 3
console.log(a.c)
// 2
Object.defineProperty(a, 'c', {value: '4'})
console.log(a.c)
// error: Uncaught TypeError: Cannot redefine property: c
1.2 set和get
// 模拟vue响应式过程
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) { // 递归设置set和get
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
// render()
dep.notify() // 这里进行virtualDom更新,通知需要更新的组件render
}
});
dep是Vue负责管理依赖的一个类
补充: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
const vm = new Vue({
data: {
a: 1
}
})
// vm.a是响应式的
vm.b = 2
// vm.b是非响应式的
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'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
const o = Object.create(Object.prototype, {
foo: { // foo会成为所创建对象的数据属性
writable:true,
configurable:true,
value: "hello"
},
bar: { // bar会成为所创建对象的访问器属性
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
console.log(o) // {foo: 'hello'}
vue中的装饰者模式
const arraypro = Array.prototype // 获取Array的原型
const arrob = Object.create(arraypro) // 用Array的原型创建一个新对象,arrob.__proto__ === arraypro,免得污染原生Array;
const arr=['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 需要重写的方法
arr.forEach(function(method) {
arrob[method] = function () {
arraypro[method].apply(this, arguments) // 重写时先调用原生方法
dep.notify() // 并且同时更新
}
})
// 对于用户定义的数组,手动将数组的__proto__指向我们修改过的原型
const a = [1, 2, 3]
a.__proto__ = arrob
上面对于新对象arrob的方法,我们是直接赋值的,这样会有一个问题,就是用户可能会不小心改掉我们的对象,所以我们可以用到我们前面讲到的Object.defineProperty来规避这个问题,我们创建一个公用方法def专门来设置不能修改值的属性
function def (obj, key, value) {
Object.defineProperty(obj, key, {
// 这里我们没有指定writeable,默认为false,即不可修改
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的响应式和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;
// render()
dep.notify() // 这里进行virtualDom更新,通知需要更新的组件render
}
});
}
补充:
new proxy(target, handler)中的handler不仅可以在get和set时触发,还可以在下列方法时触发getPrototypeOf()
setPrototypeOf()
isExtensible()
preventExtensions()
getOwnPropertyDescriptor()
defineProperty()
has()
get()
set()
deleteProperty()
ownKeys()
apply()
construct()可以利用proxy的特性,优化表单的校验
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)