ES6 -- Reflect 与 Proxy (反射 和 代理)

属性描述符

Property Descriptor 属性描述符,用于描述一个属性的相关信息。

通过Object.getOwnPropertyDescriptor()可以得到一个对象的某个属性的属性描述符。

const obj = {
    a: 1,
    b: 2
}

const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 1, writable: true, enumerable: true, configurable: true}
  • value: 属性值。
const obj = {
    a: 1,
    b: 2
}

Object.defineProperty(obj, "a", {
    value: 4,
});

obj.a = 10;

const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 10, writable: true, enumerable: true, configurable: true}
  • configurable: 该属性的描述符是否可以修改。
const obj = {
    a: 1,
    b: 2
}

Object.defineProperty(obj, "a", {
    value: 4,
    configurable: false,
});

obj.a = 10;

// Cannot redefine property: a at Function.defineProperty 
Object.defineProperty(obj, "a", {
    value: 4,
    configurable: true, 
});

const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc);
  • enumerable: 该属性是否可以被枚举。
const obj = {
    a: 1,
    b: 2
}

Object.defineProperty(obj, "a", {
    value: 4,
    configurable: false,
    enumerable: false
});


// enumerable: true
// for(const prop in obj) {
//     console.log(prop); // a  b
// }


// enumerable: false
for(const prop in obj) {
    console.log(prop); // b
}

const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 4, writable: true, enumerable: true, configurable: true}
  • writable: 该属性是否可以被重新赋值。
const obj = {
    a: 1,
    b: 2
}

Object.defineProperty(obj, "a", {
    value: 4,
    configurable: false,
    enumerable: false,
    writable: false
});


obj.a = 10;

const desc = Object.getOwnPropertyDescriptor(obj, "a");
console.log(desc); // {value: 4, writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptors(对象)可以得到某个对象的所有属性描述符。

const obj = {
    a: 1,
    b: 2
}

const desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc); // {a: {…}, b: {…}}

如果需要为某个对象添加属性时 或 修改属性时,配置其属性描述符,可以使用下面的代码:

Object.defineProperty(对象,属性名,描述符);

Object.defineProperties(对象,多个属性的描述符);
const obj = {
    a: 1,
    b: 2,
    c: 3
}

Object.defineProperty(obj, "a", {
    value: "a",
    configurable: false,
    enumerable: false,
    writable: false
})

Object.defineProperties(obj, {
    b: {
        value: 33,
        configurable: false,
        enumerable: false,
        writable: false
    },
    c: {
        value: 55,
        configurable: false,
        enumerable: false,
        writable: false
    },
})

const desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc); // {a: {…}, b: {…}, c: {…}}

存取器属性

属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。

get 和 set 配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行 get 方法,将 get 方法得到的返回值作为属性值;如果给该属性赋值,则会运行 set 方法。

const obj = {
    a: 1
}

Object.defineProperty(obj, "a", {
    get() {
        console.log("运行了属性a的get函数");
    },
    set() {
        console.log("运行了属性a的set函数");
    }
})

// obj.a = 20; // set(20)
// console.log(obj.a); // console.log(get())

obj.a = obj.a + 1; // set(obj.a + 1)   set(get() + 1)
console.log(obj.a);
const obj = {
    a: 1
}

Object.defineProperty(obj, "a", {
    get() {
        console.log("运行了属性a的get函数");
        return obj._a;
    },
    set(val) {
        console.log("运行了属性a的set函数");
        obj._a = val;
    }
})

obj.a = 20; // set(20)
console.log(obj.a); // console.log(get())

存取器属性最大的意义,在于可以控制属性的读取和赋值。

const obj = {
    name: "qwee"
}

Object.defineProperty(obj, "age", {
    get() {
        console.log("运行了属性a的get函数");
        return obj._age;
    },
    set(val) {
        console.log("运行了属性a的set函数");
        if(typeof val !== "number") {
            throw new TypeError("年龄必须是数字");
        }
        if(val < 0) {
            val = 0;
        }else if(val > 200) {
            val = 200;
        }
        obj._age = val;
    }
})

// obj.age = "aa"; // 报错
obj.age = -100;
console.log(obj.age); // console.log(get())
<p>
    <span>姓名:</span>
    <span id="name"></span>
</p>
<p>
    <span>年龄:</span>
    <span id="age"></span>
</p>
<script>
    const spanName = document.getElementById("name");
    const spanAge = document.getElementById("age");

    const user = {};

    Object.defineProperties(user, {
        name: {
            get() {
                return spanName.innerText;
            },
            set(val) {
                spanName.innerText = val;
            }
        },
        age: {
            get() {
                return +spanAge.innerText;
            },
            set(val) {
                if (typeof val !== "number") {
                    throw new TypeError("年龄必须是数字");
                }
                if (val < 0) {
                    val = 0;
                } else if (val > 200) {
                    val = 200;
                }
                spanAge.innerText = val;
            }
        }

    })
</script>

Reflect 反射

1. Reflect 是什么?

Reflect 是一个内置的 JS 对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些 JS 底层功能。

由于它类似于其他语言的反射,因此取名为 Reflect。

  1. 它可以做什么?

使用 Reflect 可以实现诸如属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在于对象中等等功能。

  1. 这些功能不是已经存在了吗?为什么还需要用 Reflect 实现一次?

有一个重要的理念,在 ES5 就被提出:减少魔法,让代码更加纯粹。

这种理念很大程度上是受到 函数式编程的影响。

ES6 进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,需要将它们提取出来,形成一个正常的 API,并高度聚合到某个对象中,于是,就早就了 Reflect 对象。

因此,你可以看到 Reflect 对象中有很多的 API 都可以使用过去的某种语法或其他 API 实现。

  1. 它里面到底提供了哪些 API 呢?
  • Reflect.set(target, propertyKey, value): 设置对象 target 的属性 propertyKey 的值为 value,等同于给对象的属性赋值。
const obj = {
    a: 1,
    b: 2
}

// obj.a = 20; // 魔法

Reflect.set(obj, "a", 33);

console.log(obj.a); // 33
  • Reflect.get(target, propertyKey): 读取对象 target 的属性 propertyKey,等同于读取对象的属性值。
const obj = {
    a: 1,
    b: 2
}

// console.log(obj.a); // 魔法

console.log(Reflect.get(obj, "a")); // 1
  • Reflect.apply(target, thisArgument, argumentsList): 调用一个指定的函数,并绑定 this 和参数列表。等同于函数调用。

thisArgument: 绑定的 this。

function test(a, b) {
    console.log(a, b); // 3  4
}

// test(10, 20); // 调用

Reflect.apply(test, null, [3, 4]);
  • Reflect.deleteProperty(target, propertyKey): 删除一个对象属性。
const obj = {
    a: 1,
    b: 2
}

Reflect.deleteProperty(obj, "b");

console.log(obj); // {a: 1}
  • Reflect.defineProperty(target, propertyKey, attributes): 类似于 Object.defineProperty,不同的是如果配置出现问题,返回 false 而不是报错。
const obj = {
    a: 1,
    b: 2
}

Reflect.defineProperty(obj, "a", {
    enumerable: false
})

console.log(Object.getOwnPropertyDescriptors(obj));
  • Reflect.construct(target, argumentsList): 用构造函数的方式创建一个对象。
function test(a, b) {
    this.a = a;
    this.b = b;
}
const t = Reflect.construct(test, [1, 2]);
console.log(t); // test {a: 1, b: 2}
  • Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性。
const obj = {
    a: 1,
    b: 2
}

// console.log("a" in obj); // true

console.log(Reflect.has(obj, "a")); // true

Proxy 代理

代理:提供了修改底层实现的方式。

// 代理一个目标
// target:目标对象
// handler:是一个普通对象,其中可以重写底层实现
// 返回一个代理对象
new Proxy(target, handler);
const obj = {
    a: 1,
    b: 2
}

const proxy = new Proxy(obj, {
    set(target, propertyKey, value) {
        // console.log(target, propertyKey, value); // {a: 1, b: 2} "a" 3

        // target[propertyKey] = value;

        Reflect.set(target, propertyKey, value);
    },
    get(target, propertyKey) {
        if(Reflect.has(target, propertyKey)) {
            return Reflect.get(target, propertyKey);
        }else{
            return -1;
        }
    },
    has(target, propertyKey) {
        // 重写has方法
    }
})

proxy.a = 3;

console.log(obj,proxy); // {a: 3, b: 2}    Proxy {a: 3, b: 2}

console.log(proxy.d); // -1

应用-观察者模式

有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。

以前的实现方式:缺点有两个对象

<div id="container"></div>
<script>
    // 创建一个观察者
    function observer(target) {
        const div = document.getElementById("container");
        const ob = {};
        const props = Object.keys(target);
        for(const prop of props) {
            // ob[prop] = target[prop];
            Object.defineProperty(ob, prop, {
                get() {
                    return target[prop];
                },
                set(val) {
                    target[prop] = val;
                    render();
                },
                enumerable: true
            })
        }
        render();
        function render() {
            let html = "";
            for(const prop of Object.keys(ob)) {
                html += `
                    <span>${prop}:</span><span>${ob[prop]}</span></p>
                `;
            }
            div.innerHTML = html;
        }
        return ob;
    }

    const target = {
        a: 1,
        b: 2
    }
    const obj = observer(target);
</script>

代理实现:

<div id="container"></div>
<script>
    // 创建一个观察者
    function observer(target) {
        const div = document.getElementById("container");
        const proxy = new Proxy(target, {
            set(target, prop, value) {
                Reflect.set(target, prop, value);
                render();
            },
            get(target, prop) {
                return Reflect.get(target, prop);
            }
        })
        
        render();
        function render() {
            let html = "";
            for(const prop of Object.keys(target)) {
                html += `
                    <span>${prop}:</span><span>${target[prop]}</span></p>
                `;
            }
            div.innerHTML = html;
        }
        return proxy;
    }

    const target = {
        a: 1,
        b: 2
    }
    const obj = observer(target);
</script>

应用-偷懒的构造函数

构造函数:

class User {
    constructor(firstName, lastName, age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;

        console.log(this.firstName, this.lastName, this.age); // 12  13  14
    }
}

const a = new User(12, 13, 14);

偷懒的构造函数:

class User {

}

function ConstructorProxy(Class, ...propNames) {
    return new Proxy(Class, {
        construct(target, argumentsList) {
            console.log("构造函数被调用了");
            const obj = Reflect.construct(target, argumentsList);
            propNames.forEach((name, i) => {
                obj[name] = argumentsList[i];
            })
            return obj;
        }
    })
}

const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");
const a = new UserProxy(12, 13, 14);
console.log(a); // User {firstName: 12, lastName: 13, age: 14}

class Monster {

}


const MonsterProxy = ConstructorProxy(Monster, "sttack", "defence", "hp", "rate", "name");
const b = new MonsterProxy(12, 13, 14, 100, "aaa");

console.log(b); // Monster {sttack: 12, defence: 13, hp: 14, rate: 100, name: "aaa"}

应用-可验证的函数参数

function sum(a, b) {
    return a + b;
}

function validatorFunction(func, ...types) {
    const proxy = new Proxy(func, {
        apply(target, thisArgument, argumentsList) {
            types.forEach((t, i) => {
                const arg = argumentsList[i];
                if(typeof arg !== t) {
                    throw new TypeError(`第${i + 1}参数${argumentsList[i]}不满足类型${t}`);

                }
            })

            Reflect.apply(target, thisArgument, argumentsList);
        }
    })
    return proxy;
}

const sumProxy = validatorFunction(sum, "number", "number");

sumProxy(1, 2);

以前的做法:

function sum(a, b) {
    return a + b;
}

function validatorFunction(func, ...types) {
    return function(...argumentsList) {
        types.forEach((t, i) => {
            const arg = argumentsList[i];
            if(typeof arg !== t) {
                throw new TypeError(`第${i + 1}参数${argumentsList[i]}不满足类型${t}`);

            }
        })
        return func(...argumentsList);
    }
}

const sumProxy = validatorFunction(sum, "number", "number");

sumProxy(1, "2");
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容