在目标对象之前架设了一层拦截,当外界访问这个对象的时候,都必须经过这层拦截,相当于是提供一个机制对于外界的访问,进行一个过滤或者改写

ES5 中的代理 defineProperty

Object.defineProperty() 方法中包含三个参数:拦截的对象,拦截的对象中的属性, 拦截的方法

  1. let obj = {}
  2. Object.defineProperty(obj, 'name', {
  3. get(){
  4. console.log('get')
  5. return 'imooc'
  6. },
  7. set(){
  8. console.log('set')
  9. }
  10. })
  11. console.log('get方法', obj.name)
  12. obj.name = 'es6'

image.png
调用set() 方法时,一定要注意,不能设置对象下已经存在的属性,这样会导致死循环,因为set()方法会一直被触发

ES6中的代理 Proxy

new Proxy() 第一个参数为包装的对象,第二个参数为存放方法的对象,主要处理过滤或者改写,即 get() / set()

let obj = {}
let p = new Proxy(obj, {})
p.name = 'imooc'
console.log(obj.name) // imooc
let arr = [7,8,9]
arr = new Proxy(arr, {
  get(target, prop) {
    console.log(target, prop)
    // 判断传入的参数是否存在数组中,不存在返回error
    return prop in target ? target[prop] : 'error'
  }
})
console.log(arr[1])
console.log(arr[10])

image.png
当我们想要获取我们所代理的对象中的值时,会自动调用 proxy中设置的 get() 方法,我们可以在 get() 中写一些业务处理的方法

同样对于 对象类型数据的 代理和 数组的代理道理类似

get() 拦截属性读取的钩子函数

创建一个字典类型的业务,如果字典中有该字段对应的数据,则返回对应数据,若没有则返回输入的数据

let dict = {
  'hello': '你好',
  'world': '世界'
}
dict = new Proxy(dict, {
  get(target, prop) {
    return prop in target ? target[prop] : prop
  }
})

console.log(dict['world']) // 世界
console.log(dict['imooc']) // imooc

set() 拦截属性设置的钩子函数

会返回一个布尔值, proxy.foo = v 或者 proxy['foo'] = v 的时候会触发
业务描述:一个数组中的元素只能是Number类型,设置值的时候需要进行类型检查,非Number类型,抛出错误

let arr = []
arr = new Proxy(arr, {
  set(target, prop, val) {
    if (typeof val == 'number') {
      target[prop] = val;
      return true;
    } else {
      return false
    }
  }
})
arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length) // 5 6 2

使用代理方法的时候,并不会破坏目标对象原有的一些属性和方法

has() 拦截in()操作的钩子函数

判断一个key 是否存在 对象中,并返回一个 boolean 类型的值

业务场景:判断一个数据是否在某个范围内

let range = {
  start: 1,
  end: 5
}

range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end
  }
})
console.log(2 in range) // true
console.log(9 in range) // false

ownKeys() 拦截循环遍历的钩子函数

Object.keys(),for … in , Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 这些方法时会触发, 并且返回一个数组
业务场景: 如果对象中有某个属性是私有属性,不想被外人访问

let userInfo = {
  username: '潜龙',
  age: 24,
  _password: '***'
}

userInfo = new Proxy(userInfo, {
  ownKeys(target) {
    // target 表示当前被 代理的对象
    // 屏蔽下划线开头的属性
    return Object.keys(target).filter(key => !key.startsWith('_'))
  }
})

for (let key in userInfo) {
  console.log(key) // username, age
}
console.log(Object.keys(userInfo)) // (2) ["username", "age"]

deleteProperty() 拦截删除的钩子函数

let user = {
  name: '潜龙',
  age: 24,
  _password: '***'
}
user = new Proxy(user, {
  deleteProperty(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('不可删除')
    } else {
      delete target[prop]
      return true
    }
  }
})
try{
  delete user.age
  delete user._password
}catch (e){
  console.log(e.message) // 不可删除
}
console.log(user) // Proxy{name: "潜龙", _password: "***"}

apply() 拦截函数调用的钩子函数

函数的 方法调用,以及 call() apply() 方法都会触发,这个钩子函数
可以用这个钩子函数来修改函数的一些返回值

let sum = (...args) => {
  let num = 0
  args.forEach(item => {
    num += item
  });
  return num
};

sum = new Proxy(sum, {
  // 目标对象, 上下文, 参数数组
  apply(target, ctx, args) {
    return target(...args) * 2
  }
})
console.log(sum(1, 2)); // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null,[ 1, 2, 3])) // 12

construct() 拦截new命令的钩子函数

主要处理,实例化对象过程中的事务, 并且这个钩子函数必须要返回一个对象

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

User = new Proxy(User, {
  // proxy包裹的对象,传入的参数数组,new实例化的对象
  construct(target, argArray, newTarget) {
    console.log('construct')
    return new target(...argArray)
  }
})
console.log(new User('imooc')) // User{name: "imooc"}

综合应用

get() 配合私有属性

let user = {
  name: '潜龙',
  age: 24,
  _password: '***'
}

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith("_")) {
      throw new Error('不可访问')
    }else{
      return target[prop]
    }
  }
})
console.log(user.age);
console.log(user._password);

image.png

set() 配合私有属性

let user = {
  name: '潜龙',
  age: 24,
  _password: '***'
}

user = new Proxy(user, {
  set(target, prop, val) {
    if (prop.startsWith('_')) {
      throw new Error('不可访问');
    } else {
      target[prop] = val
      return true
    }
  }
})

user.age = 25
console.log(user.age)
try {
  user._password = '----'
} catch (error){
  console.log(error.message)  // 不可访问
}

image.png