前端JavaScript中的反射和代理

目录
  • 1、什么是反射
  • 2、JavaScript中Reflect
    • 2.1 Reflect.get(target, name, receiver)
    • 2.2 Reflect.set(target, name, value, receiver)
    • 2.3 Reflect.has(obj, name)
    • 2.4 Reflect.deleteProperty(obj, name)
    • 2.5 Reflect.construct(target, args)
    • 2.6 Reflect.setPrototypeOf(obj, newProto)
    • 2.7 Reflect.apply(func, thisArg, args)
    • 2.8 Reflect.defineProperty(target, propertyKey, attributes)
    • 2.9 Reflect.getOwnPropertyDescriptor(target, propertyKey)
    • 2.10 Reflect.isExtensible (target)
    • 2.11 Reflect.preventExtensions(target)
    • 2.13 Reflect.ownKeys (target)
  • 3、JavaScript中Proxy
    • 3.1 Proxy中支持的拦截操作
    • 3.2 get()
    • 3.3 set()
    • 3.4 has()
    • 3.5 defineProperty()
    • 3.6 deleteProperty()
    • 3.7 getOwnPropertyDescriptor()
    • 3.8 getPrototypeOf()
    • 3.9 setPrototypeOf()
    • 3.10 isExtensible()
    • 3.11 ownKeys()
    • 3.12 preventExtensions()
    • 3.13 apply()
    • 3.14 construct()
  • 4、观察者模式

1、什么是反射

反射这个概念在很多编程语言中都存在,像JavaC#

在面向对象编程中,一般会先将类和方法定义好,然后创建对象显式调用方法,比如下面的例子:

public class User{
   private String name;
   private Date birthday;
       //....
   public int calculateAgeByBirthday(){
            // .....
   }
}
// 调用
User u = new User("jack", new Date());
u.calculateAgeByBirthday();

上面这种调用方式我们比较熟悉,不过当你想编写一些抽象框架时(框架又需要与业务定义的类进行互操作),由于你不知道业务类的成员和方法,这时反射动态获取成员变量或调用方法。

下面例子,我们利用反射将json转换为Java对象。

public static class User {
 private String name;
 public String getName() {
    return name;
 }
   public void setName(String name) {
     this.name = name;
   }
}

// 使用反射调用对象setter方法。
public static <T> T fill(Class<T> userClass, Map<String, Object> json) throws Exception {
        Field[] fields = userClass.getDeclaredFields();
        T user = userClass.newInstance();
        for (Field field : fields) {
            // 首字母大写
            String name = field.getName();
            char[] arr = name.toCharArray();
            arr[0] = Character.toUpperCase(arr[0]);
            System.out.println(new String(arr));
            Method method = userClass.getDeclaredMethod("set" + new String(arr), field.getType());
            Object returnValue = method.invoke(user, json.get(name));
        }
        return user;
}

2、JavaScript中Reflect

JavaScriptES6提供了反射内置对象Reflect,但JavaScript里面的反射和Java反射有所不同。先看下Reflect提供的13个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

2.1 Reflect.get(target, name, receiver)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

const obj = {
  name: 'jack',
  age: 12,
  get userInfo() {
    return this.name + ' age is ' + this.age;
  }
}

Reflect.get(obj, 'name') // jack
Reflect.get(obj, 'age') // 12
Reflect.get(obj, 'userInfo') // jack age is 12

// 如果传递了receiver参数,在调用userInfo()函数时,this是指向receiver对象。
const receiverObj = {
  name: '小明',
  age: 22
};

Reflect.get(obj, 'userInfo', receiverObj) // 小明 age is 22

2.2 Reflect.set(target, name, value, receiver)

const obj = {

  name: 'jack',
  age: 12,
  set updateAge(value) {
    return this.age = value;
  },
}
Reflect.set(obj, 'age', 22);
obj.age // 22

// 如果传递了receiver参数,在调用updateAge()函数时,this是指向receiver对象。
const receiverObj = {
  age: 0
};

Reflect.set(obj, 'updateAge', 10, receiverObj) //
obj.age         // 22
receiverObj.age // 10

2.3 Reflect.has(obj, name)

Reflect.has方法相当于name in obj里面的in运算符。

const obj = {
  name: 'jack',
}
obj in name // true
Reflect.has(obj, 'name') // true

2.4 Reflect.deleteProperty(obj, name)

Reflect.deleteProperty方法相当于delete obj[name] ,用于删除对象的属性。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false。

const obj = {
  name: 'jack',
}
delete obj.name
Reflect.deleteProperty(obj, 'name')

2.5 Reflect.construct(target, args)

Reflect.construct方法等同于new target(...args)

function User(name){
  this.name = name;
}
const user = new User('jack');
Reflect.construct(User, ['jack']);
Reflect.getPrototypeOf(obj)
Reflect.getPrototypeOf方法用于读取对象的__proto__属性。

2.6 Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype)。返回一个布尔值,表示是否设置成功。

const obj = {
  name: 'jack',
}
Reflect.setPrototypeOf(obj, Array.prototype);
obj.length // 0

2.7 Reflect.apply(func, thisArg, args)

Reflect.apply方法相当于Function.prototype.apply.call(func, thisArg, args) ,用于绑定this对象后执行给定函数。

const nums = [1,2,3,4,5];
const min = Math.max.apply(Math, nums);
// 通过 Reflect.apply 调用
const min = Reflect.apply(Math.min, Math, nums);

2.8 Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty方法相当于Object.defineProperty,用来为对象定义属性。

const obj = {};
Object.defineProperty(obj, 'property', {
  value: 0,
  writable: false
});

Reflect.defineProperty(obj, 'property', {
  value: 0,
  writable: false
});

2.9 Reflect.getOwnPropertyDescriptor(target, propertyKey)

获取指定属性的描述对象。

2.10 Reflect.isExtensible (target)

返回一个布尔值,表示当前对象是否可扩展。

2.11 Reflect.preventExtensions(target)

用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。

2.13 Reflect.ownKeys (target)

Reflect.ownKeys方法用于返回对象的所有属性。

const obj = {
  name: 'jack',
  age: 12,
  get userInfo() {
    return this.name + ' age is ' + this.age;
  }
}
Object.getOwnPropertyNames(obj)
Reflect.ownKeys(obj) // ['name', 'age', 'userInfo']

3、JavaScript中Proxy

代理在编程中很有用,它可以在目标对象之前增加一层“拦截”实现一些通用逻辑。

Proxy 构造函数 Proxy(target, handler) 参数:

  • target:代理的目标对象,它可以是任何类型的对象,包括内置的数组,函数,代理对象。
  • handler:它是一个对象,它的属性提供了某些操作发生时的处理函数。
const user = {name: 'hello'}
const proxy = new Proxy(user, {
  get: function(target, property) { // 读取属性时触发
    return 'hi';
  }
});
proxy.name // 'hi'

3.1 Proxy中支持的拦截操作

  • handler.get(target, property, receiver)
  • handler.set(target, property, value, receiver)
  • handler.has(target, property)
  • handler.defineProperty(target, property, descriptor)
  • handler.deleteProperty(target, property)
  • handler.getOwnPropertyDescriptor(target, prop)
  • handler.getPrototypeOf(target)
  • handler.setPrototypeOf(target, prototype)
  • handler.isExtensible(target)
  • handler.ownKeys(target)
  • handler.preventExtensions(target)
  • handler.apply(target, thisArg, argumentsList)
  • handler.construct(target, argumentsList, newTarget)

3.2 get()

用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身,其中最后一个参数可选。

const user = {
  name: 'jack'
}
// 只有属性存在才返回值,否则抛出异常。
const proxy = new Proxy(user, {
  get: function(target, property) {
    if (!(property in target)) {
       throw new ReferenceError(`${property} does not exist.`);
    }
    return target[property];
  }
});
proxy.name // jack
proxy.age // ReferenceError: age does not exist.

我们可以定义一些公共代理对象,然后让子对象继承。

// 只有属性存在才返回值,否则抛出异常。
const proxy = new Proxy({}, {
  get: function(target, property) {
    if (!(property in target)) {
       throw new ReferenceError(`${property} does not exist.`);
    }
    return target[property];
  }
});
let obj = Object.create(proxy);
obj.name = 'hello'
obj.name // hello
obj.age // ReferenceError: age does not exist.

3.3 set()

用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

// 字符类型的属性长度校验
let sizeValidator = {
  set: function(target, property, value, receiver) {
    if (typeof value == 'string' && value.length > 5) {
       throw new RangeError('Cannot exceed 5 character.');
    }
    target[property] = value;
    return true;
  }
};

const validator = new Proxy({}, sizeValidator);
let obj = Object.create(validator);
obj.name = '123456' // RangeError: Cannot exceed 5 character.
obj.age = 12     // 12

3.4 has()

用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。如in运算符。

它接受两个参数,分别是目标对象、需查询的属性名。

const handler = {
  has (target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false

3.5 defineProperty()

defineProperty()方法拦截了Object.defineProperty()操作。

3.6 deleteProperty()

用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

3.7 getOwnPropertyDescriptor()

getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined

3.8 getPrototypeOf()

主要用来拦截获取对象原型,拦截的操作如下:

  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • __proto__
  • Object.prototype.isPrototypeOf()
  • instanceof
const obj = {};
const proto = {};
const handler = {
    getPrototypeOf(target) {
        console.log(target === obj);   // true
        console.log(this === handler); // true
        return proto;
    }
};

const p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto);    // true

3.9 setPrototypeOf()

主要用来拦截Object.setPrototypeOf()方法。

const handlerReturnsFalse = {
    setPrototypeOf(target, newProto) {
        return false;
    }
};

const newProto = {}, target = {};

const p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // returns false

3.10 isExtensible()

方法拦截Object.isExtensible()操作。

const p = new Proxy({}, {
  isExtensible: function(target) {
    console.log('called');
    return true;//也可以return 1;等表示为true的值
  }
});

console.log(Object.isExtensible(p)); // "called"
                                     // true

3.11 ownKeys()

用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环。
const p = new Proxy({}, {
  ownKeys: function(target) {
    console.log('called');
    return ['a', 'b', 'c'];
  }
});

console.log(Object.getOwnPropertyNames(p)); // "called"

3.12 preventExtensions()

用来拦截Object.preventExtensions() 。该方法必须返回一个布尔值,否则会被自动转为布尔值。

这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。

const p = new Proxy({}, {
  preventExtensions: function(target) {
    console.log('called');
    Object.preventExtensions(target);
    return true;
  }
});

console.log(Object.preventExtensions(p)); // "called"
                                          // false

3.13 apply()

apply方法拦截以下操作。

  • proxy(...args)
  • Function.prototype.apply() Function.prototype.call()
  • Reflect.apply()

它接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

const handler = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
};

例子:

const target = function () { };
const handler = {
  apply: function (target, thisArg, argumentsList) {
    console.log('called: ' + argumentsList.join(', '));
    return argumentsList[0] + argumentsList[1] + argumentsList[2];
  }
};

const p = new Proxy(target, handler);
p(1,2,3) // "called: 1, 2, 3" 6

3.14 construct()

用于拦截new命令,下面是拦截对象的写法:

const handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

它方法接受三个参数。

  • target:目标对象。
  • args:构造函数的参数数组。
  • newTarget:创造实例对象时,new命令作用的构造函数。

注意:方法返回的必须是一个对象,目标对象必须是函数,否则就会报错。

const p = new Proxy(function() {}, {
  construct: function(target, argumentsList) {
    return 0;
  }
});

new p() // 返回值不是对象,报错

const p = new Proxy({}, {
  construct: function(target, argumentsList) {
    return {};
  }
});
new p() //目标对象不是函数,报错

4、观察者模式

观察者是一种很常用的模式,它的定义是当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

我们使用Proxy 来实现一个例子,当观察对象状态变化时,让观察函数自动执行。

观察者函数,包裹观察目标,添加观察函数。

  • observable包裹观察目标,返回一个Proxy对象。
  • observe 添加观察函数到队列。
const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
// 属性改变时,自动执行观察函数。
function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

例子:

const user = observable({
  name: 'jack',
  age: 20
});

function userInfo() {
  console.log(`${user.name}, ${user.age}`)
}

observe(userInfo);
user.name = '小明'; // 小明, 20

到此这篇关于前端JavaScript中的反射和代理的文章就介绍到这了,更多相关JavaScript反射和代理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ES6 如何改变JS内置行为的代理与反射

    代理(Proxy)可以拦截并改变 JS 引擎的底层操作,如数据读取.属性定义.函数构造等一系列操作.ES6 通过对这些底层内置对象的代理陷阱和反射函数,让开发者能进一步接近 JS 引擎的能力. 一.代理与反射的基本概念 什么是代理和反射呢? 代理是用来替代另一个对象(target),JS 通过new Proxy()创建一个目标对象的代理,该代理与该目标对象表面上可以被当作同一个对象来对待. 当目标对象上的进行一些特定的底层操作时,代理允许你拦截这些操作并且覆写它,而这原本只是 JS 引擎的内部能

  • 前端JavaScript中的反射和代理

    目录 1.什么是反射 2.JavaScript中Reflect 2.1 Reflect.get(target, name, receiver) 2.2 Reflect.set(target, name, value, receiver) 2.3 Reflect.has(obj, name) 2.4 Reflect.deleteProperty(obj, name) 2.5 Reflect.construct(target, args) 2.6 Reflect.setPrototypeOf(obj

  • 前端JavaScript中的class类

    目录 1.类 1.1 constructor() 1.2 getter和setter 1.3 this 1.4 静态属性 1.5 静态方法 2.继承 2.1 super关键字 2.2 _proto_和prototype 2.3 继承中的__proto__ 2.4 继承实例中的__proto__ 3.小结 1.类 类是用于创建对象的模板.JavaScript中生成对象实例的方法是通过构造函数,这跟主流面向对象语言(java,C# )写法上差异较大,如下: function Point(x, y)

  • 5分钟快速看懂ES6中的反射与代理

    反射:Reflect 反射到底是个什么东西? 它的本质就是一个对象(JS内置的),里边有一些函数可供调用. 为什要叫做反射? 因为它能做的事情,和其他语言(Java.python等)中的反射很相似.其他语言中都叫做反射了,那就共鸣(降低学习成本?) 反射能做什么事情呢? let animal = { name: "dog", age: 3 } Reflect.get(animal, "name") // 传参为:对象, 属性名 // 等同于: animal.name

  • JavaScript中三种for循环语句的使用总结(for、for...in、for...of)

    前言 每个接触JS的开发人员都不可避免的与for循环打交道,毕竟这是遍历必不可少的工具之一.JavaScript 中的 for 循环语句相信大家都已经快用厌了,现在有好多文章都在讲怎么减少代码中的 for 循环语句,但是,你又不得不承认它们真的很有用.今天,我来总结一下前端 JavaScript 中三种 for 循环语句. for 这大概是应用最广的循环语句了吧,简单实用,且大多数时候性能还是在线的,唯一的缺点大概就是太普通,没有特色,导致很多人现在不愿用它. const array = [4,

  • 浅析javascript中的事件代理

    本文的主要内容是根据前不久面试某家公司Web前端开发岗位,面试时做的一道数组去重问题的解题思路进行整理的,分享给大家. 题目本身很简单:一个ul中有一千个li,如何给这一千个li绑定一个鼠标点击事件,当鼠标点击时alert出这个li的内容和li的位置坐标xy, <ul id="ulItem"> <li id="li1">1</li> <li id="li2">2</li> <li

  • Java中反射动态代理接口的详解及实例

    Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

  • javascript中的事件代理初探

    事件在javascript中一直是最强大的对象之一.javascript提供了addEventListener和attachEvent两个方法来为DOM节点绑定事件,jquery作了进一步封装,提供了兼容各个浏览器的bind方法.现在来看,这种传统的事件绑定方式存在着以下不足: 1.可能需要绑定很多的EventHander. 假如页面中某个表有100行,现在必须为每一行绑定一个click事件.那么就必须绑定100个EventHandler,这对页面性能来说有着极大的负担,因为需要创建更多的内存来

  • 详解Java中的反射机制和动态代理

    一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制.通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以,这是一种动态获取类的信息以及动态调用对象方法的能力. 想要使用反射机制,就必须要先获取到该类

  • JavaScript中的依赖注入详解

    计算机编程的世界其实就是一个将简单的部分不断抽象,并将这些抽象组织起来的过程.JavaScript也不例外,在我们使用JavaScript编写应用时,我们是不是都会使用到别人编写的代码,例如一些著名的开源库或者框架.随着我们项目的增长,我们需要依赖的模块变得越来越多,这个时候,如何有效的组织这些模块就成了一个非常重要的问题.依赖注入解决的正是如何有效组织代码依赖模块的问题.你可能在一些框架或者库种听说过"依赖注入"这个词,比如说著名的前端框架AngularJS,依赖注入就是其中一个非常

  • 前端 javascript 实现文件下载的示例

    在 html5 中,a 标签新增了 download 属性,包含该属性的链接被点击时,浏览器会以下载文件方式下载 href 属性上的链接.示例: <a href="https://www.baidu.com" rel="external nofollow" download="baidu.html">下载</a> 1. 前端 js 下载实现与示例 通过 javascript 动态创建一个包含 download 属性的 a

随机推荐