Skip to content
On this page

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

js
// es5 使用 Object.defineProperty 拦截对象
let obj = {}
let newVal = ''
Object.defineProperty(obj, 'name', {
	get() {
		console.log('--get')
		return newVal
	},
	set(val) {
		console.log('--set')
		// this.name = val // VM2132:8 Uncaught RangeError: Maximum call stack size exceeded
		newVal = val
	}
})
console.log(obj.name)
obj.name = 'es'
console.log(obj.name)

// ------------------------------------------ //
let obj = {}
let p = new Proxy(obj, {})
p.name = 'hello'
console.log(obj.name)
for(let key in obj) {
	console.log(key) // name
}



一些钩子方法:get set has ownKeys deleteProperty apply construct 。

js
// get
let arr = [1,2,3]
arr = new Proxy(arr, {
	get(target, prop) {
		console.log(target, prop)
		return prop in target ? target[prop] : null
	}
})
console.log(arr[3])

// set
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)
console.log(arr[0], arr) // 5  Proxy {0: 5}

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

// ownKeys
let obj = {
	name: 'es6',
	[Symbol('es')]: 'es6'
}
Object.getOwnPropertyNames(obj) // ['name']
Object.getOwnPropertySymbols(obj) // [Symbol(es)]
Object.keys(obj) // ['name']
for(let key in obj) {
	console.log(key) // name
}
// ownKeys 能实现对对象的 key 进行拦截
// 场景:对 _password 隐藏
let user = {
	name: 'lisi',
	age: 22,
	_password: '***'
}
for(let key in user) {
	console.log(key) // name age _password
}
userInfo = new Proxy(user, {
	ownKeys(target) {
		return Object.keys(target)
			.filter(key => !key.startsWith('_'))
	}
})
for(let key in userInfo) {
	console.log(key) // name age
}
Object.getOwnPropertyNames(obj) // ['name', 'age']
Object.keys(obj) // ['name', 'age']

// 删除 deleteProperty
let user = {
	name: 'lisi',
	age: 22,
	_password: '***'
}
userInfo = new Proxy(user, {
	get(target, prop) {
		if(prop.startsWith('_')) {
			throw new Error('不可访问')
		} else {
			return target[prop]
		}
	},
	set(target, prop, val) {
		if(prop.startsWith('_')) {
			throw new Error('不可设置')
		} else {
			target[prop] = val
			return true
		}
	},
	deleteProperty(target, prop) {
		if(prop.startsWith('_')) {
			throw new Error('不可删除')
		} else {
			delete target[prop]
			return true
		}
	},
	ownKeys(target) {
		return Object.keys(target)
			.filter(key => !key.startsWith('_'))
	}
})
userInfo._password = '123' // VM2654:16 Uncaught Error: 不可设置
console.log(userInfo._password) // Uncaught Error: 不可访问
delete userInfo._password // Uncaught Error: 不可删除

// apply 
let sum = (...args) => {
	let num = 0
	args.forEach(i => num += i)
	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)) // 6
console.log(sum.apply(null, [1, 2])) // 6

// construct 拦截 new 操作符
let User = class {
	constructor(name) {
		this.name = name
	}
}
User = new Proxy(User, {
	construct(target, args, newTarget) {
		return new target(...args)
	}
})
console.log(new User('imooc')) // User {name: 'imooc'}

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。 将 Object 属于语言内部的方法放到 Reflect 上;修改某些 Object 方法的返回结果,让其变的更合理;让 Object 操作变成函数行为;Reflect 对象的方法与 Proxy 对象的方法一一对应。

js
// 将 Object 属于语言内部的方法放到 Reflect 上
let obj = {}
let newVal = ''
Reflect.defineProperty(obj, 'name', {
	get() {
		console.log('--get')
		return newVal
	},
	set(val) {
		console.log('--set')
		// this.name = val // VM2132:8 Uncaught RangeError: Maximum call stack size exceeded
		newVal = val
	}
})
console.log(obj.name)
obj.name = 'es'
console.log(obj.name)

// 修改某些 Object 方法的返回结果
// 当 Object.defineProperty 某些属性无法定义会抛出异常,使用 try catch 包裹,不想使用 try catch 怎么办?用 Reflect.defineProperty,它返回值是 boolean 类型
if(Reflect.defineProperty()) {} else {}

// 让 Object 操作变成函数行为
console.log('assign' in Object) // true
console.log(Reflect.has(Object, 'assign')) // true

// Reflect 对象的方法与 Proxy 对象的方法一一对应
let user = {
	name: 'lisi',
	age: 22,
	_password: '***'
}
userInfo = new Proxy(user, {
	get(target, prop) {
		if(prop.startsWith('_')) {
			throw new Error('不可访问')
		} else {
			return Reflect.get(target, prop)
		}
	},
	set(target, prop, val) {
		if(prop.startsWith('_')) {
			throw new Error('不可设置')
		} else {
			Reflect.set(target, prop, val)
			return true
		}
	},
	deleteProperty(target, prop) {
		if(prop.startsWith('_')) {
			throw new Error('不可删除')
		} else {
			delete Reflect.deleteProperty(target, prop)
			return true
		}
	},
	ownKeys(target) {
		return Reflect.ownKeys(target)
			.filter(key => !key.startsWith('_'))
	}
})
// userInfo._password = '123' // VM2654:16 Uncaught Error: 不可设置
// console.log(userInfo._password) // Uncaught Error: 不可访问
delete userInfo.age

// apply
let sum = (...args) => {
	let num = 0
	args.forEach(i => num += i)
	return num
}
sum = new Proxy(sum, {
	apply(target, ctx, args) {
		return Reflect.apply(target, target, [...args]) * 2
	}
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2)) // 6
console.log(sum.apply(null, [1, 2])) // 6

Proxy 和 Reflect 差别

比较 Reflect 和 Object 方法 - JavaScript | MDN (mozilla.org)

Released under the MIT License.