js的Proxy和Reflect的应用场景

proxy基本使用方式

/**
 * target: 表示要代理的目标,可以是object, array, function类型
 * handler: 是一个对象,可以编写各种代理的方法
 */
const proxy = new Proxy(target, handler);

1. 跟踪属性访问

const user = {
    name: 'Jack'
}

const userPorxy = new Proxy(user, {
    get(target, prop) {
        console.log(`Getting ${prop}`);
        return Reflect.get(...arguments);
    },
    set(target, prop, val) {
        console.log(`Setting ${prop} to ${val}`);
        return Reflect.set(...arguments);
    }
});

userPorxy.name;
// Getting name
userPorxy.name = 'Wango';
// Setting name to Wango

2. 隐藏属性

const user = {
    name: 'Wango',
    age: 24,
    addr: 'Chongqing, China',
    income: 'classified',
    email: 'example@mail.com'
}

const hiddenProps = ['addr', 'income', 'email'];

const userProxy = new Proxy(user, {
    get(target, prop) {
        if (hiddenProps.includes(prop)) {
            return undefined;
        }
        return Reflect.get(...arguments);
    },
    has(target, prop) {
        if (hiddenProps.includes(prop)) {
            return false;
        }
        return Reflect.has(...arguments);
    }
});

console.log(userProxy.name); // Wango
console.log(userProxy.addr); // undefined

console.log('age' in userProxy); // true
console.log('income' in userProxy); // false
console.log('email' in userProxy); // false

// 但是for...in依旧可以枚举属性
for (let key in userProxy) {
    console.log(key);
}

3. 属性验证

const user = {
    name: 'Wango',
    age: 24
}

const userPorxy = new Proxy(user, {
    set(target, prop, val) {
        if (prop === 'age') {
            if (typeof val !== 'number') {
                throw new TypeError('A number expected!');
            }
        }
        return Reflect.set(...arguments);
    }
});

userPorxy.age = 33;
console.log(userPorxy.age); // 33
userPorxy.age = '100'; // TypeError: A number expected!

4. 函数和构造函数参数验证

function add(...args) {
    return args.reduce((a, b) => a + b);
}

const addProxy = new Proxy(add, {
    apply(target, thisArg, args) {
        for (let i = 0; i < args.length; i++) {
            if (typeof args[i] !== 'number') {
                throw new TypeError('Non-number argument provided');
            }
        }
        return Reflect.apply(...arguments);
    }
}); 

console.log(addProxy(1, 2, 3, 4, 5)); // 15
console.log(addProxy(1, 2, 3, 4, '5')); 
// TypeError: Non-number argument provided

class User {
    constructor(id) {
        this.id = id;
    }
}

const UserProxy = new Proxy(User, {
    construct(target, args, newTarget) {
        if (args[0] === undefined) {
            throw new Error('User cannot be instantiated without id');
        }
        return Reflect.construct(...arguments);
    }
});

const u1 = new UserProxy('Wango');
const u2 = new UserProxy(); 
// Error: User cannot be instantiated without id

5. 数据绑定和可观察对象

被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中

const userList = [];

class User {
    constructor(name) {
        this.name = name;
    }
}

const UserProxy = new Proxy(User, {
    construct() {
        const newUser = Reflect.construct(...arguments);
        userList.push(newUser);
        return newUser;
    }
});

new UserProxy('Wango');
new UserProxy('Jack');
new UserProxy('Lily');

console.log(userList);
// [User, User, User]

把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息

function emit(newVal) {
    console.log(newVal);
}

const userList = [];

const userListProxy = new Proxy(userList, {
    set(target, prop, val, receiver) {
        const result = Reflect.set(...arguments);
        // if (result) {
        //     emit(Reflect.get(target, prop, receiver));
        // }
        // 加个判断,对length的修改不发送消息
        if (prop !== 'length' && result) {
            emit(Reflect.get(target, prop, receiver));
        }
        return result;
    }
});

// push会对userList进行两次set操作,
// 第一次新增一个元素,第二次修改length的值
userListProxy.push('Wango');
// Wango
// 1
userListProxy.push('Lily');
// Lily
// 2

6. 代理数组

const arr = [1, 2, 3, 4];
const arrProxy = new Proxy(arr, {
  get(target, key, receiver) {
    console.log('arrProxy.get', target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log('arrProxy.set', target, key, value);
    return Reflect.set(target, key, value, receiver);
  },
  deleteProperty(target, key) {
    console.log('arrProxy.deleteProperty', target, key);
    return Reflect.deleteProperty(target, key);
  },
});

7. 统计函数被调用的上下文和次数

const countExecute = (fn) => {
  let count = 0;
 
  return new Proxy(fn, {
    apply(target, ctx, args) {
      ++count;
      console.log('ctx上下文:', ctx);
      console.log(`${fn.name} 已被调用 ${count} 次`);
      return Reflect.apply(target, ctx, args);
    },
  });
};

const getSum = (...args) => {
  if (!args.every((item) => typeof item === 'number')) {
    throw new TypeError('参数应当均为number类型');
  }
  return args.reduce((sum, item) => sum + item, 0);
};
 
const useSum = countExecute(getSum);
 
useSum(1, 2, 3); // getSum 已被调用 1 次
 
useSum.apply(window, [2, 3, 4]); // getSum 已被调用 2 次
 
useSum.call(person, 3, 4, 5); // getSum 已被调用 3 次

8. 实现一个防抖功能

const throttleByProxy = (fn, rate) => {
  let lastTime = 0;
  return new Proxy(fn, {
    apply(target, ctx, args) {
      const now = Date.now();
      if (now - lastTime > rate) {
        lastTime = now;
        return Reflect.apply(target, ctx, args);
      }
    },
  });
};
 
const logTimeStamp = () => console.log(Date.now());
window.addEventListener('scroll', throttleByProxy(logTimeStamp, 300));

9. 实现观察者模式

我们在这里实现一个最简单类 mobx 观察者模式。

const list = new Set();
const observe = (fn) => list.add(fn);
const observable = (obj) => {
  return new Proxy(obj, {
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      list.forEach((observer) => observer());
      return result;
    },
  });
};
const person = observable({ name: 'wenzi', age: 20 });
const App = () => {
  console.log(`App -> name: ${person.name}, age: ${person.age}`);
};
observe(App);

person就是使用 Proxy 创建出来的代理对象,每当 person 中的属性发生变化时,就会执行 App()函数。这样就实现了一个简单的响应式状态管理。

 

出处:https://www.cnblogs.com/hycstar/p/14591158.html?ivk_sa=1024320u

        https://zxbcw.cn/post/212296/

posted @ 2022-01-18 13:52  全玉  阅读(564)  评论(0编辑  收藏  举报