Skip to content
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

控制对象的访问(getter/setter、proxy) #18

Open
SinLv opened this issue Jan 11, 2021 · 0 comments
Open

控制对象的访问(getter/setter、proxy) #18

SinLv opened this issue Jan 11, 2021 · 0 comments

Comments

@SinLv
Copy link
Owner

SinLv commented Jan 11, 2021

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant