一、defineProperty
1、ES5提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。该方法无法监听数组的变化。
2、Object.defineProperty(obj,prop,descriptor)
- obj:目标对象
- prop:需要定义或修改的属性名
- descriptor:目标属性所拥有的的特性
let obj = { test:"hello"}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
//能不能被删除
configurable: true | false,
//是否可枚举
enumerable: true |false,
value: 任意类型的值,
//是否可写,此配置和get/set不能同时存在
writable: true | false,
get(){},
set(value){}
})
3、案例:实现数据的双向绑定
<body>
<div id="root">
<input type="text" v-model="username">
<hr>
<h3 v-bind='username'></h3>
<input type="text" v-bind="username">
</div>
<script>
// 数据源
let data = {
username: '',
age: 0
};
observer(data);
// 监听数据
function observer(data) {
for (let key in data) {
// 数据代理
definedReactive(data, key, data[key]);
}
}
// 数据代理
function definedReactive(target, key, value) {
Object.defineProperty(target, key, {
get() {
return value;
},
set(newValue) {
if (value !== newValue) { //数据发生了改变,才设置
value = newValue
// 更新视图
updateView(key, value)
}
}
})
}
// 更新视图
function updateView(key, value) {
document.querySelectorAll(`[v-bind='${key}']`).forEach(el => {
if (el.tagName === 'INPUT') {
el.value = value
} else {
el.innerHTML = value
}
})
}
// 给input添加键盘事件
document.querySelectorAll('[v-model]').forEach(el => {
el.addEventListener('keyup', function () {
let key = this.getAttribute('v-model');
data[key] = this.value.trim();
})
})
</script>
</body>
4、案例:实现数据源多层级绑定
let data = Object.freeze({
username: '',
age: 0,
user: { id: 1 }
});
observer(data);
data.user.id = 2000
// 监听数据
function observer(data) {
// 如果对冻结,则不去处理代理
if (Object.isFrozen(data)) return data;
if (typeof data !== 'object') return;
for (let key in data) {
// 数据代理
definedReactive(data, key, data[key]);
}
}
// 数据代理
function definedReactive(target, key, value) {
// 递归
observer(value)
Object.defineProperty(target, key, {
get() {
return value;
},
set(newValue) {
console.log('更新一下视图')
}
})
}
5、案例:es5类中添加获取器和修改器
es5中不存在获取器和修改器,这里可以使用Object.defineProperty来达到类似的效果。
const idKey = Symbol('id');
function Fn(){
this[idKey] = 100;
Object.defineProperty(this,'id',{
get(){
return this[idKey];
},
set(val){
this[idKey] = val;
}
})
}
let fn = new Fn;
fn.id = 1000;
console.log(fn.id);
二、Proxy
1、Proxy是es6提供的新的API,Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Proxy 这个词的原意是代理,用在这里表示用它来“代理”某些操作,可以理解外“代理器”。
2、Proxy的特点:
- Proxy 可以直接监听对象而非属性
- Proxy 可以劫持整个对象,并返回一个新对象,不论是操作便利程度还是底层功能上都远胜于 Object.defineProterty
- Proxy 可以直接监听数组的变化
- Proxy 有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has 等等是Object.property 不具备的
let data = {
id: 100
};
// js以后会把所有的方法,都写在此类中,也会把Object中的方法向此类中移植
let proxy = new Proxy(data, {
// 获取器
get(target, key) {
// return target[key];
return Reflect.get(target, key) || '';
},
// 修改器
set(target, key, value) {
// console.log(target, key, value)
// target[key] = value
// 如果你开启了严格模式,则此处一定要设置返回值,true
// return true;
return Reflect.set(target, key, value);
}
})
proxy.id = 200;
console.log(proxy.id)
3、案例:字符串截取
<body>
<div id="root"></div>
<script>
const root = document.querySelector('#root');
const news = [
{
title: "海外网深一度:气候危机“逼近灾难临界值”,中国行动振奋世界"
},
{
title: "小区不准外卖员入内,杭州一外卖小哥回家被保安拦下!民警到场调解,小哥情绪崩溃"
},
{
title: "网传浙江慈溪上林中学一女生教室内被多次扇耳光 当地回应:已去处置"
}
];
let newsProxy = makeProxyTitle(news)
function makeProxyTitle(data, len = 10) {
return new Proxy(data, {
get(target, key) {
let value = Reflect.get(target, key)
if (typeof value === "object") {
value['title'] = value.title.length > len ? value.title.substr(0, len) + '...' : value.title;
return value;
} else {
return value;
}
},
set(target, key, value) {
if (value !== target[key]) {
return Reflect.set(target, key, value)
}
}
})
}
newsProxy.forEach(item => {
const li = document.createElement('li');
// let title = item.title.length > 10 ? item.title.substr(0, 10) + '...' : item.title
li.innerHTML = item.title;
root.appendChild(li)
})
</script>
</body>
4、案例:proxy双向绑定
<body>
<div id="root">
<input type="text" v-model="username">
<hr>
<h3 v-bind='username'></h3>
<input type="text" v-bind="username">
</div>
<script>
// 数据源
let data = {
username: ''
};
// 监听
let proxyData = observer(data)
function observer(data) {
return new Proxy(data, {
get(target, key) {
return Reflect.get(target, key)
},
set(target, key, value) {
if (value !== target[key]) {
// 更新视图
updateView(key, value)
return Reflect.set(target, key, value)
}
}
})
}
function updateView(key, value) {
document.querySelectorAll(`[v-bind='${key}']`).forEach(el => {
if (el.tagName === 'INPUT') {
el.value = value
} else {
el.innerHTML = value
}
})
}
// 给input添加键盘事件
document.querySelectorAll('[v-model]').forEach(el => {
el.addEventListener('keyup', function () {
let key = this.getAttribute('v-model');
proxyData[key] = this.value.trim();
})
})
</script>
</body>