您现在的位置是:首页 >技术杂谈 >object.defineproperty方法解析/自己实现一个object.defineProperty/Vue3中的Proxy解析和实现网站首页技术杂谈
object.defineproperty方法解析/自己实现一个object.defineProperty/Vue3中的Proxy解析和实现
简介object.defineproperty方法解析/自己实现一个object.defineProperty/Vue3中的Proxy解析和实现
object.defineproperty
首先说明一下 object.defineproperty不是Vue的方法,而是ES5中新增的方法。该方法可以用于定义对象属性的特性,包括可写性、可枚举、可配置性等。
该方法的代码如下:
Object.defineProperty(obj, prop, descriptor)
其中,obj
表示要操作的目标对象,prop
要定义或修改的属性名,descriptor
是一个描述符对象,用来指定属性的特性。descriptor
对象可以包含一下属性:
value
:设置属性值,默认为undefined。writable
:设置属性是否可写(即是否可以被赋值),默认为false。enumerable
:设置属性是否可枚举(即是否可以通过for...in
遍历循环得到),默认为false。configurable
:设置是否可配置(即是否可以使用delete属性删除或重新定义),默认为false。get
:定义属性的读取器函数,该函数会在读取属性值时被调用。set
:定义属性的设置器函数,该函数会在设置属性值时被调用。
比如如下我们创建一个对象:
const obj = {};
Object.defineProperty(obj, 'PI', {
value: 3.14,
writable: false,
enumerable: true,
configurable: false
});
console.log(obj.PI); // 输出 3.14
obj.PI = 3.14159; // 不允许修改
console.log(obj.PI); // 输出 3.14
for (const key in obj) {
console.log(key); // 输出 'PI'
}
delete obj.PI; // 不允许删除
console.log(obj.PI); // 输出 3.14
需要注意的是,Object.defineProperty() 方法只能定义或修改单个属性。这也就是说如果有多个的话,必然在Vue的源码中有一个循环遍历。一下为一段源码看一下即可 循环为while循环
function initData(vm) {
var data = vm.$options.data;
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
// --------------------------------------在这 在这--------------------------------
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn$2("Method "".concat(key, "" has already been defined as a data property."), vm);
}
}
if (props && hasOwn(props, key)) {
warn$2("The data property "".concat(key, "" is already declared as a prop. ") +
"Use prop default value instead.", vm);
}
else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
// observe data
var ob = observe(data);
ob && ob.vmCount++;
}
// 给大家稍微解释一下这段源码内容
0、 data = vm._data = isFunction(data) ? getData(data, vm) : data || {};判断传过来的是 对象还是 函数 因为我们data本身有
data:{} 和 data(){return{}}两种方法!
1、isReserved(key) 用于判断当前字段是否为 _ 或者 $ 开头,以 _ 或 $ 开头的属性 不会 被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突。可以理解为只可读不可用,是vue源码中自己使用的。
2、 proxy(vm, "_data", key);
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
可以看出是进行了代理。简单了解一下,如果想学可以去看看。
那我们怎么按照Vue的思想自己实现一个数据劫持那?
- 明确方向:Vue本身通过的是 new 进行了实例化。
- 属性:含有一个内部可操作属性this._data属性。
- 添加方法:自定义 get 和 set 方法。
- 遍历:循环遍历添加属性。
let vm = new Vue({
data:{
name:"屈小康"
}
})
// 定义class
class Vue{
// 为什么叫 options 因为源码就是这样写的
constructor(options){
this._data = options.data;
this.$options = options;
this.initData();
}
initData(){
let data = this._data;
// 只针对于单层 data:{name:"屈小康" }
Object.keys(data).forEach(item=>{
Object.defineProperty(this,item,{
enmuerable:true,
configurable:true,
get(){
return data[item];
},
set(value){
data[item] = value;
}
})
})
}
}
那如果是 data:{name:"屈小康",person:{age:18}}
怎么办? 显然我们需要递归的方法。
let vm = new Vue({
data:{
name:"屈小康"
}
})
// 定义class
class Vue{
// 为什么叫 options 因为源码就是这样写的
constructor(options){
this._data = options.data;
this.$options = options;
this.initData();
}
initData(){
let data = this._data;
// 那如果是 data:{name:"屈小康",person:{age:18}}怎么办? 显然我们需要递归的方法。
observe(data);
}
}
// 添加 get 和 set 方法
function defineReactive(data,key,value){
object(data[item]) //递归调用
Object.defineProperty(data,key,{
enumerable:trye,
configurable:true,
get(){
return value
},
set(val){
data[key] = val;
}
})
}
// 定义监听 class
class Observer{
construct(data){
this.work(data)
}
work(data){
Object.keys(data).forEach(item=>{
defineReactive(data,item,data[item]);
})
}
}
// 判断是否需要进行监听
function observe(data){
function observe(data) {
let type = Object.prototype.toString.call(data);
if (type !== '[object Object]' && type !== '[object Array]') {
return;
}
new Observer(data);
}
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。