zoukankan      html  css  js  c++  java
  • 第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
    

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

  • 相关阅读:
    2017-5-15 winform项目总结(知识点补充)
    2017-5-7 time控件 三级联动(省,市,区)
    2017-5-4 进程 线程 用户控件
    2017-5-3 打印控件 MDI 窗体容器 Activated事件
    2017-5-2 对话框控件 MessageBox.Show()用法补充 打开新窗体的3中模式
    窗体移动 窗体阴影API
    2017-4-28 ListView控件学习
    【2017-03-28】JS基础、DOM操作
    【2017-03-24】样式表样式
    【2017-03-24】CSS样式表
  • 原文地址:https://www.cnblogs.com/hycstar/p/14032403.html
Copyright © 2011-2022 走看看