前言
当我们了解一个方法时,建议从以下几个维度着手
1、方法的定义
2、了解方法的使用场景
3、在场景中解决什么问题
带着这样的好奇心,去学习、研究,我们可能更好的理解、掌握、运用它
复制代码
定义
此方法,可以直接在对象上定义一个新属性
,或者修改
一个对象的现有属性, 并返回这个对象。
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
要在其上定义属性的对象
复制代码
prop
需要定义或修改的属性名称
复制代码
descriptor
需要定义或修改的属性描述符
复制代码
返回
被传递给函数的对象
复制代码
属性描述符
- 数据描述符
是一个具有值的属性,该值可能是可写的,也可能不是可写的。
复制代码
- 存取描述符
是由getter-setter函数对描述的属性。
复制代码
二者不可同时存在
参数列表
参数名 | 默认值 | 描述 | |
---|---|---|---|
共存参数 | |||
configurable | false | 为true时,对应的属性可以被修改个删除 | |
enumerable | false | 为true时,对应的属性才能够出现在对象的枚举属性中 | |
数据描述符 | |||
value | undefined | 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 | |
writable | false | 为true时,对应的属性的value才能被赋值运算符改变。 | |
存取描述符 | |||
get | undefined | 给对应属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。 | |
set | undefined | 给对应属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。 |
描述符可同时具有的键值
configurable enumerable value writable get set 数据描述符 Yes Yes Yes Yes No No 存取描述符 Yes Yes No No Yes Yes数据描述符
- 明确指定所有的选项【避免来自继承的属性描述被覆盖】
显式
// 冻结 Object.prototype,明确指定所有的选项
var person={};
person.name = 'taro' // 下面执行后被覆盖
Object.defineProperty(person,"name",{
configurable: true,
enumerable: true,
writable:true,
value:"zhangjinlong"
});
console.log(person.name);//zhangjinlong
person.name="dragon";
console.log(person.name);//dragon 
复制代码
- 阻断继承【避免来自继承的属性描述被覆盖】
注:
如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。
当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字
段的默认值都是false。value,get和set字段的默认值为undefined。一个没
有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数
据描述符。
复制代码
var person={};
var discriptor = Object.create(null) // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
discriptor.value = 'zhangjinlong'
Object.defineProperty(person,"name",discriptor);
console.log(person.name);// zhangjinlong
person.name="dragon";
console.log(person.name);//zhangjinlong 
复制代码
- 属性描述为空对象
var person={};
// 默认没有 enumerable,没有 configurable,没有 writable
discriptor.value = 'zhangjinlong'
Object.defineProperty(person,"name",{});
console.log(person.name);// undefined
person.name="dragon";
console.log(person.name);//undefined
复制代码
存取描述符
var obj = {}; // 创建一个新对象
// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(obj, "key", {
value : 37,
writable : true,
enumerable : true,
configurable : true
});
// 对象obj拥有了属性key,值为37
console.log(obj.key)
// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(obj, "name", {
get : function(){
return bValue;
},
set : function(newValue){
bValue = newValue;
},
enumerable : true,
configurable : true
});
obj.name = 'zhangjinlong';
// 对象obj拥有了属性name,值为zhangjinlong
obj.name = 'dragon';
// 对象obj调用属性的set方法,值为dragon 【数据描述符】
obj.key = 38;
// 对象obj调用属性的value,值改为38 【存取描述符】
复制代码
数据描述符和存取描述符同时用的时候(error)
var o = {}
// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
value: 0x9f91102,
get: function() {
return 0xdeadbeef;
}
});
// err Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
at Function.defineProperty (<anonymous>)
at <anonymous>:3:8
复制代码
使用场景
- 在vue框架下的使用
先看一段代码
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
复制代码
这算是vue框架最核心的代码了,对比上面的概念,不难理解discriptor是一个数据属性符
再看另一段,vue响应式代码,有删减
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
}
})
复制代码
上面这段代码,算是vue架构最核心的东西了,不难看出,这里是用到了descriptor的存取属性符
看到这里,可能会有疑问,vue用Object.defineProperty到底解决了什么问题,是什么给尤雨溪
【vue创始人】启发,开发出MVVM模式的框架
解决什么问题
- 响应式编程,实现双向绑定(MVVM)
看个例子:
var vm = new Vue({
// view
template: `<div><span>{{name}}</span></div>`,
// state
data: {
name: 'zhangjinlong'
},
// actions
mounted: function () {
this.setName()
},
// actions
methods: {
setName () {
this.name = 'dragon'
}
}
})
复制代码
看下上面代码运行的逻辑,一一拆解:
第一步:初始化state,属性name被赋值为zhangjinlong
// state
data: {name: 'zhangjinlong'}
复制代码
// 初始化属性name,并赋予get和set方法 如图:
第二步:声明式变量name映射到模板,执行存取描述符
的get方法获取值
// view
template: `<div><span>{{name}}</span></div>`
复制代码
用VNode实例化node节点,把属性值以形参形式传入VNode方法,text指向属性值(这里是zhangjinlong)
根据node节点信息创建html对象和元素【这里创建的是虚拟dom】 返回创建的虚拟dom(html对象和元素)(vm._c指向创建的对象),最终呈现在屏幕上 第三步:action操作,改变声明式变量name,执行存取描述符
的set方法进行赋值
// actions
methods: {setName () { this.name = 'dragon' }}}
复制代码
执行setName()方法,this.name指向字符串dragon,此时执行存取描述符
的set方法,传入的新值 "dragon"与原值"zhangjinlong"进行比对
1、如果新值与原值相等直接返回,不做赋值
2、如果新值与原值不相等,进行赋值操作
3、借助代理proxy完成,代理赋值后续处理
复制代码
进行全二叉树遍历,发现有不同,就覆盖原node对象.更新虚拟dom,最终渲染到界面上。
总结:
1、用defineProperty解决了,双向数据流传输的问题
2、vue框架根据这一特点,加上依赖收集(dep)、依赖触发(dep),观察者(Observer),侦测(watcher),Proxy(代理)实现了双向绑定,响应式编程,解决了传统的单项数据流以及复杂dom操作的痛点
3、上面亲测,可能不排除有不到位的或不妥的地方,希望亲们能批评指正
复制代码
上面只是简单的走了一下流程,下一篇我会着重分析vue源码,更透彻的理解这个框架,敬请期待。。。