对于实战使用到Object.defineProperty大家想到的都是数据双向绑定,数据劫持等等,或者现在实现的vue主流框架实现,3.0现在改用proxy了,这个我们后面也会对比分析。我们先从最简单的通过属性方式实现的一些实战。
使用ES5实现const
首先大家肯定都知道 ES6 中 const 是用来声明一个常量的,一旦声明则常量的值就不可改变
eg:
const Pi = 3.1415926
Pi = 3
// Assignment to constant variable
const Obj = {}
Obj.name = 'chencc'
Obj = {}
// Assignment to constant variable
通过上面大家可以看到,如果是通过 const 给变量赋值基本类型,就不可改变了,但是给变量赋值引用类型,是不允许改变变量指向引用类型的地址值,但是可以改变改引用类型内部的属性值。
这时候我们就要思考如何通过 defineProperty 来实现 const 了。因为上一节分析了 defineProperty 中的 configure 和 writable 属性,知道可以通过这些属性可以控制对象内变量属性是否可改变,可删除,通过这个思路我们继续往下。 eg1:
var _const = {}
Object.defineProperty(_const, 'name', {
value: 'chencc',
writable: false,
configurable: true,
enumerable: true
})
console.log(_const.name) // 'chencc'
_const.name = 'chenhh' // 无法修改,因为writable为false
这一步我们已经实现了数据不可以改变,但是我们还是可以通过 defineProperty 来修改数据描述符来修改属性值,所以我们还要进行下一步修改,同时我们还发现 delete 是无法删除 const 变量的。所以我们要修改下 configurable 配置。
eg2:
var _const = {}
Object.defineProperty(_const, 'name', {
value: 'chencc',
writable: false,
configurable: false,
enumerable: true
})
console.log(_const.name) // 'chencc'
_const.name = 'chenhh' // 无法修改
Object.defineProperty(_const, 'name', {
value: 'chenhh',
writable: true,
configurable: true,
enumerable: true
})
// Cannot redefine property: name
所以最后可以简单模拟实现一个 const 的效果
eg3:
var _const = {}
Object.defineProperty(_const, 'name', {
value: 'chencc',
enumerable: true
})
Object.defineProperty(_const, 'child', {
value: {a: 1, b: 2},
enumerable: true
})
上面我们是通过 writable 和 configurable 属性共同控制实现的一个简单的 const 的效果,同时我们还可以通过 get 和 set 数据劫持的方式,来判断数据是否改变的方式来实现一个 const。
eg4:
var _const = {}
var temp = 'chencc'
Object.defineProperty(_const, 'name', {
enumerable: true,
get: function () {
return temp
},
set: function (value) {
if (temp !== value) {
console.log('Assignment to constant variable')
} else {
return value
}
}
})
实现双向数据绑定
基础概念: 双向数据绑定大家应该非常熟悉了吧,简单来说就是当一个对象的属性发生改变,对应调用这个属性的地方显示也会改变,也就是模型到视图 (model => view)。同时调用属性的这个地方改变了,那么这个对象属性也会发生改变,即视图到模型 (view => model)。
eg1:
var Obj = {}
var defaultName = 'chencc'
Object.defineProperty(Obj, 'name', {
get: function () {
console.log('获取 value 值')
return defaultName
},
set: function (value) {
console.log('设置 value 值')
defaultName - value
}
})
console.log(Obj.name)
// 获取 value 值
// 'chencc'
Obj.name = 'chenhh'
console.log(Obj.name)
// 设置 value 值
// 'chenhh'
从上面我们可以发现,每当我们获取name 的值的时候,get方法就被调用,当我们给 name 赋值的时候,set 方法就被调用,我们通过这个方式,就可以对 Obj.name 属性进行监控。
如果 input 框内的值是 Obj.name 的值,那么,我们设置值的时候给 input 元素设置下新的值,即可实现模型到视图了
HTML
<input type="text" id="model" />
<div id="modelText"></div>
JavaScript
var Obj = {}
var defaultName = 'chencc'
// 给 input 设置值
document.querySelector("#model").value = defaultName;
document.querySelector("#modelText").textContent = defaultName;
Object.defineProperty(Obj, 'name', {
get: function () {
console.log('获取 value 值')
return defaultName
},
set: function (value) {
console.log('设置 value 值')
defaultName = value
console.log('模型 => 视图')
document.querySelector("#model").value = value;
document.querySelector("#modelText").textContent = value;
}
})
console.log('----2s 从模型来改变视图的值-----')
setTimeout(() => {
Obj.name = 'chenhh'
}, 2000)
同时,我们还要实现视图到模型
HTML
<input type="text" id="model"><br/>
<div id="modelText"></div>
JavaScript
var Obj = {}
var defaultName = 'chencc'
// 获取dom元素
var model = document.querySelector("#model")
var modelText = document.querySelector("#modelText")
// 设置初始值
model.value = defaultName
modelText.textContent = defaultName
Object.defineProperty(Obj, 'name', {
get: function () {
console.log('获取 value 值')
return defaultName
},
set: function (value) {
console.log('设置 value 值')
defaultName = value
model.value = value;
modelText.textContent = value;
}
})
model.addEventListener('keyup', function() {
Obj.name = this.value
console.log('视图 => 模型')
}, false)
最后将上述两个合并起来就是一个简单的数据双向绑定了
HTML
<input type="text" id="model"><br/>
<div id="modelText"></div>
JavaScript
var Obj = {}
var defaultName = 'chencc'
// 获取dom元素
var model = document.querySelector("#model")
var modelText = document.querySelector("#modelText")
model.value = defaultName
modelText.textContent = defaultName;
Object.defineProperty(Obj, "name", {
get: function () {
return defaultName;
},
set: function (value) {
defaultName = value;
model.value = value;
console.log("-----newValue------");
console.log(value);
modelText.textContent = value;
}
})
// 模型中设置 value 值
Obj.name = 'chencc1'
model.addEventListener("keyup", function () {
Obj.name = this.value;
}, false)
从上面示例都可以看出,一个简单的双向数据绑定实现是非常简单的,就是通过defineProperty来将数据劫持,然后将通过模型的数据反馈到视图,或者监听视图上数据的变化,来反向赋值给数据。核心就是使用了get和set。在vue2.0中就是采用这种方式实现的,现在3.0采用proxy来实现了,后面我们会具体对比分析一下。
总结
对于defineProperty,我们今天就实战基于如何实现 ES6 的 const 和如何实现双向数据绑定进行了分析,大家如果有什么更好的补充,欢迎评论区交流呀。