?本次笔记的记录思路是:由里及表,自下而上
先回顾一下Object.definedProperty这个应用在数据双向绑定中的方法,getter和setter
然后讲一下数据代理以及代码的实现,最后实现一下Vue的数据代理
一、回顾Object.definedProperty方法------ES5
(一)配置对象中的常用的配置属性
Object.defineProperty(person,'sex',{//配置项
? ? ? ? ? ? value:'女',
? ? ? ? ? ? enumerable:true, ? ? ? ? //控制属性是否可以枚举,默认值为false
? ? ? ? ? ? writable:true, ? ? ? ? ? //控制属性是否可以被修改,默认值为false
? ? ? ? ? ? configurable:true ? ? ? ?//控制属性是否可以被删除,默认值为false
? ? ? ? });
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>回顾Object.definedProperty方法</title>
</head>
<body>
<script>
let person = {
name:"温开水",
age:20
}
//原型对象的知识
//添加sex属性和属性值
Object.defineProperty(person,'sex',{//配置项
value:'女',
enumerable:true, //控制属性是否可以枚举,默认值为false
writable:true, //控制属性是否可以被修改,默认值为false
configurable:true //控制属性是否可以被删除,默认值为false
});
console.log(Object.keys(person))//把对象的属性名提取出来并输出一个数组
console.log(person);
</script>
</body>
</html>
?问题:这个方法是不是只能添加属性和对应值这样的形式,不能添加函数
(二)配置对象中的get和set配置方法
监听读写操作,两对象相互影响
(1)getter
????????完整写法:
????????get : function(){
? ? ????????? ?//return语句?
????????}
? ? ? ? 目前我的理解是:getter会监听是否有读取操作发生(也就是是否有语句来获取对象属性值)。若有,则get内置函数调用,并返回值(满足你要获取属性值的欲望,给你值)。随用随取的特性让对象的值是随return的值而改变的。
让拥有getter的这个对象A的属性值 随(return后)另一个对象B属性值的改变而改变,
即B决定A,B变A也变。
(2)setter
????????完整写法:
? ? ? ? set?: function(){
? ? ????????? ?//return语句?
????????}
????????目前我的理解是:setter会监听是否有修改操作发生(也就是是否有语句来修改对象属性值)。若有,则set内置函数调用,执行内部代码,一般为赋值语句,来让另一个对象的属性值也改变。
A可以决定B,A变B也变。
Object.defineProperty(obj2,'x',{
get(){
console.log("get");
return obj1.x;
},
set(value){
console.log("set");
obj1.x = value;
//obj2.x = value;
//obj2.x = value;这句很魔性,但可以辅助我们理解set内置函数,运行这句会发生堆栈溢出
//因为setter是监听obj2.x(对象属性)的变化值,发生变化,传入变化值value
//分析:
//当我在控制台输入 obj2.x=999 时,对象属性值改变!,setter监听到对象属性值的改变,开始传入value
//然后value赋值给obj2.x(对象属性值改变!),这时setter又监听到对象属性值的改变,形成循环。
//死循环一直执行,导致堆栈溢出。
}
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>回顾Object.definedProperty方法</title>
</head>
<body>
<script>
let sex = '女';
let person = {
name:"温开水",
age:20
}
//原型对象的知识
//添加sex属性和属性值
Object.defineProperty(person,'sex',{//配置项
// value:'女', //直接赋值
// enumerable:true, //控制属性是否可以枚举,默认值为false
// writable:true, //控制属性是否可以被修改,默认值为false
// configurable:true //控制属性是否可以被删除,默认值为false
//当读取person的sex属性时,get函数就会被调用,且返回值就是age的值
get: function(){
return sex;
}
});
console.log(Object.keys(person))//把对象的属性名提取出来并输出一个数组
console.log(person);
</script>
</body>
</html>
(三) getter和setter源码解读
尊重原创:地址如下
Vue setter/getter 是何原理? - kimoon - 博客园 (cnblogs.com)https://www.cnblogs.com/the-last/p/11525483.html
1 、 defineProperty 重定义对象
JS原生es5版本提供对象重新定义的接口?defineProperty?
defineProperty 可以修改对象的访问器属性,对象属性值发生变化前后可以触发回调函数。
对象的访问器属性包括?2 种类型:数据描述符、?存取描述符
?1.1 数据描述符 value:对象key的值,默认是 空字符串 '' writeable:是否可写,默认 true configurable:true是否可配置,默认 true enumerable:true是否可枚举, 默认 true
Object.getOwnPropertyDescriptors(obj);
{
k:{
configurable: true,
enumerable: true,
value: 90,
writable: true
}
}
?1.2 存取描述符
set:function(){}属性访问器 进行写操作时调用该方法 get:function(){}属性访问器 进行读操作时调用该方法 属性描述符:?configurable 、enumerable configurable 、enumerable、 set 、?get
对象中新增key的value发生变化时会经过set和get方法。
var obj = {};
var temp = '';
Object.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
get: function () {
return temp;
},
set: function (newValue) {
temp = newValue
}
}); // 需要维护一个可访问的变量 temp
或写在 obj对象内,如下:
1 2 3 4 5 6 7 8 9 10 11 12 | var ?obj = { ?????? tempValue:? 'duyi' , ?????? get name () { ??????????? return ?this .tempValue; ?????? }, ?????? set name (newValue) { ??????????? this .tempValue = newValue; ?????? } }; obj.name = 10; console.log( obj.name );? // 10 |
?小结一次:到这里来基本上知道 getter setter是可以实现的,基于这个简单理论作出一个复杂逻辑。
2 、Observer 源码
/**
* Observer类方法将对象修改为可被观察。
* 一旦应用了这个类方法, 对象的每一个key会被转换成 getter/setter
* 用于收集依赖项和触发更新。
*/
var Observer = function Observer (value) {
this.value = value; // 保存被观察的值到方法的实例属性
this.dep = new Dep(); // 建立一个Dep实例
this.vmCount = 0; // vmCount 记录vm结构的个数
def(value, '__ob__', this); // value 对象 增加 ‘__ob__’ key,并赋值 this
if (Array.isArray(value)) { // value 对象如果是数组
if (hasProto) { // 表示在[]上可以使用 __proto__
protoAugment(value, arrayMethods); // 将arrayMethods 添加到value的__proto__。arrayMethods 好像是重写了数组的一部分原生方法,后面再看
} else {
copyAugment(value, arrayMethods, arrayKeys); // 说不定他不支持 __proto__ ,调用def方法增强对象。
}
this.observeArray(value); // 然后将这个增强后的数组,每一项都执行observe
} else {
this.walk(value); // walk 在Observer的原型上,对象转换为 getter setter。
}
};
/**
* 遍历所有属性将它们转换为 getter/setter,仅当值类型为对象时调用。
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]); // defineReactive$$1 () 方法,这个方法才是实现 getter / setter 的原方法!!!
}
};
/**
* 观察数组项列表
*/
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]); // observe 方法
}
};
/**
* 截获原型链使用 __proto__ 的方式来
* 增强一个目标的对象或数组,简称原型增强。
*/
function protoAugment (target, src) {
/* eslint-disable no-proto */
target.__proto__ = src;
/* eslint-enable no-proto */
}
/**
* 增加对象原型properties
*/
function copyAugment (target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
/**
* 在Observer方法的原型属性 observerArray 上有用到!
* 大概意思是创建可观察实例,返回可观察实例或已有的可观察对象。
*/
function observe (value, asRootData) {
// 如果不是object时,或是 VNode 的实例不进行 observe
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
/**
* 这里是实现getter setter的关键代码
* 这里是实现getter setter的关键代码
* 这里是实现getter setter的关键代码
*
* 在对象上定义一个 有反应的原型
* 传参:obj对象,key关键字,val值,customSetter在set的时候会被执行(第4个参数),shallow 默认为undefined,为 true 时不执行 observe 函数。
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// 预定义的getter和setter
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
?订阅observe对象的变化,通知触发update,参考订阅-发布模式。
/**
* Dep 是可以有多个指令订阅的可观察对象,目的就是对一个目标深层处理
*/
var uid = 0;
var Dep = function Dep () {
this.id = uid++; // 添加了2个实例属性,id 用于排序和 subs 数组统计sub
this.subs = [];
};
// 在 subs中添加 sub
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
// 从 subs中移除 sub
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this); // addDep 是 Watcher 的原型方法,用于指令增加依赖
}
};
Dep.prototype.notify = function notify () {
// 稳定订阅列表
var subs = this.subs.slice();
if (!config.async) {
// 如果不是在异步运行,在程序调度中 subs 不可以被排序!
// 然后排序以确保正确的顺序。
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
// 然后顺序触发 Watcher 原型的 update 方法
subs[i].update();
}
};
// 当前目标程序被评估,这个评估全局唯一,一次只要一个观察者可以被评估
Dep.target = null;
var targetStack = [];
// 将目标程序推送到目标栈
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
// 执行出栈先去掉
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
?全局def方法,定义对象。
// def 方法比较简单
/**
* 定义一个原型
* obj
* key 对象的key关键字
* val 对象的value值
* 是否可枚举,默认可写可配置
*/
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
二、Vue中的数据代理
(一)数据代理的底层实现
使用getter和setter来实现
数据代理概念:通过一个对象对另一个对象的属性进行操作(读/写)
实现的目标:修改 obj1 或者 obj2 任意一个对象中的 x 属性都会让另一个对象的 x 属性改变,让 obj1 和 obj2 的 x 属性值始终保持一致
?Vue数据双向绑定?? ? 、
代码实现,注释仔细看
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>数据代理</title>
</head>
<body>
<!-- 数据代理:通过一个对象对另一个对象的属性进行操作(读/写) -->
<script>
let obj1 = {x:100};
let obj2 = {y:200};
//实现的目标:修改 obj1 或者 obj2 任意一个对象中的 x 属性都会让另一个对象的 x 属性改变
//Vue双向绑定? obj1 和 obj2的 x 属性值始终保持一致
Object.defineProperty(obj2,'x',{
get(){
console.log("get");
return obj1.x;
//也可以写return value = obj1.x
},
set(value){
console.log("set");
obj1.x = value;
//obj2.x = value; //这句很魔性,但可以辅助我们理解set内置函数,运行这句会发生堆栈溢出
//因为setter是监听obj2.x(对象属性)的变化值,发生变化,传入变化值value
//分析:
//当我在控制台输入 obj2.x=999 时,对象属性值改变!,setter监听到对象属性值的改变,开始传入value
//然后value赋值给obj2.x(对象属性值改变!),这时setter又监听到对象属性值的改变,形成循环。
//死循环一直执行,导致堆栈溢出。
}
})
console.log(obj2);
</script>
</body>
</html>
(二)Vue中数据代理的实现
前面学过Vue的两种数据绑定,复习一下
(一)单向绑定(v-bind):数据只能从data流向页面
(二)双向绑定(v-model):数据不仅能从data流向页面,还可以从页面data
? ? ? ? 备注:(1)双向绑定一般应用在表单元素上(如:input、select)
? ? ? ? ? ? ? ? ? ?(2)v-model:value? 可以简写为v-model ,因为v-model默认收集的就是value值
但我们并不使用,只是复习一下
代码示例
要注意这一句? ?<h2>{{name}}</h2>,其他都只是复习
<!DOCTYPE html>
<html">
<head>
<meta charset="UTF-8">
<title>数据绑定</title>
<!-- 引入vue -->
<script type="text/javascript" src="../vuejs/vue.js"></script>
</head>
<body>
<!-- 容器 -->
<div id="root">
单向数据绑定:<input type="text" v-bind:value="name"><br>
双向数据绑定:<input type="text" v-model:value="name">
<!-- 数据代理 -->
<h2>{{name}}</h2>
</div>
<script type="text/javascript">
//创建vue实例
const vm = new Vue({
el:'#root',
data:{
name:"温开水",
other:{
url:"https://cn.vuejs.org",
name:"vue"
}
}
});
</script>
</body>
</html>
参考一下Vue数据代理图示
数据代理小总结
1.Vue中的数据代理: 通过vm对象来代理data对象中属性的操作(读/写) | 2. Vue中数据代理的好处:更加方便的操作data中的数据 | 3.基本原理: 通过Object.defineProperty( )把data对象中所有属性添加到vm上。为每个添加到vm上的属性, 都指定一个getter/setter。(图)在getter/setter内部去操作(读/写) data中对应的属性。 |
Vue数据代理这部分写的比较乱
三、问题
为什么在视频中修改vm.name会让页面自动改变,试着从模板语法中寻找答案
?
|