js中的对象(下)
属性特征
JS 中可以对属性的访问特性进行控制。
查看特征
使用 Object.getOwnPropertyDescriptor查看对象属性的描述。
const obj = {
name: 'kevin',
age: 20
};
let desc = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(desc);
// {
// value: 'kevin',
// writable: true,
// enumerable: true,
// configurable: true
// }
使用 Object.getOwnPropertyDescriptors查看对象所有属性的描述
const obj = {
name: 'kevin',
age: 20
};
let desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc);
// {
// name: {
// value: 'kevin',
// writable: true,
// enumerable: true,
// configurable: true
// },
// age: { value: 20, writable: true, enumerable: true, configurable: true }
// }
属性包括以下四种特性
| 特性 | 说明 | 默认值 |
|---|---|---|
| configurable | 能否使用 delete、能否修改属性特性、或能否修改访问器属性 | true |
| enumerable | 对象属性是否可通过 for-in 循环,或 Object.keys() 读取 | true |
| writable | 对象属性是否可修改 | true |
| value | 对象属性的默认值 | undefined |
设置特征
使用Object.defineProperty 方法修改属性特性,通过下面的设置属性 name 将不能被遍历、删除、修改。
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.defineProperty(obj, 'name',{
value: 'tom',
writable: false,
enumerable: false,
configurable: false
});
// 不允许修改
// obj.name = 'uu'; ////Error
// 不允许遍历
// console.log(Object.keys(obj));//[ 'age' ]
// 不允许删除
// delete obj.name; //Error Cannot delete property 'name' of #<Object>
// 不允许配置
// Object.defineProperty(obj, 'name',{
// value: 'hh',
// writable: false,
// enumerable: false,
// configurable: true
// }); //Cannot redefine property: name
使用 Object.defineProperties 可以一次设置多个属性,具体参数和上面介绍的一样。
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.defineProperties(obj, {
name: {writable: false},
age: {value: 18}
});
console.log(obj);//{ name: 'kevin', age: 18 }
obj.name = 'tom';
//TypeError: Cannot assign to read only property 'name' of object '#<Object>'
禁止添加
Object.preventExtensions 禁止向对象添加属性
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.preventExtensions(obj);
//严格模式下添加属性报错
// TypeError: Cannot add property site, object is not extensible
obj.site = 'this is a site';
//非严格模式下
console.log(obj.site);//undefined
Object.isExtensible 判断是否能向对象中添加属性
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.preventExtensions(obj);
console.log(Object.isExtensible(obj));//false
封闭对象
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 configurable: false
不能添加新属性,不能使用 delete、不能修改属性特性、不能修改访问器属性。
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.seal(obj);
console.log(Object.getOwnPropertyDescriptors(obj));//configurable的值为false
obj.site = 'this is a site';
// TypeError: Cannot add property site, object is not extensible
Object.isSealed 如果对象是密封的则返回 true,属性都具有 configurable: false。
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.seal(obj);
console.log(Object.isSealed(obj));//true
冻结对象
Object.freeze 冻结对象后不允许添加、删除、修改属性,writable、configurable 都标记为false,只能取值和遍历
'use strict';
const obj = {
name: 'kevin',
age: 20
};
Object.freeze(obj);
console.log(obj.name);
console.log(Object.keys(obj));//[ 'name', 'age' ]
console.log(Object.isFrozen(obj));//true
用freeze方法可以很好的快速对对象进行保护。
属性访问器
getter 方法用于获得属性值(getter方法必须有且只有一个形参),setter 方法用于设置属性。这是 JS 提供的存取器特性即使用函数来管理属性。
- 用于避免错误的赋值
- 需要动态监测值的改变
- 属性只能在访问器和普通属性任选其一,不能共同存在
getter/setter
下面是对用户的年龄数据使用访问器监控控制
'use strict';
const obj = {
date: {name: 'kevin', age: 20},
set age(value){
if(typeof value !== 'number' || value>100 || value < 0){
throw new Error('年龄错误');
}
else{
this.date.age = value;
}
},
get age(){
return `${this.date.name}今年${this.date.age}岁了`;
}
};
obj.age = 21;
console.log(obj.age);//kevin今年21岁了
// obj.age = -1;
// console.log(obj.age);//Error: 年龄错误
访问器伪造属性操作
'use strict'
const Lesson = {
lesson: [
{title: 'js', click: 99},
{title: 'ts', click: 55},
{title: 'node', click: 77},
],
set total(value){
console.log(value);
},
get total(){
return this.lesson.reduce((t,v) => t + v.click,0);
}
}
Object.freeze(Lesson);
Lesson.total = 111;//111 这个并不是添加属性,而是在向函数传值
console.log(Lesson.total);//231
使用访问器批量设置属性
下面通过设置站网站名称与网址体验getter/setter批量设置属性的使用
'use strict'
const lesson = {
name:'kevin',
url: 'dd.com',
set site(value){
[this.name,this.url] = value.split(',')//使用数组的解构赋值和split方法
},
get site(){
return `${this.name}的网址是${this.url}`;
}
};
lesson.site = 'tom,www.niu.cn';
console.log(lesson.site);//tom的网址是www.niu.cn
下面是设置 token 储取的示例,将业务逻辑使用getter/setter处理更方便,也方便其他业务的复用。
<body></body>
<script>
let Require = {
set token(value){
localStorage.setItem('token',value);
},
get token(){
let token = localStorage.getItem('token');
return !token ? '请登录' : token;
}
};
Require.token = 'asdfasdf2342134234';
console.log(Require.token);//asdfasdf2342134234
</script>
访问器的优先级
同名的访问器和属性,谁在后面声明,就会覆盖掉前面的
属性在后面
'use strict'
let user = {
get name(){
return 'kk';
},
name: 'kevin',
age: 20
};
console.log(user);//{ name: 'kevin', age: 20 }
console.log(user.name);//kevin
访问器在后面
'use strict'
let user = {
name: 'kevin',
age: 20,
get name(){
return 'kk';
}
};
console.log(user);//{ name: [Getter], age: 20 }
console.log(user.name);//kk
如果想要保存属性,我们可以将它定义为一个私有属性
'use strict'
const DATA = Symbol('user的私有属性');
const user = {
[DATA]: { name: 'kk' },
age: 20,
set name(value){
this[DATA].name = value;
},
get name(){
return this[DATA].name;
}
};
user.name = 'kevin'
console.log(user.name);//kevin
访问器描述符
使用 defineProperty 可以模拟定义私有属性,从而使用面向对象的抽象特性。
function User(name, age){
let data = {name, age};
Object.defineProperties(this, {
name: {
set(value){
if(value.trim() === '') throw new Error('无效的姓名');
data.name = value;
},
get(){
return `名字是${data.name}`;
}
},
age: {
set(value){
if(value<=0||value>100) throw new Error('年龄错误');
data.age = value;
},
get(){
return `年龄是${data.age}`;
}
}
});
}
let user = new User('keivn',20);
console.log(user.name, user.age);//名字是keivn 年龄是20
[user.name, user.age] = ['tom', 22];
console.log(user.name, user.age);//名字是tom 年龄是22
上面的代码也可以使用语法糖 class定义,class是构造函数的语法糖
"use strict";
const DATA = Symbol();
class User{
constructor(name,age){
this[DATA] = {name, age};
}
set name(value){
if(value.trim() === '') throw new Error('无效的姓名');
this[DATA].name = value;
}
get name(){
return `名字是${this[DATA].name}`;
}
set age(value){
if(value<=0||value>100) throw new Error('年龄错误');
this[DATA].age = value;
}
get age(){
return `年龄是${this[DATA].age}`;
}
}
let user = new User('keivn',20);
console.log(user.name, user.age);//名字是keivn 年龄是20
[user.name, user.age] = ['tom', 22];
console.log(user.name, user.age);//名字是tom 年龄是22
代理拦截
代理(拦截器)是对象的访问控制,setter/getter 是对单个对象属性的控制,而代理是对整个对象的控制。
- 读写属性时代码更简洁
- 对象的多个属性控制统一交给代理完成
- 严格模式下
set必须返回布尔值
其中:
Proxy怎么用
let p = new Proxy(target, handler);
语法非常简单,只有两个参数,很好理解
target
用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个对象,其属性是当执行一个操作时定义代理的行为的函数。
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身。所有的属性调用都会进入同一个get即使是没有的属性。
- 可以创造一些本来没有的属性
- 可以在取数据时对数据进行验证和转换
- 可以自定义一些语法糖操作
- get返回一个函数的话可以把一个属性转换成方法
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
- 可以用来验证属性是否符合要求
- 可以用来更改数据格式
- 可以用来监听数据更改事件
- 屏蔽一些赋值操作比如以"_"开头的私有变量
示例:
"use strict";
const user = {name: 'kevin'};
const proxy = new Proxy(user, {
get(obj, key){
return obj[key];
},
set(obj, key, value){
obj[key] = value;
return true;
}
});
proxy.age = 20;
console.log(user);//{ name: 'kevin', age: 20 }
代理函数
如果代理以函数方式执行时,会执行代理中定义 apply 方法。
- 参数说明:函数,上下文对象,参数
下面使用 apply 计算函数执行时间
"use strict";
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('run');
}
});
proxy.apply(this,[1000]);//run: 0.161ms
数组实现代理拦截
下例中对数组进行代理,用于截取标题操作
"use strict";
const stringDot = {
get (target, key){
const title = target[key].title;
const len = 5;
return title.length > len
? title.substr(0, len) + ".".repeat(3)
: title;
}
};
const lesson = [
{
title: 'js的手写Promise',
click: 88
},{
title: 'ts装饰器',
click: 55
},{
title: 'node为什么高并发',
click: 66
}
];
const stringDotProxy = new Proxy(lesson, stringDot);
console.log(stringDotProxy[0]);//js的手写...
console.log(stringDotProxy[1]);//ts装饰器
使用代理拦截实现表单验证
<style>
body {
padding: 50px;
background: #34495e;
}
input {
border: solid 10px #ddd;
height: 30px;
}
.error {
border: solid 10px red;
}
</style>
<body>
<input type="text" validate rule="max:12,min:3" />
<input type="text" validate rule="max:3,isNumber" />
</body>
<script>
"use strict";
//验证处理类
class Validate {
max(value, len) {
return value.length <= len;
}
min(value, len) {
return value.length >= len;
}
isNumber(value) {
return /^\d+$/.test(value);
}
}
//代理工厂
function makeProxy(target) {
return new Proxy(target, {
get(target, key) {
return target[key];
},
set(target, key, el) {
const rule = el.getAttribute("rule");
const validate = new Validate();
let state = rule.split(",").every(rule => {
const info = rule.split(":");
return validate[info[0]](el.value, info[1]);
});
el.classList[state ? "remove":"add"]("error");
return true;
}
});
}
const nodes = makeProxy(document.querySelectorAll("[validate]"));
nodes.forEach((item, i) => {
item.addEventListener("keyup", function() {
nodes[i] = this;
});
});
</script>
JSON
- json 是一种轻量级的数据交换格式,易于人阅读和编写。
- 使用
json数据格式是替换xml的最佳方式,主流语言都很好的支持json格式。所以json也是前后台传输数据的主要格式。 - json 标准中要求使用双引号包裹属性,虽然有些语言不强制,但使用双引号可避免多程序间传输发生错误语言错误的发生。
序列化
序列化是将 json 转换为字符串,一般用来向其他语言传输使用。
let obj = {
name: 'kevin',
age: 18,
lesson: {
title: 'js',
click: 66
}
};
let json = JSON.stringify(obj, null, 2)
console.log(json);
// {
// "name": "kevin",
// "age": 18,
// "lesson": {
// "title": "js",
// "click": 66
// }
// }
根据第二个参数指定保存的属性
let json = JSON.stringify(obj, ['name','age'], 2)
console.log(json);
// {
// "name": "kevin",
// "age": 18
// }
第三个是参数用来控制 TAB 数量
let json = JSON.stringify(obj, ['name','age'], 6)
console.log(json);
// {
// "name": "kevin",
// "age": 18
// }
为数据添加 toJSON 方法来自定义返回格式
let obj = {
name: 'kevin',
age: 18,
lesson: {
title: 'js',
click: 66
},
'JSON': function(){
return {
'name': this.name,
'lesson': this.lesson.title
}
}
};
console.log(JSON.stringify(obj));
//{"name":"kevin","age":18,"lesson":{"title":"js","click":66}}
反序列化
使用 JSON.parse 将字符串 json 解析成对象
let obj = {
name: 'kevin',
age: 18,
lesson: {
title: 'js',
click: 66
}
};
let json = JSON.stringify(obj, null, 2)
let newObj = JSON.parse(json);
console.log(newObj);
// { name: 'kevin', age: 18, lesson: { title: 'js', click: 66 } }
使用第二个参数函数来对返回的数据二次处理
let obj = {
name: 'kevin',
age: 18,
lesson: {
title: 'js',
click: 66
}
};
let json = JSON.stringify(obj, null, 2)
let newObj = JSON.parse(json, (key, value)=>{
if (key === 'name') return `[名字]-${value}`;
return value;
});
console.log(newObj);
// { name: '[名字]-kevin', age: 18, lesson: { title: 'js', click: 66 } }