defineProperty和Proxy数据劫持

前言

在js中常见的数据劫持有两种,一种是Object.definePropert,在Vue2.*版本中作为数据双向绑定的基础;另一种是ES2015中新增的Proxy,即将在Vue3中做数据数据双向绑定的基础

严格来讲Proxy应该被称为『代理』而非『劫持』,不过由于作用有很多相似之处,我们在下文中就不再做区分,统一叫『劫持』。
基于数据劫持的当然还有已经凉透的Object.observe方法,已被废弃。

Object.definePropert

在搞清楚Object.definePropert之前我们先要了解一下Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

  1. 写法:Object.getOwnPropertyDescriptor(obj, prop)
  2. 参数:obj-需要查找的目标对象;prop-目标对象内属性名称
  3. 返回值:如果指定的属性存在于对象上,则返回其属性描述符对象(property descriptor),否则返回 undefined。
{
    configurable: true,    // 属性是否可以被操作,比如删除。 默认true
    enumerable: true,      // 检测的属性值是否可以被更改,默认是true
    value: 2,              // 该属性的值
    writable: true,        // 当且仅当指定对象的属性可以被枚举出时,默认true。
}

然后我们在使用definePropert做一些劫持,了解一下configurable,enumerable,value,writable的作用

// value
let obj ={
  a: 123,
  b: 234,
  c: function() {
    console.log('do ...')
  }
}
Object.defineProperty(obj, 'b', {
  value: 1214341
})
console.log(obj.b) // 1214341

// writable
let obj ={
  a: 123,
  b: 234,
  c: function() {
    console.log('do ...')
  }
}
Object.defineProperty(obj, 'b', {
  writable: false
})
obj.b = 'jsbin'
console.log(obj.b)  // 234

// configurable
let obj ={
  b: 234
}
Object.defineProperty(obj, 'b', {
  configurable: false
})
delete obj.b
console.log(obj.b) // 234

// enumerable
let obj = {
  b: 123,
  c: 456,
  fn: function () {}
}
Object.defineProperty(obj, 'b', {
  enumerable: false,
})
for(let key in obj) {
  console.log(`key-----${obj[key]}`)
}

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

  1. 语法:Object.defineProperty(obj, prop, descriptor)
  2. 参数:obj-要在其上定义属性的对象, prop-要定义或修改的属性的名称,descriptor- 将被定义或修改的属性描述符
{
    enumerable: true,      // 检测的属性值是否可以被更改,默认是true
    configurable: true,    // 属性是否可以被操作,比如删除。 默认true  
    get: function(){},     // 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined
    set: function(){}      // 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined
}

我们在上述阐述的defineProperty和getOwnPropertyDescriptor的返回值,我们统称为“属性描述符”

对象里目前存在的属性描述符有两种主要形式:==数据描述符==和==存取描述符==。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

image
let obj = {
  b: 123,
  c: 456,
  fn: function () {}
}
let _newValue = obj.b
Object.defineProperty(obj, 'b', {    // 使用该方法get,set必须同事存在
  enumerable: true,
  configurable: true,
  writable: true,
  get: function(){
    return _newValue
  },
  set: function(newValue){
    return _newValue = newValue
  }
})

obj.b = 90
console.log(obj.b)

上面代码执行结果如下:


image

就是说数据描述符中不能出现get,set;存取描述符中不能出现writable;并且在==存取描述中get和set要同时出现==;如果没有了get则访问别劫持的对象属性会显undefined;反之set方法没有,设置对象属性值不会生效

let obj = {
  b: 123,
}
let _newValue = obj.b
Object.defineProperty(obj, 'b', { 
  enumerable: true,
  configurable: true,
  set: function(newValue){
    return _newValue = newValue
  }
})

console.log(obj.b)   // undefined

Object.defineProperty(obj, 'b', { 
  enumerable: true,
  configurable: true,
  get: function(){
    return _newValue
  },
})
obj.b = 90
console.log(obj.b) // 123

数据劫持实现简版数据双向绑定

/**
 * 遍历所有属性
 * @param {Object} data 遍历对象
 */
 function observe(data) {
  if (!data || typeof data !== 'object') {
    return;
  }
  Object.keys(data).forEach(function (key) {
    defineReactive(data, key, data[key]);
  });
}

/**
 * 劫持监听数据
 * @param {Object} data 监听对象
 * @param {String} key 对象键名
 * @param {String, Number} val  对象键值
 */
function defineReactive(data, key, val) {
  observe(val);  // 如果子属性为object也进行遍历监听
  Object.defineProperty(data, key, {
    configurable: false,
    enumerable: true,
    get: function () {
      //在Watcher初始化实例的时候回触发对应属性的get函数
      return val
    },
    set: function (newValue) {
      if (val === newValue) {
        return
      }
      val = newValue
      rander(val)
    }
  })
}

function rander(value) {
  let dom = document.getElementById('app')
  console.log(value)
  dom.innerHTML = value
}

let obj = {
  b: 'I am jsbin'
}

observe(obj)
rander(obj.b)

由上面的例子可以看出,使用defineProperty做数据劫持实现数据双向绑定,要做被检测对象的循环处理,且无法实现数组的检测绑定,检测数组则使用装饰着模式

let arrOld = Array.prototype
let arrC = Object.create(arrOld)
let arr = ['push']
// 装饰者模式
arr.forEach(function(method) {
  arrC[method] = function() {
    console.log('监听到数据')
    return arrOld[method].apply(this, arguments);
  }
});
function rander(value) {
  let dom = document.getElementById('app')
  console.log(value)
  dom.innerHTML = value
}
let arrinfo = [1,2,3]
arrinfo.__proto__ = arrC

Proxy

Proxy 可以理解成在目标对象之前进行拦截,访问该对象属性需要先过拦截这一步骤。因此提供了一种机制,可以对外界的访问进行过滤和读写。

  1. 官方定义: Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
  2. 基本语法: let p = new Proxy(target, handler);
  3. 参数
target: 需要伪装(代理)的数据,该数据可以是任何类型的的对象,原生数组函数,也可以是另一个代理
handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器,理解为过滤数据的方法)
 *   handler.apply()
 *   handler.construct()
 *   handler.defineProperty()
 *   handler.deleteProperty()
 *   handler.enumerate()
 *   handler.get()
 *   handler.getOwnPropertyDescriptor()
 *   handler.getPrototypeOf()
 *   handler.has()
 *   handler.isExtensible()
 *   handler.ownKeys()
 *   handler.preventExtensions()
 *   handler.set()
 *   handler.setPrototypeOf()
//  目标对象
let people = {
    name: 'jsBin',
    age: 18,
}

// handler拦截(伪装)数据的方法
let handler = {
    /**
     * handler.get() 方法用于拦截对象的读取属性操作。
     * @param {Any} target 目标数据
     * @param {String} property 被获取的属性名
     * @param {Object} receiver Proxy或者继承Proxy的对象
     */
    get: function(target, property, receiver)
    {
        switch (property) {
            case 'name': return 'name:' + target[property]; break;
            case 'age': return 'age:' + target[property]; break;
            default: return '这个值没有定义 undefined' 
        }
    },


    /**
     * handler.set() 方法用于拦截设置属性值的操作
     * @param {*} target 目标数据
     * @param {*} property 被设置的属性名
     * @param {*} value 被设置的新值
     * @param {*} receiver 最初被调用的对象。通常是proxy本身,但handler的set方法也有可能在原型链上或以其他方式被间接地调用(因此不一定是proxy本身)
     */
    set: function(target, property, value, receiver)
    {
        if(property === 'age' && typeof value !== "number") {
            console.log('传入数据格式不真确')
        } else {
            console.log(arguments)
            return Reflect.set(...arguments)
        }
    }
}

let p = new Proxy(people, handler)
p.age = 4324
console.log(p.age)

问题1:对于对象检测只能检测一层

问题2:监听数组,使用数组方法触发2次

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

推荐阅读更多精彩内容

  • defineProperty() 学习书籍《ECMAScript 6 入门 》 Proxy Proxy 用于修改某...
    Bui_vlee阅读 668评论 0 1
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 11,077评论 6 13
  • 一、Proxy概述 Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(me...
    傑仔阅读 8,428评论 0 8
  • Proxy 概述 Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(met...
    pauljun阅读 3,258评论 0 1
  • 这个世界上钱真的很多,赚钱的路子也很多,打工卖时间是最难最低效的赚钱方法,绝大多数人必须从中跳出来才能实现财富的快...
    王玉增之成长阅读 438评论 0 1