只学习用法
// Object.defineProperty()
// 参数:obj prop descriptor
// 需要定义的对象 属性名称 描述符(配置集合)
function defineProperty() {
var _obj = {
}
Object.defineProperty(_obj, 'a', {
value: 1
})
return _obj
}
var obj = defineProperty()
console.log(obj)
这就是使用方法,也可以如下
function defineProperty() {
var _obj = {
}
Object.defineProperties(_obj, {
a: {
value: 1
},
b: {
value: 2
}
})
return _obj
}
var obj = defineProperty()
console.log(obj)
然后你会发现
obj.a = 5
console.log(obj)//属性值不可修改
for(var k in obj){
console.log(K + ':' + obj[k])
}//属性不可枚举
delete obj.a
console.log(obj)//属性不可删除
实际上
// 实际上
function defineProperty() {
var _obj = {
}
Object.defineProperties(_obj, {
a: {
value: 1,
writable:false,//默认为false,不可修改,所以改为true,才能修改
enumerable:false,//默认为false,不可枚举,所以改为true,才能枚举
configurable:false//默认为false,不可删除,所以改为true,才能删除
},
b: {
value: 2
}
})
return _obj
}
在一个,如果设置了value或者writable中的任意一个,那么就不能设置get和set,否则会报错
function defineProperty() {
var _obj = {
},a=2;
// 每个属性定义的时候 会内置有 getter setter
Object.defineProperties(_obj, {
a: {
get(){
console.log('get触发')
return a
},
set(newVal){
console.log('set触发')
a = newVal;
}
}
})
return _obj
}
var obj = defineProperty()
obj.a = 1 //触发set
console.log(obj.a)//触发get 打印1 然后才是打印obj.a
如果是数组的话
function DataArr (){
var _val = null,_arr = [];
Object.defineProperty(this,'val',{
get(){
return _val
},
set(newVal){
_val =newVal
_arr.push({
val:_val})
console.log('set')
}
})
this.getArr = function(){
return _arr
}
}
var dataArr = new DataArr()
dataArr.val = 123
dataArr.val = 234
console.log(dataArr.getArr())
在Vue2.x中,数组中的变化没有引起视图的更新是一个缺陷。
所以通过索引或者length又或者pop/push等数组api来改变数组触发defineProperty的set方法引起视图更新是不可行的。
解决方法一般有以下两种
<body>
<div id="app">
{
{
colors}}
</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
colors: ['red', 'green', 'blue']
}
}
})
// 比如我现在要改变第0项
vm.colors[0] = 'black'//发现无效
// 只能通过
vm.$set(vm.colors, 0, 'black')
// 第一项是数组,第二项是索引,第三项是值 来重新设置数组,并更新视图
// 或者
vm.colors.splice(0, 1, 'black')
// 因为vue是劫持了splice方法的(实际上是大量重写的数组的API) 使用splice操作数组以后,视图会更新
// 又比如 我现在想要改变colors的长度,删除最后一项
vm.colors.length--
// 无效 视图不更新
// 于是使用
vm.$delete(vm.colors, 1);//删除1项
// 或者
vm.colors.splice(2)
</script>
</body>
Vue2.x的响应式原理,重点在于vm和diff算法,使用Object.defineProperty只是实现数据劫持的一种手段,在Vue3.0中,就换成使用ES6 的new Proxy来实现了。
let obj = new Proxy(target, handler)
// target 目标对象 你需要进行处理的对象
// handler 容器 里面有若干可以处理对象属性的方法
从这里可以看出proxy和defineProperty的区别,defineProperty是定义一个对象和其中的属性,而proxy是传入目标对象,传入的对象中可能已经有一些属性或者方法了。
下面看
var target = {
a: 1,
b: 2
}
let proxy = new Proxy(target, {
get(target, prop) {
console.log('prop val ' + target[prop])
},
set(target, prop, value) {
console.log('set ' + JSON.stringify(target) + ' 中的 ' + prop + ' = ' + value)
target[prop] = value
}
})
console.log(proxy.a) //这里undefined 是因为get没有返回值 如果直接return target[prop] 就可以拿到proxy.a的值了
console.log(target.a)
proxy.b = 3
console.log(target.b)//proxy相当于target的代理,代理的属性改变,那么原对象的值也会改变
那么如果是数组呢?
let arr = [{
name: '小明',
age: 18
},
{
name: '小红',
age: 17
},
{
name: '小黄',
age: 15
},
{
name: '小仔',
age: 16
},
{
name: '小小',
age: 10
},
{
name: '小呆',
age: 5
}
]
let persons = new Proxy(arr, {
get(arr, prop) {
return arr[prop]
},
set(arr, prop, value) {
arr[prop] = value
}
})
console.log(person[3])
person[1] = {
name: '小张',
age: 20
}
console.log(persons, arr)
可以看到proxy出来的persons实际上还是一个对象,他的target是arr,handler中是我们定义的get和set方法。
那么,如果是函数呢?
let fn = function () {
console.log('fn')
}
fn.a = 123
let newFn = new Proxy(fn, {
get(fn, prop) {
return fn[prop]
}
})
console.log(newFn.a)
可以发现,引用类型的数据都是可以进行代理。
下面尝试用Object.defineProperty来重写Proxy
// 重写
function MyProxy(target, handler) {
let _target = deepClone(target);
Object.keys(_target).forEach((key) => {
Object.defineProperty(_target, key, {
get() {
return handler.get && handler.get(target, key)
},
set(newVal) {
handler.set && handler.set(target, key, newVal)
}
})
})
return _target
function deepClone(org, tar) {
var tar,
toStr = Object.prototype.toString,
arrType = '[object Array]';
tar = toStr.call(org) === arrType ? [] : {
}
for (var key in org) {
if (org.hasOwnProperty(key)) {
if (typeof (org[key]) === 'object' && org[key] !== null) {
tar[key] = deepClone(org[key], tar[key])
} else {
tar[key] = org[key]
}
}
}
return tar
}
}
let target = [{
b:2},{
a:1}]
let proxy = new MyProxy(target, {
get(target, prop) {
return 'get:' + prop + '=' + JSON.stringify(target[prop])
// return target[prop]
},
set(target, prop, value) {
target[prop] = value
console.log('set:' + prop + '=' + value)
}
})
console.log(proxy[0],proxy[1].a)
proxy[0] = 3
下面在看一下另外几个方法
var target = {
a: 1,
b: 2
}
let proxy = new Proxy(target, {
get(target, prop) {
console.log('prop val ' + target[prop])
},
set(target, prop, value) {
console.log('set ' + JSON.stringify(target) + ' 中的 ' + prop + ' = ' + value)
target[prop] = value
},
has (target,prop){
console.log(target[prop])
},
deleteProperty(target,prop){
delete target[prop]
console.log('delete触发了')
}
})
console.log('a' in proxy)//false 实际上proxy下面只有[[handler]] [[target]] [[isRevoked]]三个属性,而a在[[target]]中
delete proxy.b
console.log(proxy) //你会发现b没有了,且出发了deleteProperty函数
再讲一下Reflect
var target = {
a: 1,
b: 2
}
let proxy = new Proxy(target, {
get(target, prop) {
// 直接return target[prop],其实也不是特别好,我们希望函数式编程来返回结果
return Reflect.get(target, prop)
// Reflect实际上是一个对象方法的容器,(对象底层有14中方法来操作对象),通过它提供的get API 我们能够实现 相同的效果
},
set(target, prop, value) {
target[prop] = value
// 同理
Reflect.set(target, prop, value)
// 这种函数方法 是有返回值的, const isOk = Reflect.set(target, prop, value) 可以打印看一下结果
}
})
// 在Object上有很多很多方法,这其实并不合理,于是后来整理了部分API放入了Reflect中,Reflect则相当于作为一个公共方法集合的容器,来对这些方法进行统一的管理
// 在一个 Object中很多操作和方法 都会抛出异常,你需要用try catch来捕获异常,或者是使用各种关键字如 in 等等来驱动,而Reflect的方法,调用以后则有返回值,这是一种更合理的编程方式
// Reflect 是一个全局对象,里面全部是静态方法,你可以全局直接使用,而不用实例化。就像Math一样
最后说一下Object底层的14中方法
// ECMAScript 对 对象操作 有14中方法
var obj = {
a: 1,
b: 2
}
// 1 获取原型 [[GetPrototyOf]]
var proto = Object.getPrototypeOf(obj)
console.log(proto)
// 或者
console.log(obj.__proto__)
console.log(obj.prototype)
// 实际上三种方法都是可以的
// 2 设置原型 [[SetPrototypeOf]]
Object.setPrototypeOf(obj, {
c:3,d:4})
// 同理只用__proto__ 或者prototype直接赋值也是可以的
console.log(obj)
// 3 获取对象的可扩展性 [[IsExtensible]]
var extensible = Object.isExtensible(obj)
console.log(extensible)
Object.freeze(obj)
var extensible2 = Object.isExtensible(obj)
console.log(extensible2)
// 4 封闭对象
Object.seal(obj)
obj.c = 3
console.log(obj)//不可新增,不可删除,但可写
// 5 冻结对象
Object.freeze(obj) //然后只可读,不可新增,不可删除,不可写,但是可以枚举
// 4 获取自有属性 [[GetOwnProperty]]
Object.setPrototypeOf(obj,{
c:3,d:4})
console.log(Object.getOwnPropertyDescriptor(obj))
console.log(Object.getOwnPropertyNames(obj))
console.log(Object.getOwnPropertySymbols(obj))
// 5 禁止扩展对象 [[PreventExtensions]]
Object.preventExtensions(obj)
obj.c = 3
console.log(obj)
// 6 拦截对象操作 [[DefineOwnProperty]]
Object.defineProperty()
// 7 判断属性是否是自身属性 [[HasProperty]]
console.log(obj.hasOwnProperty('a'))
// 8 [[GET]]
console.log('a' in obj)
console.log(obj.a)
// 9 [[SET]]
obj.a=3
obj['b'] = 4
console.log(obj)
// 10 [[Delete]]
delete obj.a
console.log(obj)
// 11 [[Enumerable]]
// for... in ...
// 12 获取键集合 [[OwnPropertyKeys]]
console.log(Object.keys(obj))
// 13 调用函数
function test(){
}
test()
//call / apply属于函数调用,相当于在Object分支下面,bind不是
obj.test =function(){
}
obj.test()
// 14 实例化过程 new
function Test(){
}
new Test()
这次就总结到这里。我也是看b站小野的视频学习的,有些东西讲的是通俗易懂。但有的代码示例什么,建议还是手敲一遍,有的有逻辑错误,自己发现改正并实现对于你的学习有更好的帮助。
加油学习。