Object.defineProperty 深入浅出(二、实战篇)

537 阅读4分钟

对于实战使用到Object.defineProperty大家想到的都是数据双向绑定,数据劫持等等,或者现在实现的vue主流框架实现,3.0现在改用proxy了,这个我们后面也会对比分析。我们先从最简单的通过属性方式实现的一些实战。

Object.defineProperty —— 基础篇

Object.defineProperty —— 实战篇

使用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 和如何实现双向数据绑定进行了分析,大家如果有什么更好的补充,欢迎评论区交流呀。