继续记录对象的一些特性
属性特征
JS中可以对属性的访问特性进行控制
查看对象的属性特征
使用 Object.getOwnPropertyDescriptor
或者 Object.getOwnPropertyDescriptor
查看属性的一些特征
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| let foo = { name: "foo", age: 18 } console.log(JSON.stringify(Object.getOwnPropertyDescriptor(foo, "name"), null, 2));
console.log(JSON.stringify(Object.getOwnPropertyDescriptors(foo), null, 2));
|
属性的四个特征
属性 |
意义 |
默认值 |
value |
当前属性的值 |
undefined |
writable |
是否可被修改 |
false |
enumerable |
是否可被for in 遍历 |
false |
configurable |
是否可被删除 |
false |
上面三个特征默认值为 false
是对于 defineProperty
来说的,如果只是使用点来定义的话,全部都是true。
设置特征
在js面向对象笔记-2中有说明
禁止添加
Object.preventExtensions
禁止向对象添加属性
1 2 3 4 5
| let foo = { name: "foo" } Object.preventExtensions(foo); foo.age = 18;
|
Object.isExtensible
判断是否能向对象中添加属性
1 2 3 4 5
| let foo = { name: "foo" } Object.preventExtensions(foo); console.log(Object.isExtensible(foo));
|
封闭对象
Object.seal()
方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
,Object.isSealed
方法判断是否封闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| let foo = { name: "foo", age: 18 }
Object.seal(foo); console.log(JSON.stringify(Object.getOwnPropertyDescriptors(foo), null, 2))
console.log(Object.isSealed(foo)); delete foo.name;
|
冻结对象
Object.freeze
冻结对象后不允许添加、删除、修改属性,writable
、configurable
都标记为false
1 2 3 4 5
| let foo = { name: "foo" } Object.freeze(foo); foo.age = 18;
|
属性访问器
getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。
- 用于避免错误的赋值
- 需要动态监测值的改变
- 属性只能在访问器和普通属性任选其一,不能共同存在
getter/setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let user = { data: {name: "foo", age: null}, set age(value) { if (typeof value != "number" || value > 100 || value < 10) { throw new Error("年龄格式错误"); } this.data.age = value; }, get age() { return `年龄是:${this.data.age}` } } user.age = 18 console.log(user.age)
|
下面使用getter设置只读的课程总价
1 2 3 4 5 6 7 8 9 10 11 12 13
| let Lesson = { lists: [ { name: "js", price: 100 }, { name: "mysql", price: 212 }, { name: "vue.js", price: 98 } ], get total() { return this.lists.reduce((t, b) => t + b.price, 0); } }; console.log(Lesson.total); Lesson.total = 30; console.log(Lesson.total);
|
批量设置属性的使用
1 2 3 4 5 6 7 8 9 10 11 12
| const web = { name: "百度", url: "baidu.com", get site() { return `${this.name} ${this.url}`; }, set site(value) { [this.name, this.url] = value.split(","); } }; web.site = "好123,hao123.com"; console.log(web.site);
|
下面是设置token储取的示例,将业务逻辑使用 getter/setter
处理更方便,也方便其他业务的复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let Request = { get token() { let con = localStorage.getItem('token'); if (!con) { alert('请登录后获取token') } else { return con; } }, set token(con) { localStorage.setItem('token', con); } }; Request.token = 'Bearer xxxxx' console.log(Request.token);
|
定义内部私有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const user = { get name() { return this._name; }, set name(value) { if (value.length <= 3) { throw new Error("用户名不能小于三位"); } this._name = value; } }; user.name = "foo"; console.log(user.name); user.name = "foo and bar"; console.log(user.name);
|
访问器描述符
使用 defineProperty
可以模拟定义私有属性,从而使用面向对象的抽象特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function User(name, age) { let data = { name, age } Object.defineProperties(this, { name: { get() { return data.name }, set(value) { if (value.trim() == "") throw new Error("无效用户名") data.name = value } }, age: { get() { return data.name; }, set(value) { if (value.trim() == "") throw new Error("无效的用户名"); data.name = value; } } }) } let foo = new User("foo", 3) console.log(foo.name) foo.name = "bar" console.log(foo.name)
|
也可以使用es6之后的class语法糖定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const DATA = Symbol(); class User { constructor(name, age) { this[DATA] = { name, age }; } get name() { return this[DATA].name; } set name(value) { if (value.trim() == "") throw new Error("无效的用户名"); this[DATA].name = value; } get age() { return this[DATA].name; } set age(value) { if (value.trim() == "") throw new Error("无效的用户名"); this[DATA].name = value; } }
|
闭包访问器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let data = { name: 'foo', } for (const [key, value] of Object.entries(data)) { observer(data, key, value) }
function observer(data, key, v) { Object.defineProperty(data, key, { get() { return v }, set(newValue) { v = newValue }, }) } data.name = 'bar' console.dir(data.name)
|
代理拦截
代理(拦截器)是对象的访问控制,setter/getter
是对单个对象属性的控制,而代理是对整个对象的控制。
使用方法
转到:js元编程:代理
1 2 3 4 5 6 7 8 9 10 11 12
| const foo = { name: "foo"} const proxy = new Proxy(foo, { get(obj, property) { return obj[property] }, set(obj, property, value) { obj[property] = value return true } }) proxy.age = 10 console.log(foo)
|
代理函数
如果代理以函数方式执行时,会执行代理中定义 apply
方法。
参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间
1 2 3 4 5 6 7 8 9 10 11
| function factorial(num) { return num == 1 ? 1 : num * factorial(num - 1) } let proxy = new Proxy(factorial, { apply(func, obj, args) { console.time("run") func.apply(obj,args) console.timeEnd("rum") } }) proxy.apply(this, [1,2,3])
|