1. 使用getter和setter控制属性访问

1.1 定义getter与setter

通过对象字面量定义,或在ES6的class中定义

// 通过对象字面量定义
const students = {
student: ["Wango", "Lily", "Jack"],
// 使用set和get关键字,相当于给对象新增了一个属性(而不是方法)
// 在这个属性被赋值或被读取时,隐式调用getter或setter方法
get firstStudent() { // getter方法不接收任何参数
return this.student[0];
},
set firstStudent(val) {
this.student[0] = val;
}
} // 如同访问标准对象属性一样访问firstStudent属性
console.log(students.firstStudent);
// Wango // 如同操作标准对象属性一样为fristStudent赋值
students.firstStudent = "Tom";
console.log(students.firstStudent);
// Tom // 在class中定义setter与getter
class Student {
constructor() {
this.students = ["Wango", "Lily", "Jack"];
} get firstStudent() {
return this.students[0];
} set firstStudent(val) {
this.students[0] = val;
}
} const s1 = new Student(); console.log(s1.firstStudent);
// Wango
s1.firstStudent = "Tom";
console.log(s1.firstStudent);
// Tom /**
* 针对指定的属性不一定需要同时定义getter和setter,
* 通常仅提供getter,如果在这种试图写入属性值
* 非严格模式下写入的属性值会被忽略
* 严格模式下会抛出异常
*/

通常来讲,setter和getter是用于控制访问私有属性的,但以上两种方式都是控制的公共属性。因为JS没有私有属性,只能通过闭包来模拟私有。而字面量和类中getter/setter和属性不是在同一个作用域中定义的,因此无法控制私有属性。

通过使用内置的Object.defineProperty方法

function Student(name) {
// 构造函数参数初始化属性值,需要注意的是:
// 这个初始化的值没有经过校验,可能会出错
let _name = name; Object.defineProperty(this, "name", {
get: () => _name,
set: val => _name = val
});
} const s = new Student("Wango"); // 只能通过setter和getter设置和访问属性
console.log(s.name);
// Wango
s.name = "Tom";
console.log(s.name);
// Tom // 无法直接访问私有属性
console.log(typeof s._name === "undefined");
// true
// 在类中使用这个方法同样有效
class Student {
constructor(name) {
// 构造函数参数初始化属性值,需要注意的是:
// 这个初始化的值没有经过校验,可能会出错
let _name = name;
Object.defineProperty(this, "name", {
get: () => _name,
set: val => _name = val
});
}
} const s = new Student("Wango"); console.log(s.name);
// Wango
s.name = "Tom";
console.log(s.name);
// Tom
console.log(typeof s._name === "undefined");
// true

1.2 使用setter和getter校验属性值

function Student() {
// 直接定义初始值,不由外界输入,确保安全
let _age = 0; Object.defineProperty(this, "age", {
get: () => _age,
set: val => {
// 检查输入是否是整数
if(!Number.isInteger(val)) {
throw new TypeError("Age should be an Integer");
}
_age = val;
}
});
} const s = new Student(); // 整数类型通过
s.age = 24 console.log(s.age);
// 24 // 字符串类型被拦截
s.age = "25";
// Uncaught TypeError: Age should be an Integer

使用setter还可以跟踪值的变化,提供性能日志,提供值发生变化的提示等

1.3 使用getter与setter定义如何计算属性值

class Student {
constructor() {
// 设置俩个公共属性
this.firstName;
this.lastName; } // 对参数分割并单独存放
set fullName(name) {
const segment = name.split(" ");
this.firstName = segment[0];
this.lastName = segment[1];
}
// 拼接两个属性
get fullName() {
return this.firstName + " " + this.lastName;
}
} const s = new Student(); s.fullName = "Wango Liu";
console.log(s.firstName);
// Wango
console.log(s.lastName);
// Liu

2. 使用代理控制访问

const student = {
name: "Wango",
age: 24
}
// 初始化代理对象
// 第一个参数为目标对象
// 第二个参数为一个对象,其中定义了在对象执行特定行为时触发的函数
const proxy = new Proxy(student, {
// 获取属性时检测是否存在该属性
get: (target, key) => {
return key in target ? target[key] : "This property do not exist.";
},
set: (target, key, value) => {
// 在这里可以进行类型判断、数值追踪等操作
target[key] = value;
}
}); console.log(proxy.name);
// Wango
console.log(proxy.addr);
// This property do not exist. proxy.addr = "China"; console.log(proxy.addr);
// China
console.log(student.addr);
// China

对象内部的getter和setter作用于某个属性,代理作用于整个代理目标

  • 代理还有很多其他方法,包括但不限于:

    • 调用函数时激活apply
    • 使用new操作符时激活construct
    • 读取/写入属性时激活get/set
    • 执行for-in语句时激活enumerate

2.1 使用代理记录日志

// 定义函数为每个参数对象提供代理
function makeLoggable(target) {
// 代理的工作为记录日志
return new Proxy(target, {
set: (target, key, value) => {
console.log(`Writing value: ${value} to ${key}`);
target[key] = value;
},
get: (target, key) => {
console.log(`Reading: ${key}`);
return target[key];
}
});
} let student = {
name: "Wango",
age: 24
} student = makeLoggable(student); console.log(student.name);
// Reading: name
// Wango
student.age = 25;
// Writing value: 25 to age

2.2 使用代理检测性能

function isPrime(num) {
if(num < 2) { return false; } for(let i = 2; i < num; i++) {
if(num % i === 0) {
return false;
}
}
return true;
} isPrime = new Proxy(isPrime, {
apply: (target, thisArg, args) => {
// 启动计时器记录时间
console.time("isPrime");
const result = target.apply(thisArg, args);
console.timeEnd("isPrime");
// 要记得储存和返回函数的计算结果
return result;
}
}); isPrime(129982790);
// isPrime: 0.034931640625 ms

2.3 使用代理自动填充属性

function Address() {
return new Proxy({}, {
get: (target, key) => {
// 如果对象不具有该属性就创建该属性
if(!target[key]) {
target[key] = new Address();
} return target[key];
}
});
} const addr = new Address(); // 自动创建属性,不会报错
addr.Asia.China.Chongqing = "Hot-pot"; console.log(addr.Asia.China.Chongqing);
// Hot-pot

2.4 使用代理实现负数组索引

function creatNegativeArrayProxy(array) {
// 类型检测
if(!Array.isArray(array)) {
throw new TypeError("Expected an Array.");
} return new Proxy(array, {
get: (array, index)=> {
index = +index; // 使用一元操作符将属性名变为数值
// 如果访问的是负向索引,则逆向访问数组
return array[index < 0 ? array.length + index : index];
},
set: (array, index, value) => {
index = +index;
array[index < 0 ? array.length + index : index] = value;
}
});
} let arr = [0, 1, 2]; arr = creatNegativeArrayProxy(arr); // 负向索引可以正常使用
console.log(arr[1]);
// 1
console.log(arr[-1]);
// 2
arr[-1] = -1;
console.log(arr[-1]);
// -1 // 后面就有一些迷惑行为
console.log(arr);
// Proxy {0: 0, 1: 1, 2: -1}
console.log(arr.length);
// undefined
console.log(Array.isArray(arr));
// true

2.5 代理的性能消耗

function creatNegativeArrayProxy(array) {
if(!Array.isArray(array)) {
throw new TypeError("Expected an Array.");
} return new Proxy(array, {
get: (array, index)=> {
index = +index;
return array[index < 0 ? array.length + index : index];
},
set: (array, index, value) => {
index = +index;
array[index < 0 ? array.length + index : index] = value;
}
});
} function measure(items) {
const startTime = new Date().getTime();
for(let i = 0; i < 500000; i++) {
items[0] = "Wango";
items[1] = "Lily";
items[2] = "Tom";
}
return new Date().getTime() - startTime;
} let arr = ["Wango", "Lily", "Tom"]; arrProxy = creatNegativeArrayProxy(arr); console.log(Math.round(measure(arrProxy) / measure(arr)));
// 49 --> Chrome浏览器在50左右
// 42 --> Edge浏览器在40左右
// 60-120之间 Firefox浏览器 最低值55,最高值124

代理效率不高,在需要执行多次的代码中需要谨慎使用

最新文章

  1. thrift 服务端linux C ++ 与客户端 windows python 环境配置(thrift 自带tutorial为例)
  2. C# 获得MP4时长
  3. windows下安装redis以及测试
  4. ffmpeg mp3 to m3u8
  5. C语言回滚(三)-指针
  6. 采用post的方式提交数据
  7. zw版【转发&#183;台湾nvp系列Delphi例程】HALCON AffineTransImage
  8. poj 1279 -- Art Gallery (半平面交)
  9. nginx+tomcat动静分离结构
  10. aircrack-ng on OSX 从零开始之安装
  11. Transparency Tutorial with C# - Part 1
  12. 添加多盟SDK 库函数
  13. mysql 行锁排查
  14. xcode-build/version-bump
  15. Aspose.Cells.dll操作execl
  16. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎
  17. 十二、u-boot 调试--串口修改
  18. 添加浏览器back操作时的响应事件
  19. python 遍历enumerate
  20. 鼠标悬浮在img上让图片变大

热门文章

  1. VirtualBox安装Centos出现E_FAIL (0x80004005)的解决方法
  2. 6种css3 transform图片悬停动态效果
  3. 【题解】Railway [Uva10263]
  4. Fabric v2.0中的隐私数据
  5. STL——容器(Map &amp; multimap)的排序与遍历
  6. C++异常之五 异常和继承
  7. 四、testNG.xml 简单介绍
  8. gnuplot取消曲线标题
  9. 迭代 可迭代对象 迭代器的bj
  10. Nginx(一):安装与常用命令