资讯专栏INFORMATION COLUMN

ES6-Proxy与数据劫持(12)

li21 / 3323人阅读

摘要:我们先以框架出发,探索其中数据劫持的奥秘。针对对象在数据劫持这个问题上,可以被认为是的升级版。技术支持监测数组的等方法操作,支持对象属性的动态添加和删除,极大的简化了响应化的代码量。

随着前端界的空前繁荣,各种框架横空出世,包括各类mvvm框架百家争鸣,比如Anglar、Vue、React等等,它们最大的优点就是可以实现数据绑定,再也不需要手动进行DOM操作了,它们实现的原理也基本上是脏检查或数据劫持。我们先以Vue框架出发,探索其中数据劫持的奥秘。

Vue 2.0的版本所使用的数据劫持,说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情,举个栗子:

var data = {
    name:"xiaoming"
}

Object.keys(data).forEach(function(key){
    Object.defineProperty(data,key,{
        get:function(){
            console.log("get");
        },
        set:function(){
            console.log("监听到数据发生了变化");
        }
    })
});
data.name //控制台会打印出 “get”
data.name = "xiaohong" //控制台会打印出 "监听到数据发生了变化"

但是有没有比Object.defineProperty更好的实现方式呢?

答案是肯定的有,那就是我们今天的主人公:Proxy

1、Proxy简介

Proxy这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器。

也可以理解成在目标对象之前设置一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

在生活中,代理模式的场景是十分常见的,例如我们现在如果有购买海外产品(给女朋友买一个LV的包包,前提是你要先有个女朋友,^_^)的需求,更多的是去找代购中介机构,而不是直接去国外买。此时,代购起到的作用就是代理的作用。


Proxy构造函数能够让我们轻松的使用代理模式:

var proxy = new Proxy(target, handler);

Proxy构造函数中有两个参数:

target是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。

讲的通俗点,如何让代购帮你买LV的包包呢?

首先,你需要告诉代购你看好了哪款包包,这个款式就是Proxy里的第一个参数target。

其次就是制定购买策略,例如国外比国内便宜20%,就买2个,便宜40%,就买4个,这个策略就是第二个参数handle。

2、Proxy中的处理方法

Proxy有13种数据劫持的操作,那是相当的强大:

2.1 get方法

get方法是在你得到某对象属性值时预处理的方法,接受两个常用参数

target:得到的目标值

key:目标的key值,相当于对象的属性

可以代购来模拟handle中的get方法,如下

var Bao = {
      name: "LV",
    price:9999,
};
var proxyBao = new Proxy(Bao, {
    get: function(target, key) {
        if (target["price"]>5000) {
          return "超出客户心理价位,不买了";
        } else {
          return "符合客户心理预期,买买买";
        }
    }
});
proxyBao.price
//"超出客户心理价位,不买了"

解释一下:客户想买一个LV的包,心理价位是5000,把购买目标和需求都告诉了代购,代购询问了下国外的价格,这款LV的包是9999,超出了客户的心理价位,于是不买了。

2.2 set方法

set方法用来拦截某个属性的赋值操作,可以接受四个参数

target:目标值。

key:目标的Key值。

value:要改变的值。

receiver:改变前的原始值。

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求。

let validator = {
  set: function(target, key, value) {
    if (key === "age") {
      if (!Number.isInteger(value)) {
        throw new TypeError("The age is not an integer");
      }
      if (value > 200) {
        throw new RangeError("The age seems invalid");
      }
    }

    // 对于满足条件的 age 属性以及其他属性,直接保存
    target[key] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = "young" // 报错 The age is not an integer
person.age = 300     // 报错 The age seems invalid

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。

3、Proxy相比Object.defineProperty的优势

3.1 支持数组

let arr = [1,2,3]
let proxy = new Proxy(arr, {
    get (target, key, receiver) {
        console.log("get", key)
        return Reflect.get(target, key, receiver)
    },
    set (target, key, value, receiver) {
        console.log("set", key, value)
        return Reflect.set(target, key, value, receiver)
    }
})
proxy.push(4)
// 能够打印出很多内容
// get push     (寻找 proxy.push 方法)
// get length   (获取当前的 length)
// set 3 4      (设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)

Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。

3.2 针对对象

在数据劫持这个问题上,Proxy 可以被认为是 Object.defineProperty() 的升级版。外界对某个对象的访问,都必须经过这层拦截。因此它是针对 整个对象,而不是 对象的某个属性,所以也就不需要对 keys 进行遍历。

let obj = {
  name: "Eason",
  age: 30
}
let handler = {
  get (target, key, receiver) {
    console.log("get", key)
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    console.log("set", key, value)
    return Reflect.set(target, key, value, receiver)
  }
}
let proxy = new Proxy(obj, handler)
proxy.name = "Zoe" // set name Zoe
proxy.age = 18 // set age 18

3.3 嵌套支持

本质上,Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是一样的。因此也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回,代码如下:

let obj = {
  info: {
    name: "eason",
    blogs: ["webpack", "babel", "cache"]
  }
}
let handler = {
  get (target, key, receiver) {
    console.log("get", key)
    // 递归创建并返回
    if (typeof target[key] === "object" && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    console.log("set", key, value)
    return Reflect.set(target, key, value, receiver)
  }
}
let proxy = new Proxy(obj, handler)
// 以下两句都能够进入 set
proxy.info.name = "Zoe"
proxy.info.blogs.push("proxy")

4、应用实例

4.1 使用Proxy实现表单校验

let person = {
    name: "xiaoming",
    age: 30
}
let handler = {
    set (target, key, value, receiver) {
      if (key === "name" && typeof value !== "string") {
        throw new Error("用户姓名必须是字符串类型")
      }
      if (key === "age" && typeof value !== "number") {
        throw new Error("用户年龄必须是数字类型")
      }
      return Reflect.set(target, key, value, receiver)
    }
}
let boy = new Proxy(person, handler)
boy.name = "xiaohong" // OK
boy.age = "18" // 报错  用户年龄必须是数字类型

5、总结

Proxy本质上属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。

通俗的说Proxy在数据外层套了个壳,然后通过这层壳访问内部的数据,就像下面的图:


Proxy让JS开发者很方便的使用代理模式,使函数更加强大,业务逻辑更加清楚。

Proxy 不但可以取代 Object.defineProperty 并且还扩增了非常多的功能。Proxy 技术支持监测数组的 push 等方法操作,支持对象属性的动态添加和删除,极大的简化了响应化的代码量。Vue 3.0的也会使用Proxy去实现部分核心代码。

在业务开发时应该注意Proxy使用场景,当对象的功能变得复杂或者我们需要进行一定的访问限制时,便可以考虑使用代理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109557.html

相关文章

  • 初入ES6-Proxy和Reflect

    摘要:用于修改某些操作的默认行为和访问器属性的行为类似在对象的前面多一层代理,对象字面量中定义属性的特性方法,访问器属性此时属性被定义为访问器属性不一样的写法中是用代理的写法第一个参数是对象,第二个是要操作的方法对象也有两个属性,一个是目标对象, 1,Proxy用于修改某些操作的默认行为和访问器属性的行为类似,在对象的前面多一层代理, const obj = { ...

    BakerJ 评论0 收藏0
  • 经历不可抗力是一种什么体验

    摘要:郑昀最后更新于知己知彼,百战不殆,了解一下过去那几年我们所经历过的各种不可抗离奇事件吧。金山客服表示,今天处理问题与昨天处理的问题是同一个问题在两个不同软件中的体现,所以需要进行两次处理。 郑昀 最后更新于2015/1/29showImg(https://segmentfault.com/img/bVLOtb?w=846&h=832);知己知彼,百战不殆,了解一下过去那几年我们所经历过...

    hightopo 评论0 收藏0
  • 全球HTTPS时代已来,你跟上了吗?

    摘要:而未来的互联网网络链路日趋复杂,加重了安全事件发生。苹果强制开启标准苹果宣布年月日起,所有提交到的必须强制开启安全标准,所有连接必须使用加密。最后是安全意识。 互联网发展20多年,大家都习惯了在浏览器地址里输入HTTP格式的网址。但前两年,HTTPS逐渐取代HTTP,成为传输协议界的新宠。​早在2014年,由网际网路安全研究组织Internet Security Research Gr...

    hss01248 评论0 收藏0
  • 从函数劫持角度看开发调试工具AlloyLever

    摘要:第四行是为了保存当前语境下的,事实上在浏览器的调试工具中直接运行这些代码的时候,这个指向的就是全局对象,所以去掉这一行,将下面的改成,程序的运行结果是一模一样的。 在腾讯的AlloyTeam的Blog上发现了这样的一款工具:AlloyLever(原blog地址:http://www.alloyteam.com/2016...),觉得非常有趣且实用。尤其是其实现的原理也并不复杂,却可以给...

    xiongzenghui 评论0 收藏0
  • 浅谈前端安全

    摘要:安全问题的分类按照所发生的区域分类后端安全问题所有发生在后端服务器应用服务当中的安全问题前端安全问题所有发生在浏览器单页面应用页面当中的安全问题按照团队中哪个角色最适合来修复安全问题分类后端安全问题针对这个安全问题,后端最适合来修复前端安全 安全问题的分类 按照所发生的区域分类 后端安全问题:所有发生在后端服务器、应用、服务当中的安全问题 前端安全问题:所有发生在浏览器、单页面应用、...

    Cympros 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<