第8章 控制对象的访问(setter、getter、proxy)
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
代理效率不高,在需要执行多次的代码中需要谨慎使用
第8章 控制对象的访问(setter、getter、proxy)的更多相关文章
- Spring学习笔记之 Spring IOC容器(一)之 实例化容器,创建JavaBean对象,控制Bean实例化,setter方式注入,依赖属性的注入,自动装配功能实现自动属性注入
本节主要内容: 1.实例化Spring容器示例 2.利用Spring容器创建JavaBean对象 3.如何控制Bean实例化 4.利用Spring实现bean属性sett ...
- JavaScript进阶 - 第9章 DOM对象,控制HTML元素
第9章 DOM对象,控制HTML元素 9-1 认识DOM 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法.DOM 将HTML文档呈现为带有元素.属 ...
- 第五章 JavaScript对象及初识面向对象
第五章 JavaScript对象及初识面向对象 一.对象 在JavaScript中,所有事物都是对象,如字符串.数值.数组.函数等. 在JavaScript对象分为内置对象和自定义对象,要处理一些 ...
- 【WPF学习】第四十五章 可视化对象
前面几章介绍了处理适量适中的图形内容的最佳方法.通过使用几何图形.图画和路径,可以降低2D图形的开销.即使正在使用复杂的具有分层效果的组合形状和渐变画刷,这种方法也仍然能够正常得很好. 然而,这样设计 ...
- Windows核心编程 第三章 内核对象
第3章内核对象 在介绍Windows API的时候,首先要讲述内核对象以及它们的句柄.本章将要介绍一些比较抽象的概念,在此并不讨论某个特定内核对象的特性,相反只是介绍适用于所有内核对象的特性. 首先介 ...
- windows核心编程---第三章 内核对象及句柄本质
本章讨论的是相对抽象的概念,不涉及任何具体的内核对象的细节而是讨论所有内核对象的共有特性. 首先让我们来了解一下什么是内核对象.内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存 ...
- iOS视图控制对象生命周期
iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...
- IOS 视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途
iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...
- 【转】【iOS知识学习】_视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途
原文网址:http://blog.csdn.net/weasleyqi/article/details/8090373 iOS视图控制对象生命周期-init.viewDidLoad.viewWillA ...
随机推荐
- Java安全之Unsafe类
Java安全之Unsafe类 0x00 前言 前面使用到的一些JNI编程和Javaagent等技术,其实在安全里面的运用非常的有趣和微妙,这个已经说过很多次.后面还会发现一些比较有意思的技术,比如AS ...
- Apriori 算法-如何进行关联规则挖掘
公号:码农充电站pro 主页:https://codeshellme.github.io 在数据分析领域有一个经典的故事,叫做"尿布与啤酒". 据说,在美国西部的一家连锁超市发现, ...
- 【SDOI2017】天才黑客(前后缀优化建图 & 最短路)
Description 给定一张有向图,\(n\) 个点,\(m\) 条边.第 \(i\) 条边上有一个边权 \(c_i\),以及一个字符串 \(s_i\). 其中字符串 \(s_1, s_2, \c ...
- KM 算法
KM 算法 可能需要先去学学匈牙利算法等二分图相关知识. 模板题-洛谷P6577 [模板]二分图最大权完美匹配 给 \(n\) 和 \(m\) 与边 \(u_i,v_i,w_i(1\le i\le m ...
- STL—— 容器(vector)元素的删除
1. clear() 将整个 vector 都删除 使用 vectorname.clear() 可以将整个vector 中的元素全部删除,但是内存不会释放,如下代码: 1 #include <i ...
- 最短路径——BFS算法
最短路径--BFS算法 单源最短路径问题 每对顶点间的最短路径 BFS求无权图的单源最短路径 bool visited[MAX_VERTEX_NUM]; //访问标记数组 //广度优先遍历 void ...
- C#知识结构
C#知识结构 对于一个工作多年的程序员而言,接口.反射.索引器.事件.委托这些耳熟能详的词汇,提起来别说多简单了,但是让老司机坐在那一个人拿起一支笔,把脑海中对C#知识结构进行梳理一下,大抵是写不了多 ...
- 【Electron Playground 系列】文件下载篇
作者:long.woo 文件下载是我们开发中比较常见的业务需求,比如:导出 excel. web 应用文件下载存在一些局限性,通常是让后端将响应的头信息改成 Content-Disposition: ...
- 阿里不允许使用 Executors 创建线程池!那怎么使用,怎么监控?
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 五常大米好吃! 哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃, ...
- 点击劫持(Iframe clickJack)练习
实验内容: 寻找一个合适的网站放入到iframe标签中.实验中测试了包括知网首页及登录界面.淘宝首页及登录界面,百度首页,微信下载界面.发现淘宝登录界面无法放入,会直接跳转到淘宝真实的登录界面,其他的 ...