Object.create
- Object.create和Object.setPrototypeOf的区别?
1.Object.create
// 第一个参数为原型链上的属性
// 第二个属性为实例上的属性
var obj = Object.create({protytypeProp: 666}, {
p1: {
value: 123,
enumerable: true,
configurable: true,
writable: true,
},
p2: {
value: 'abc',
enumerable: true,
configurable: true,
writable: true,
}
});
// Object.create实现
function create (prototypes) {
const noop = function () {}
noop.prototype = prototypes
return new noop()
}
2.Object.setPrototypeOf
// .Object.setPrototypeOf将一个对象设置为另一个对象的原型对象。
var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);
Object.getPrototypeOf(a) === b // true
a.x // 1
proxy
- proxy是ES5中Object.defineProperty的升级版本。
- proxy可以设置在原型链上。
- 原来的属性描述符会影响proxy的使用,如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修 改该属性,否则通过 Proxy 对象访问该属性会报错。
- proxy可以判断数据合法性、读取属性名是否为下划线开头。如果是就跑错,模拟私有属性。
- proxy拦截方法一共有十三种。
- proxy不仅可以代理对象,还可以代理很多类型对数据,比如函数,对应的拦截方法就是apply。
- get有三个参数:第一个是原对象,第二个是访问的属性名,第三个是proxy实例。
- set有四个参数:第一个是原对象,第二个是访问属性名,第三个是设置的属性值,第四个是proxy实例。
- has只拦截in运算符,不拦截for...in
- 在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。需要绑定原始对象this来解决。
- 一个应用的例子
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl+'/' + propKey);
}
});
}
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});
defineProperty
// 语法
Object.defineProperty(obj, prop, descriptor)
descriptor包含了一些属性描述符,属性描述符包含以下几种
value、writeable、configurable、enumerable、get、set
value或writable不能和set或get同时出现。
function update () {
console.log('更新视图')
}
let data = {
name: '吴晗君',
age: 18,
friends: [{aa:1}, 2, 3, 4, 5],
address:{
location:'浦江'
}
}
function observer (data) {
if (typeof data !== 'object') return data
for (let k in data) {
// 在这里调用observer的目的是:
// 1. 当data[k]的类型是对象时,需要递归遍历,修改把该对象的各属性值的存取描述符set和get。
// 2. 同时需要将对象本身的存取描述符也进行更改(这一步在下面做,和此处调用observer无关)
// 3. 其实只需要当值为对象时候,才需要调用,但是observer函数开头有判断,值不是对象就直接返回。所以在这里就不判断了。
observer(data[k])
// 将值缓存到一个变量上,这样在get方法中直接返回该变量,而不是返回data[k],如果是后者,会造成循环引用,造成栈溢出。
// 在这里更好的方式就是将下面的代码封装成一个函数,将data[k]作为参数传进去,就不用这一步了。
let value = data[k]
Object.defineProperty(data, k, {
set (newValue) {
update()
observer(newValue)
value = newValue
},
get () {
return value
}
})
}
}
observer(data)
data.address = {
newLocation: '杭州'
}
const arrMethods = [
'traverse', 'push',
'pop', 'shift',
'unshift', 'sort',
'join', 'splice',
'slice', 'map',
'forEach', 'filter',
'some', 'every',
'reduce', 'reduceRight',
'concat', 'indexOf',
'lastIndexOf'
]
arrMethods.forEach((method) => {
let oldMethod = Array.prototype[method]
Array.prototype[method] = function (...args) {
update()
oldMethod.call(this, ...args)
}
})
let arr = [1, 2, 3]
arr.unshift({
first: 666
})
Object.defineProperty的缺点,不能监控数组长度的变化,对原生方法的监控也需要进行重写才能监控。
遗留问题
- 为什么Proxy严格模式下set方法内要返回true??
- proxy在框架源码中有哪些应用?
- 重写方法后,如push方法传进的值还是需要用属性描述符转换。要不然对该值进行更改不会触发update。