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.14for (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 datavar 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);}
}