一:vue2的响应式原理 (数据双向绑定原理)
Object.defineProperty()
Object.defineProperty() 其实核心的并不是为对象做数据双向绑定,而是去给对象做属性标签,只不过属性里的 get 和 set 实现了响应式。
1. Object.defineproperty()
属性名 | 默认值 |
value | undefined |
set | undefined |
get | undefined |
writable(可写性) | true |
enumerable (可枚举性)for/in 是否可循环 | true |
configurable (可配置性) | true |
例子:
var o = {}
Object.defineProperty(o, 'name', {
value: 'zhoufangbing',
configurable: false,
enumerable: false,
writable: false
})
console.log(o) // { name: "zhoufangbing" }
console.log(o.name) // zhoufangbing
o.name = 'xx'
console.log(o.name) // zhoufangbing
// 因为 writable (可写性)为false,所以值不可更改,仍为zhoufangbing
// 若值为 true , 则会输出 xx
// Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。
console.log(Object.keys(o)) // []
// 因为 enumerable (可枚举性)false 所以返回 [], 如果enumerable 为true
console.log(Object.keys(o)) // ['name']
// 访问器属性 get set
var person = {
_age: 22, // 属性前面加_,代表属性只能通过对象方法访问 不然浏览器报错
isGrowUp: false
}
Object.defineProperty(person, 'age', {
get: function() {
return this._age // this 指向 person
},
set: function(value) {
this._age = value // set 重新赋值
if (value >= 18) {
this.isGrowUp = true
} else {
this.isGrowUp = false
}
}
})
console.log(person.age) // 22
person.age = 19
console.log(person.age) // 19
console.log(person.isGrowUp) // true
person.age = 12
console.log(person.age) // 12
console.log(person.isGrowUp) // false
2. Object.defineProperties()
Object.defineProperty(object, propertyName, descriptor) 定义新属性时,descriptor 中不能同时有 访问器(getter/setter) 与 value/writable 属性
var person = {}
Object.defineProperties(person, {
age: {
value: 18,
writable: true,
enumerable: true
},
isGroup: {
get: function() {
if (this.age >= 18) {
return true
} else {
return false
}
}
}
})
笔记:
1. vue2 中的数据双向绑定原理:使用Object.defineProperty() 中的 get 和 set
2. 当从对象中取值时,会触发 get 函数,当重新赋值时,会触发 set 函数~
如果定义了get 函数,get函数必须要有 return ,否则 【对象.属性】 取值 会是undefined~ return 的值 就是 【对象.属性】
二: vue 从改变一个数据到发生改变的全过程
1. 数据更改触发 Object.defineProperty() 中的set函数
2. Set 函数触发更新
3. 更改对应的虚拟dom
4. 更新render
三:数据双向绑定简易版代码
<body>
<div id="app"></div>
<script>
function Vue(dataObj) {
this.$data = dataObj
this.virtualDom = ''
this.el = document.getElementById('app')
this.observer(dataObj)
this.render()
}
// 注册 set 和 get
Vue.prototype.observer = function(obj) {
var _this = this
// 递归一下 防止对象里面套对象
for(var key in obj) {
var value = obj[key]
if (typeof value === 'object') {
this.observer(value)
} else {
Object.defineProperty(obj, key, { // TODO
get: function() {
// 进行依赖收集
// 1. data 中的数据并不是所有地方都能用到的
// 2. 如果直接更新整个视图,有点亏
// 3. 先收集来依赖 改变变量 的视图(组件
// 4. 把依赖的组件进行视图更新,其他不变视图
return value
},
set: function(newValue) {
value = newValue
_this.render()
}
})
}
}
}
// 渲染页面
Vue.prototype.render = function() {
this.virtualDom = 'i am ' + this.$data.a
this.el.innerHTML = this.virtualDom
}
// 创建实例
var vm = new Vue({
a: 1
})
setTimeout(() => {
vm.$data.a = 2 // 值改变,执行set函数
}, 1000)
</script>
</body>
四: Object.defineProperty() 定义的get 和 set 是对象的属性,那么数组怎么办?
在 Vue 中,通过数组下标更改数组内容,是不会触发视图更新的
var arr = [1,2,3,4]
arr[3] = 5
只有使用数组的方法,视图才能更新
arr.push()
arr.unshift()
arr.shift()
arr.pop()
arr.reserve()
先学透知识、会用,再去看原理、源码~
五:Vue3 的响应式原理
Proxy
// Proxy 对象用于定义基本操作的自定义行为
// 和 Object.defineProperty() 功能基本一样,但是用法不同
Proxy: 代理
作用:扩展(增强)对象的一些功能,比如预警、上报、扩展功能、统计等
语法: new Proxy(被代理的对象,对代理的对象做什么操作)
new Proxy : 优点不会污染原对象,重新代理 objProxy
比如:
// 原始对象
let obj = { name: 'zhouzhou' }
// 代理对象
let objProxy = new Proxy(obj, {
// get 方法两个参数,第一个是对象,第二个是你访问的是谁就是谁
// objProxy.name 则 property 就是 name
get: function(target, property) {
console.log(`您访问了${property}属性`)
return target[property]
}
})
console.log(objProxy.name) // get 函数的return值
例子1:实现一个访问一个对象的属性,正常情况下是返回 undefined,希望如果不存在返回 warning 警告信息
<script>
let obj = {
name: 'zhouzhou',
age: 24,
sex: '女'
}
let objProxy = new Proxy(obj, {
get: function(target, property) {
if (obj[property]) {
return target[property]
} else {
throw new Error(`您访问的属性${property}不存在`)
}
}
})
console.log(objProxy.sex)
console.log(objProxy.money)
</script>
例子2:定义一个Proxy,DOM.div() 则生成一个div,DOM.a() 生成一个 a 标签,DOM.span 生成一个 span
<div id="app"></div>
<script>
let DOM = new Proxy({}, {
get: function(target, peoperty) {
return function(attr = {}, ...argument) {
// 创建对象
const el = document.createElement(peoperty)
// 设定属性
for(keys of Object.keys(attr)) {
el.setAttribute(keys, attr[keys])
}
// 设定内容
for(let elem of argument) {
if (typeof elem === 'string') {
child = document.createTextNode(elem)
} else {
child = elem
}
el.appendChild(child)
}
return el
}
}
})
const node = DOM.div(
{ class: 'wrapper'},
'hhhhh',
DOM.p({}, 'aaa'),
DOM.a({href: 'https:www.baidu.com'}, '点击跳转')
)
console.log(node)
const app = document.getElementById('app')
app.appendChild(node)
</script>
Proxy 的其他属性~~
1. set : 设置值 拦截
var obj = new Proxy({}, {
// set 三个参数:目标对象、属性、属性值
set: function(target, property, value) {
// 可以进行值拦截校验
if (property === 'age') {
if (!Number.isInteger(value)) {
throw TypeError('年龄必须为整数')
return
}
if (value < 18 || value > 80) {
throw RangeError('年龄不在合法范围内')
return
}
}
// 设置属性值
target[property] = value
}
})
obj.name = 'zhouzhou'
obj.age = 19
console.log(obj) // {name: "zhouzhou", age: 19}
2. deleteProperty() : 删除属性
let json = {
a: 1,
b: 2
}
var obj = new Proxy(json, {
deleteProperty: function(target, property) {
console.log(`you will delete ${property}`) // 删除之前提示
delete target[property]
}
})
delete obj.a
console.log(obj)
3. has():判断属性是否在 搭配关键字 in
let json = {
a: 1,
b: 2
}
var obj = new Proxy(json, {
has: function(target, property) {
console.log(`判断是否存在${property}属性`)
return property in target
}
})
console.log('a' in obj) // true
console.log('c' in obj) // false
4. apply() 拦截方法
function sum (a, b) {
return a + b
}
let newSum = new Proxy(sum, {
// 目标对象、this指向、函数参数(数组形式)
apply: function(target, context, args) {
return Reflect.apply(...arguments) // 执行目标函数 sum 返回 5
}
})
const result = newSum(2,3)
console.log(result)
所以 Vue3 的响应式原理:Proxy 的 set 和 get
重写:
function Vue(dataObj) {
this.$data = dataObj
this.virtualDom = ''
this.el = document.getElementById('app')
this.observer(dataObj)
this.render()
}
// 注册 set 和 get
Vue.prototype.observer = function(obj) {
var _this = this
// 递归一下 防止对象里面套对象
this.$data = new Proxy(this.$data, {
set: function(target, property, value) {
target[property] = value
_this.render()
},
get: function(target, property) {
return target[property]
}
})
}
// 渲染页面
Vue.prototype.render = function() {
this.virtualDom = 'i am ' + this.$data.a
this.el.innerHTML = this.virtualDom
}
// 创建实例
var vm = new Vue({
a: 1
})
setTimeout(() => {
vm.$data.a = 2 // 值改变,执行set函数
}, 1000)