【vue.js之夯实基础-11】详解vue.js数据双向绑定,及数据劫持(数据监听机制) 用 Object.defineProperty() 属性定义(数据描述符,存取描述符)/属性赋值

引用 深入浅出Object.defineProperty()
勾三股四的 Vue.js 源码学习笔记
vue.js关于Object.defineProperty的利用原理

一 属性定义 Object.defineProperty()语法说明

通过Object.defineProperty()为对象定义属性,有两种形式,且不能混合使用,
分别为数据描述符,存取描述符

数据描述符(value,writable)

let Person = {}
Object.defineProperty(Person, 'name', {
   value: 'jack',
   writable: true // 是否可以改变
})

存取描述符 --是由一对 getter、setter 函数功能来描述的属性(get,set)

let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
  get: function () {
    return temp
  },
  set: function (val) {
    temp = val
  }
})

数据描述符和存取描述均具有以下描述符

configrable 描述属性是否配置,以及可否删除
enumerable 描述属性是否会出现在for in 或者 Object.keys()的遍历中

configurable: false 时,不能删除当前属性,且不能重新配置当前属性的描述符(有一个小小的意外:可以把writable的状态由true改为false,但是无法由false改为true),但是在writable: true的情况下,可以改变value的值
configurable: true时,可以删除当前属性,可以配置当前属性所有描述符。

注意 以下二种区别

Person.name=‘jack’
let Person={}

Person.name='jack'

//等价于
Object.defineProperty(Person, 'name', {
  value: 'jack',
  writable: true,
  enumerable: true,
  configurable: true
});

在这里插入图片描述

Object.defineProperty(Person, ‘age’, { value: ‘28’})
Object.defineProperty(Person, 'age', {  value: '28'})

//等价于
Object.defineProperty(Person, 'age', {
  value: '28',
  writable: false,
  enumerable: false,
  configurable: false
});  

在这里插入图片描述
Object.keys(Person)(enumerable false)
在这里插入图片描述
Person.age=‘30’ 不可修改( writable:false)
在这里插入图片描述
Object.defineProperty(Person, ‘age’, { value: ‘32’}) 不可重新定义 (configurable:false)

在这里插入图片描述

对象属性的 不变性

对象常量 writable: false 和 configurable: false

结合writable: false 和 configurable: false 就可以创建一个真正的常量属性(不可修改,不可重新定义或者删除)
在这里插入图片描述

禁止扩展 Object.preventExtensions(…)

如果你想禁止一个对象添加新属性并且保留已有属性,就可以使用Object.preventExtensions(…)
在这里插入图片描述
在这里插入图片描述
在非严格模式下,创建属性gender会静默失败,在严格模式下,将会抛出异常

密封 Object.seal()会创建一个密封的对象,

这个方法实际上会在一个现有对象上调用object.preventExtensions(…)并把所有现有属性标记为configurable:false

在这里插入图片描述

所以, 密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以改属性的值)

冻结 Object.freeze()

Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(),并把所有现有属性标记为writable: false,这样就无法修改它们的值。

在这里插入图片描述
这个方法是你可以应用在对象上级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(但是这个对象引用的其他对象是不受影响的)
你可以深度冻结一个对象,具体方法为,首先这个对象上调用Object.freeze()然后遍历它引用的所有对象,并在这些对象上调用Object.freeze()。但是一定要小心,因为这么做有可能会无意中冻结其他共享对象。

总结 属性定义

1 如果Obj没有名为Prop的自身属性的话:如果Obj是可扩展的话,则创建Prop这个自身属性,否则拒绝
2 如果Obj已经有了名为Prop的自身属性:则按照下面的步骤重新配置这个属性
3 如果这个已有的属性是不可配置的,则进行下面的操作会被拒绝

1: 将一个数据属性转换成访问器属性,反之变然
2: 改变`[[Configurable]]``[[Enumerable]]`
3: 改变[[Writable]]false变为true
4:`[[Writable]]``false`时改变`[[Value]]`
5: 改变[[Get]][[Set]]

4 否则这个已有的属性可以被重新配置

二 属性赋值 通过obj.prop = ''prop"形式

1 如果在原型链上存在一个名为P的只读属性(只读的数据属性或者没有setter的访问器属性),则拒绝
2 如果在原型链上存在一个名为P的且拥有setter的访问器属性,则调用这个setter
3 如果没有名为P的自身属性,则如果这个对象是可扩展的,就创建一个新属性,否则,如果这个对象是不可扩展的,则拒绝
4 如果已经存在一个可写的名为P的自身属性,则调用Object.defineProperty(),该操作只会更改P属性的值,其他的特性(比如可枚举性)都不会改变

三 数据双向绑定

view

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>

	<body>
	<div>
		<p>你好,<span id='nickName'>123</span></p>
		<div id="introduce">...</div>
	</div>  
	</body>
</html>

数据绑定

		//视图控制器
		var userInfo = {};
		Object.defineProperty(userInfo, "nickName", {
			get: function(){
				return document.getElementById('nickName').innerHTML;
			},
			set: function(nick){
				document.getElementById('nickName').innerHTML = nick;
			}
		});
		Object.defineProperty(userInfo, "introduce", {
			get: function(){
				return document.getElementById('introduce').innerHTML;
			},
			set: function(introduce){
				document.getElementById('introduce').innerHTML = introduce;
			}
		})

userinfo
在这里插入图片描述

双向绑定演示

原始页面
在这里插入图片描述
更改数据

userInfo.nickName='cch'
userInfo.introduce=' hello'

在这里插入图片描述
更新后的页面
在这里插入图片描述

总结

而vue.js主要利用了accessor descriptors的set和get来更新视图,上面这里看到的这个例子挺好,是一个简单的绑定。

在这里插入图片描述
这个例子只是数据和dom节点的绑定,而vue.js更为复杂一点,它在网页dom和accessor之间会有两层,一层是Wacher,一层是Directive,比如以下代码。

var a = { b: 1 }
var vm = new Vue({ 
  data: a
})

把一个普通对象(a={b:1})传给 Vue 实例作为它的 data 选项,
Vue.js 将遍历它的属性,用Object.defineProperty 将它们转为 getter/setter,如图绿色的部分所示。
每次用户更改data里的数据的时候,比如a.b =1,setter就会重新通知Watcher进行变动,Watcher再通知Directive对dom节点进行更改。

四 数据劫持,数据监听机制

view

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>

	<body>
	<div>
	</div>  
	</body>
</html>

数据劫持

一般对数据的劫持都是通过Object.defineProperty方法进行的,
Vue中对应的函数为 defineReactive
其普通对象的劫持的精简版代码如下:

	var foo = {
			name: 'vue',
			version: '2.0'
		}

		function observe(data) {
			if (!data || typeof data !== 'object') {
				return
			}

			// 使用递归劫持对象属性
			Object.keys(data).forEach(function(key) {
				defineReactive(data, key, data[key]);
			})
		}

		function defineReactive(obj, key, value) {
			// 监听子属性 比如这里data对象里的 'name' 或者 'version'
			observe(value)

			Object.defineProperty(obj, key, {
				get: function reactiveGetter() {
					return value
				},
				set: function reactiveSetter(newVal) {
					if (value === newVal) {
						return
					} else {
						value = newVal
						console.log(`监听成功:${value} --> ${newVal}`)
					}
				}
			})
		}

		observe(foo)

		foo.name = 'angular' // “监听成功:vue --> angular”

		foo.version='1.0' //监听成功:1.0 --> 1.0

get ,set 的方法名可以不写 reactiveGetter /reactiveSetter

function defineReactive(obj, key, value) {
			// 监听子属性 比如这里data对象里的 'name' 或者 'version'
			observe(value)

			Object.defineProperty(obj, key, {
				get: function () {
					return value
				},
				set: function (newVal) {
					if (value === newVal) {
						return
					} else {
						value = newVal
						console.log(`监听成功:${value} --> ${newVal}`)
					}
				}
			})
		}

监听结果

在这里插入图片描述

在这里插入图片描述

上面完成了对数据对象的监听,接下来还需要在监听到变化后去通知订阅者,这需要实现一个消息订阅器 Dep ,Watcher通过 Dep 添加订阅者,当数据改变便触发 Dep.notify() ,Watcher调用自己的 update() 方法完成视图更新。

vue.js 数据监听机制 源码

如何监听某一个对象属性的变化呢?我们很容易想到 Object.defineProperty 这个 API,为此vue属性设计一个特殊的 getter/setter,然后在 setter 里触发一个函数,就可以达到监听的效果。

vue源码
在这里插入图片描述

总结 vue对数据对象的监听

上面完成了对数据对象的监听,接下来还需要在监听到变化后去通知订阅者
在这里插入图片描述

  • Observer 数据监听器

负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者。

  • Compiler 指令解析器(vue文件中的的内容)

扫描模板,并对指令进行解析,然后绑定指定事件。

  • Watcher 订阅者

关联Observer和Compile,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。Update()是它自身的一个方法,用于执行Compile中绑定的回调,更新视图。
这需要实现一个消息订阅器 Dep
Watcher通过 Dep 添加订阅者,
当数据改变便触发 Dep.notify()
Watcher调用自己的 update() 方法完成视图更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值