共计 4269 个字符,预计需要花费 11 分钟才能阅读完成。
1、数据响应式原理
1.1、MVVM 是什么?
简略来说,就是数据变了,视图也会跟着变,首先你得定义一个带有 {{}} 的模板 Model,当数据中的值变动了,视图 View 就会跟着变动;视图模型 View-model 是模板 Model 和视图 View 之间的桥梁,Vue 属于非侵入式,React 和小程序就是侵入式(数据变动的时候须要调用提前写好的 API)
// Vue 数据变动,非侵入式 | |
this.a ++ | |
// React、小程序数据变动,侵入式 | |
this.setState({a: this.state.a + 1}); |
1.2、数据响应式的中心思想?
通过重写数据的 get 和 set 属性办法,让数据在被渲染时,把所有用到本人的订阅者,寄存在本人的订阅者列表中;当数据产生扭转时,该办法告诉所有订阅了本人的订阅者,达到从新渲染的目标。
是不是有点懵了?没关系,举个简略的栗子🌰:
《西游记》中的妖怪(Watcher)时刻惦记(订阅)着唐僧(Data),想吃唐僧肉,孙悟空(Component)在听到(get 收集依赖)唐僧被抓的音讯后,做出反馈(set 触发依赖),筹备救出徒弟。于是来到了妖怪(Watcher)的老巢,跟它大战几个回合后,胜利救出唐僧(Data),达到从新踏上(渲染)西天取经(Vittual DOM Tree)的目标!
2、尤大找到了一把”上帝的钥匙🔑“Object.defineProperty()办法:
数据劫持、数据代理,MDN 这样形容的:间接在一个对象上定义一个新属性,或者批改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj(定义那个对象),'a(定义这个对象的什么属性)',{ | |
// 属性值定义为多少 | |
value : 3 | |
}) | |
//-----------------------------------🌰栗子🌰------------------------------------------- | |
var obj = {}; | |
Object.defineProperty(obj, 'a', {get(){console.log('拜访 obj 的 a 属性!'); | |
return 7; | |
}, | |
set(){console.log('扭转 obj 的 a 属性', newValue); | |
temp = newValue; | |
} | |
}) | |
console.log(obj.a); // 7 | |
obj.a = 9; // 批改 a 的值 | |
console.log(obj.a); // 7 |
3、补救这把钥匙🔑的有余 defineReactive 函数:
为了解决 defineProperty()办法存在的问题,get 中并不能返回 set 刚刚批改过的值,再次调用会显示批改前的值,怎么解决这个问题?在里面定义一个全局变量,用来周转变量值。
// 解决 defineProperty 存在到的问题 | |
defineProperty(data(数据对象),key(键名),val(值)){ } | |
//-----------------------------------🌰栗子🌰------------------------------------------- | |
var obj = {}; | |
var temp; // 在里面定义一个全局变量,用来周转变量值。function defineProperty(data, key, val) { | |
Object.defineProperty(data, key, { | |
// 可枚举 | |
enumerable: true, | |
// 可被配置,比方被 delete | |
configurable: true, | |
get(){console.log('拜访 obj 的'+ key +'属性!'); | |
return temp; | |
}, | |
set(){console.log('扭转 obj 的'+ key +'属性!', newValue); | |
if(val == newValue){return;} | |
temp = newValue; | |
} | |
}); | |
} | |
defineReactive(obj, 'a',10) | |
console.log(obj.a); // 拜访 obj 的 a 属性!10 | |
obj.a = 69; // 批改 a 的值 | |
obj.a ++; | |
console.log(obj.a); // 批改 obj 的 a 属性!70 |
// definReactive 实现 | |
// 简化后的版本 | |
function defineReactive(target, key, value, enumerable) { | |
// 折中解决后, this 就是 Vue 实例 | |
let that = this; | |
// 函数外部就是一个部分作用域, 这个 value 就只在函数内应用的变量 (闭包) | |
if (typeof value === 'object' && value != null && !Array.isArray( value) ) { | |
// 是非数组的援用类型 | |
reactify(value); // 递归 | |
} | |
Object.defineProperty( target, key, { | |
configurable: true, | |
enumerable: !!enumerable, | |
get () {console.log( ` 读取 ${key} 属性 ` ); // 额定 | |
return value; | |
}, | |
set (newVal) {console.log( ` 设置 ${key} 属性为: ${newVal}` ); // 额定 | |
value = reactify(newVal); | |
} | |
} ); | |
} |
4、数组的响应式解决
改写了 7 个属性,push(数组尾部推入 )、pop( 数组尾部移除 )、shift( 数组头部插入 )、unshift( 数组尾部移出 )、splice( 切割 )、sort( 就地排序 )、reverse( 排序地位颠倒);
// 失去 Array.prototype | |
const arrayPrototype = Array.prototype | |
// 以 Array.prototype 为原型,创立 arrayMethods 对象,定义__proto 办法 | |
const arrayMethods = Object.create(arrayPrototype); | |
// 要被改写的 7 个数组办法 | |
const methodsNeedChange = ['push','pop','shift','unshift','splice','sort','reverse']; | |
// 遍历 | |
methdsNeedChange.forEach(methodName => { | |
// 备份原来的办法 | |
const original = arrayPrototype[methodName]; | |
// 把数组身上的__obj__取出来,const ob = this.__obj__; | |
// 有三种办法 push/unshift/splice 可能插入新项,把插入的新项变为 observe | |
let inserted = []; | |
switch(methodName){ | |
case 'push': | |
case 'unshift': | |
inserted = arguments; | |
break; | |
case 'splice': | |
// splice 格局是 splice(下标,数量,插入的新项) | |
inserted = arguments.slice(2); | |
brack; | |
} | |
// 判断有没有要插入的新项,让新项也变为响应的 | |
if(inserted){ob.obsetveArray(inserted); | |
} | |
// 定义新的办法 | |
def(arrayMethods, methodName, function(){original.apply(this, arguments); | |
},false); | |
}); |
面试题:数组中的响应式是怎么实现的?
答:以 Array.prototype 为原型,创立了一个 arrayMethods 的对象,用一个十分强硬的伎俩,Object.setPrototypeOf()让数组的_ proto _强制指向 arrayMethods,这样就能够调用新的改写的 7 个办法。
5、什么是依赖?
须要用到数据的递归就是 依赖 ,在 getter 中 收集 依赖,在 setter 中 触发 依赖。
收集依赖的代码封装成 Dep 类,每个 Observer 的实例都有一个 Dep 的实例;
Watcher 是一个中介,数据发生变化时通过 watcher 直达,告诉组件。
再拿《西游记》说,妖怪(Watcher)是怎么晓得唐僧(Data)路径此地的呢?那天然是派出去巡山(depend 办法)的小妖精(Dep- 订阅器)发现(收集)的;这个小妖精(Dep)巡山有三个目标(属性):指标(target)、id、subs(所有巡山的信息),当唐僧(Data)通过某个提前安排好的陷阱(生命周期的 hook)时,就会被抓,压入巢穴(targetStack 栈顶),交给妖怪(Watcher)。
6、什么时候可能把 Wather 放入到 Dep 当中?
Dep 类:封装收集的代码,治理依赖。
Wather 类:①将属性值更新;②执行 watch 中的回调函数 handler(newVal, oldVal)
先把 wather 设置到全局指定地位,而后读取数据;getter 函数当中,会从全局惟一的中央,读取正在读取数据的 wather,并把 wather 再收集到 Dep 当中。
//wather.js | |
export default class Dep{constructor(){ | |
// 用数组存储本人的订阅者,subs 是 subscribes 订阅者的意思。// 数组外面寄存的是 wather 的实例。this.subs = [];} | |
// 增加订阅 | |
addSub(sub){this.shbs.push(sub); | |
} | |
// 增加依赖 | |
dpend(){ | |
// 指定全局的地位 | |
if(Dep.target){ | |
// 如果 Dep.target 存下,则推入到 subs 外面 | |
this.addSub(Dep.target); | |
// | |
} | |
} | |
// 告诉更新 | |
notify(){// 浅克隆一份} | |
} |
7、Vue 中怎么辨认 a.b.c 的?
利用高阶函数,逐层取出外面的值。
<script> | |
// 深层套娃 | |
var o = { | |
a: { | |
b: { | |
c: {d: 68} | |
} | |
} | |
} | |
var str = 'a.b.c.d'; | |
function parsePath(str){ | |
// 依据 . 来进行拆分 | |
var segments = str.split('.'); | |
// 返回接管对象的函数 | |
return(obj) => { | |
// 遍历接管的函数 | |
for(let i = 0; i < segments.length; i++){ | |
// 判断 obj 存不存在 | |
if(!obj) return; | |
// 一层一层的剥开 o 的心 | |
obj = obj[segments[i]] | |
} | |
// 高阶函数,函数外部返回一个函数 | |
return obj; | |
} | |
} | |
// 调用一下 | |
var fn = parsePath(str); | |
var v = fn(o); | |
console.log(v); | |
</script> |