New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
如何理解Object.defineProperty()? #215
Comments
初识Object.defineProperty()静态方法Object.defineProperty()会直接在一个对象上定义一个新的属性,或者修改对象上已经存在的属性,然后返回这个对象。 const obj = {};
Object.defineProperty(obj, 'prop', {
value: 42,
writable: true
});
console.log(obj); // {prop: 42}
obj.prop = 43; // {prop: 43} 语法Object.defineProperty(obj, prop, descriptor) 参数
返回值返回传递进函数的对象。 |
Object.defineProperty()概览
基本知识点
data和accessor两种描述符对象的属性descriptor描述符主要有两种:data descriptor和accessor descriptor。 data descriptor数据描述符指的是value,writable,它可能是可写的,也可能是不可写的。 accessor descriptor权限描述符指的是通过getter-setter函数 描述符必须是data, accessor之一,不能同时具有两种特性下面的代码会报错的原因破案了:只能是data,accessor 之一。 Object.defineProperty({this, 'a', {
value: 'a', // data descriptor
get(){
// access descriptor
}
})
// `Invalid property descriptor.Cannot both specify accessors and a value or writable attribue.` 如何区分data descriptor和accessor descriptor?data accessor特有的key为value和writable。 // 典型的data descriptor
Object.defineProperty({this, 'a', {
value: 'a',
writable: false
})
// 典型的accessor descriptor
Object.defineProperty({this, 'a', {
get(){ ... }
set(){ ... }
}) descriptor key概览默认情况下是通过Object.defineProperty()定义属性的。 共享descriptor key概览configurable
为什么configurable设置为false时要这样设计?
这是因为get(), set(), enumerable, configurable是权限相关的属性,为了避免引起不必要的bug。 enumerable
data descriptor key概览value
writable
accessor descriptor key概览get
set
牢记属性不仅仅是descriptor自己的属性,还要考虑继承属性为确保保留了这些默认值:
三个很基础但是很好的例子默认descriptor:不可写,不可枚举,不可配置var obj = {};
var descriptor = Object.create(null); // no inherited properties
descriptor.value = 'static';
// not enumerable, not configurable, not writable as defaults
Object.defineProperty(obj, 'key', descriptor);
// being explicit
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
}); 重用同一对象记忆上一次的value值function withValue(value) {
var d = withValue.d || (
// 记忆住上一次的值
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: value
}
);
// 避免重复赋值
if (d.value !== value) d.value = value;
return d;
}
Object.defineProperty(obj, 'key', withValue('static')); 冻结Object.prototypeObject.freeze(Object.prototype) |
Object.defineProperty()详解创建一个property属性如果在对象上不存在的话,Object.defineProperty()会创建一个新的属性。 // 创建对象
var o = {};
// 定义属性a并且传入data descriptor
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true,
})
// 定义属性b并且传入accessor descriptor
// 伪造value(好处是更细粒度的value控制):外部变量和get()
// 伪造writable(好处是更细粒度的writable控制):外部变量和set()
// 在这个例子中,o.b的值与bValue做了强关联。bValue是什么值,o.b就是什么值。除非o.b被重新定义
var bValue = 38;
Object.defineProperty(o, 'b', {
get() { return bValue },
set(newValue) { bValue = newVlaue },
enumerable: true,
configurable: true,
})
// 不可以同时混合定义两者
Object.defineProperty(o, 'conflict', {
value: 'a',
get() { return 'a' }
})
// 报错:Cannot both specify accessors and a value or writable
// 重新解读报错:Cannot both specify accessors descriptor and data descriptor(a value or writable) 修改一个property
Writable attribute当writable设置为false时,属性是不可写的,意味着无法重新赋值。
// 非严格模式不会报错,只是赋值失败
var o = {};
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // logs 37
o.a = 25; // 不会报错
// (只会在strict mode报错,或者值没改变也不会报错)
console.log(o.a); // logs 37. 重新赋值没有生效
// 严格模式会报错
// strict mode
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // 抛出Cannot assign to read only property 'b' of object '#<Object>'
return o.b; // 2
}()); Enumerable attribute
知识点
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
Object.defineProperty(o, 'c', {
value: 3, // enumerable默认为false
});
o.d = 4; // enumerable默认为true
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
Object.defineProperty(o, Symbol.for('f'), {
value: 6,
enumerable: false
}); 在for...in中如何表现?只有'a'和'd'打印了出来。 for (var i in o) {
console.log(i); // 'a','d'
} 在Object.keys()中如何表现?只有'a'和'd'被搜集到。 Object.keys(o); // ['a', 'd'] 在展开操作符...中如何表现?enumerable为true的都能被解构出来,包括Symbol。 var p = { ...o }
p.a // 1
p.b // undefined
p.c // undefined
p.d // 4
p[Symbol.for('e')] // 5
p[Symbol.for('f')] // undefined 如何检测属性是否可以枚举?可以用obj.propertyIsEnumerable(prop)检测属性是否可遍历 o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
o.propertyIsEnumerable(Symbol.for('e')); // true
o.propertyIsEnumerable(Symbol.for('f')); // false Configurable attributeconfigurable属性控制属性是否可以被修改(除value和writable外),或者属性被删除。 var o = {};
Object.defineProperty(o, 'a', {
get() { return 1; },
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
enumerable: true
}); // throws a TypeError
Object.defineProperty(o, 'a', {
set() {}
}); // throws a TypeError (set初始值为undefined)
Object.defineProperty(o, 'a', {
get() { return 1; }
}); // throws a TypeError
// (即使set没有变化)
Object.defineProperty(o, 'a', {
value: 12
}); // throws a TypeError // ('value' can be changed when 'configurable' is false but not in this case due to 'get' accessor)
console.log(o.a); // logs 1
delete o.a; // 不能删除
console.log(o.a); // logs 1 增加属性和默认值属性的默认值很值得思考一下。 两种赋初始值方式的区别如下
通过点操作符定义的属性通过点操作符定义的属性等价于Object.defineProperty的data descriptor和共享descriptor为true。 var o = {};
o.a = 1;
// 等价于
Object.defineProperty(o, 'a', {
value: 1,
writable: true,
configurable: true,
enumerable: true
}); 通过Object.defineProperty只指定value的属性Object.defineProperty(o, 'a', { value: 1 });
// 等价于
Object.defineProperty(o, 'a', {
value: 1,
writable: false,
configurable: false,
enumerable: false
}); 自定义setter和getter下面的例子展示了如何实现一个自存档的对象。
常见的一种gettter,setter使用方式function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get(){
console.log('get!');
return temperature;
},
set(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function(){ return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{val: 11}, {vale: 13}] 这个getter和setter总是返回相同的值var pattern = {
get() {
return 'I always return this string, ' +
'whatever you have assigned';
},
set() {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
console.log(instance.myproperty);
// I always return this string, whatever you have assigned
console.log(instance.myname); // this is my name string properties的继承
主要为以下3个问题:
Object.defineProperty与prototype的问题这个例子展示了继承带来的问题: function myclass() {
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1 如何解决 Object.defineProperty与prototype的问题如何解决这个问题呢? function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this._x;
},
set(x) {
this._x = x; // 用this._x来存储value
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1 Object.defineProperty的writable和__proto__下面的例子,点操作符赋值的属性可写,但是继承的myclass.prototype的初始值不会发生更改;不可写的属性不可写。 function myclass() {
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1
|
如何获取属性的descriptor?Object.getOwnPropertyDescriptor(obj,prop) 使用示例: var o = {};
Object.defineProperty(o, 'a', { value: 1 });
Object.getOwnPropertyDescriptor(o,'a')
// {
// configurable: false
// enumerable: false
// value: 1
// writable: false
// } |
console.log(a+a+a); // 'abc'题解/*
console.log(a + a + a); // 打印'abc'
*/
/**
* 解法1: Object.defineProperty() 外部变量
*/
let value = "a";
Object.defineProperty(this, "a", {
get() {
let result = value;
if (value === "a") {
value = "b";
} else if (value === "b") {
value = "c";
}
return result;
},
});
console.log(a + a + a);
/**
* 解法1(优化版):Object.defineProperty() 内部变量
*/
Object.defineProperty(this, "a", {
get() {
this._v = this._v || "a";
if (this._v === "a") {
this._v = "b";
return "a";
} else if (this._v === "b") {
this._v = "c";
return "b";
} else {
return this._v;
}
},
});
console.log(a + a + a);
/**
* 解法2: Object.prototpye.valueOf()
*/
let index = 0;
let a = {
value: "a",
valueOf() {
return ["a", "b", "c"][index++];
},
};
console.log(a + a + a);
/**
* 解法3:charCodeAt,charFromCode
*/
let code = "a".charCodeAt(0);
let count = 0;
Object.defineProperty(this, "a", {
get() {
let char = String.fromCharCode(code + count);
count++;
return char;
},
});
console.log(a + a + a); // 'abc'
/**
* 解法3(优化版一):内部变量this._count和_code
*/
Object.defineProperty(this, "a", {
get() {
let _code = "a".charCodeAt(0);
this._count = this._count || 0;
let char = String.fromCharCode(_code + this._count);
this._count++;
return char;
},
});
console.log(a + a + a); // 'abc'
/**
* 解法3(优化版二):内部变量this._code
*/
Object.defineProperty(this, "a", {
get() {
this._code = this._code || "a".charCodeAt(0);
let char = String.fromCharCode(this._code);
this._code++;
return char;
},
});
console.log(a + a + a); // 'abc'
/*
题目扩展: 打印`a...z`
a+a+a; //'abc'
a+a+a+a; //'abcd'
*/
/**
* charCodeAt,charFromCode
*/
let code = "a".charCodeAt(0);
let count = 0;
Object.defineProperty(this, "a", {
get() {
let char = String.fromCharCode(code + count);
if (count >= 26) {
return "";
}
count++;
return char;
},
});
// 打印‘abc’
console.log(a + a + a); // 'abc'
// 打印‘abcd’
let code = "a".charCodeAt(0);
let count = 0;
// {...定义a...}
console.log(a + a + a); // 'abcd'
// 打印‘abcdefghijklmnopqrstuvwxyz’
let code = "a".charCodeAt(0);
let count = 0;
// {...定义a...}
let str = "";
for (let i = 0; i < 27; i++) {
str += a;
}
console.log(str); // "abcdefghijklmnopqrstuvwxyz"
/*
题目扩展(优化版): 打印`a...z`
a+a+a; //'abc'
a+a+a+a; //'abcd'
*/
Object.defineProperty(this, "a", {
get() {
this._code = this._code || "a".charCodeAt(0);
let char = String.fromCharCode(this._code);
if (this._code >= "a".charCodeAt(0) + 26) {
return "";
}
this._code++;
return char;
},
});
// 打印‘abc’
console.log(a + a + a); // 'abc' |
几乎所有使用Vue的开发者都知道,Vue的双向绑定是通过Object.defineProperty()实现的,也知道在getter中收集依赖,在setter中通知更新。
那么除了知道getter和setter之外,Object.defineProperty()还有哪些值得我们去注意的地方呢?是不是有很多细节的东西不懂呢?
你可能会说,除了getter和setter之外,Object.defineProperty()还有value,writable,enumerable,configurable。
那么问题来了?
data descriptor、accessor descriptor、shared descriptor
是什么?如果看了上面这些问题一脸懵逼,不要惊慌,我们先来看一道非常直观易懂的题目:
题目看完了,带着问题开始阅读下面的内容吧。
如果能耐心看完的话对于个人的前端技术提升会非常大。
往近了说,不出意外上面这些问题全部可以迎刃而解,对于a+a+a题目的题解也会理解更加透彻。
往远了说,可以去看懂Vue源码相关的实现,以及看懂任何使用到Object.defineProperty()这个API的库的源码实现,甚至最后自己写个小轮子。
console.log(a+a+a); // 'abc'
题解a...z
a...z
The text was updated successfully, but these errors were encountered: