Vue的响应式原理
数据响应式:数据变化,会自动运行依赖该数据的函数
Vue中数据变化未调用api,属于非侵入式
因为尤大找到了”上帝的钥匙“:Object.defineProperty() 数据劫持/数据代理,利用JavaScript引擎赋予的功能,检测对象属性的变化
图解:通过Object.defineProperty()遍历对象的每一个属性,把每一个属性变成getter和setter函数,读取属性时运行getter,给属性赋值时运行setter,从而变成一个响应式数据。响应式数据如何跟界面挂钩呢?通过render函数把模板里面的所有数据生成虚拟DOM树。组建的render函数会生成虚拟DOM树从而影响到界面。如何把render函数同响应式数据连接起来?当运行render函数时,render函数内部会用到一些数据,比方说用h函数来生成h1元素,h1元素的内容可能会用到一些数据,比方说是this.msg,this.msg就来自于响应式数据,使用this.msg会运行getter,getter会记录当前有一个函数叫做render用到了msg这个数据,这个过程叫做依赖收集(collect as dependency)。收集的内容包括Watcher。Watcher会监听setter,当setter被调用,就会通知Watcher数据变化。(Watcher之前是通过运行render函数来收集的依赖),Watcher收到通知后就会重新触发render函数的执行,render函数重新执行即生成新的虚拟DOM树。
实现响应式要做的事:
- 数据劫持:劫持变化的数据
- 依赖收集:视图层的内容(
DOM)依赖了哪些数据(state) - 派发更新:数据变化后,如何通知依赖这些数据的
DOM
闭包
认识闭包的前提:认识作用域和作用域链
作用域(JS静态作用域):程序源代码定义的范围
作用域链:
what:能够访问另一个函数作用域中变量的一个函数【定义在一个函数内部的函数】
Why:使用闭包的目的:为了实现JS中的封装(设置一些私有的方法和变量)
闭包的优点:避免全局变量的一个污染
闭包的缺点:闭包会常驻在内存中,增大内存的使用量,使用不当会造成内存泄漏
什么是内存泄漏:该回收的垃圾对象没有被回收,发生了内存泄漏,垃圾对象越堆越多,可用内存越来越少,若可用内存无法存放新的垃圾对象,就导致内存溢出。(拓展:内存溢出:当前创建的对象的大小大于可用的内存容量大小,发生内存溢出)
如何避免内存泄漏:使用完函数或变量后就把变量销毁
闭包形成的条件或特征:1.函数嵌套函数;2.函数内部可以引用外部函数的这个参数跟变量;3.参数和变量不会被JS的垃圾回收机制回收。
Where:闭包的使用场景:防抖和节流;延长变量的生命周期
防抖和节流
防抖:操作时不执行,确定不操作了才执行
--手机一直玩不息屏,隔一分钟不玩了就息屏
每次操作都会先清除定时器并开启定时器,如果连续操作就会不停地清除掉定时器使定时器没办法运行完成,只有不操作了定时器才会运行完成并执行动作
function debounce(fun,delay){ // 接收一个回调函数
let timer
return function(){ // 并返回一个回调函数
if(timer) clearTimeout(timer) // 每次执行前前清除定时器
let args = arguments
timer = setTimeout(() => { // 定义定时器并开启定时器
fun.apply(this,args) // 传入对应的参数并改变this指向
},delay)
}
}
节流:到时间了必须执行一次
--公交车不是来一个客户发一趟车,而是10分钟发一趟
当我们持续触发事件,需要记录初始执行的时间,每次执行时要记录当前时间,用当前时间减去初始时间,如果小于设定值就不执行,只有当前时间减去初始时间大于设定值的时候才会执行,并将现在的时间赋值给初始时间。
function throttle(fun){ // 接收回调函数
let start = 0 // 初始时间
return function(){ // 返回回调函数
let now = new Date() // 每次执行时获取当前时间
if(now - start > time){ // 当前时间减去初始时间,大于设定值就执行对应的方法
fun.apply(this,arguments) // 改变this指向并传入对应的参数
start = now // 并用现在的时间赋值给初始时间,下次执行时,初始时间就变成了更新后的值
}
}
}
JS垃圾回收机制
目标:清除不再使用的对象,腾退出所占内存
当定义一个变量并赋值,然后再重新赋值一个新的对象后,原来的对象就不再使用,就可以把它清除掉
策略:1.引用计数法;2.标记清除法
引用计数法
当一个对象被引用一次,引用数就+1,被取消引用一次,引用数就-1;当引用数为0时,就可以触发垃圾回收机制回收。
问题(缺陷):循环引用时,引用次数永远不可能为0,无法回收,内存无法释放
标记清除法
首先将所有的对象标记为0,从根对象开始遍历,将存活的对象标记为1,然后将标记为0地对象清除掉;最后将所有标记为1的对象重新标记为0,方便下一次垃圾回收机制回收
问题:清除后,位置是不连续的,如果进来一个新的对象,找位置是个新的问题,如何为新的对象找合适的位置?
找位置策略:
1.First-fit (找到能放置下新对象的第一块位置)【性能最好,主流】
2.Best-fit(找到能放置下新对象的对小块位置)
3.Worst-fit(找到最大的块,切一块空间给新对象)【产生更多不连续的空间】
V8对垃圾回收机制的优化
优化标记算法
有的对象需要频繁地回收,而有的对象不需要;一般小的,新的存活时间短的对象需要频繁回收,而大的老的,存活时间长的不需要频繁回收,所以在堆内存中分出两个区域来:1.新生代区域;2.老生代区域
新生代区域:FROM空间(使用空间)和TO空间(闲置空间)。当FROM空间中的对象要满时,我们就开始标记,将存活的对象标记好,标记好后将它们复制到闲置空间中,最后把FROM空间清空,然后交换FROM和TO的名称
老生代区域:采用标记清除算法和标记压缩算法,首先将所有对象标记为0,然后将存活的对象标记为1,将标记为0对象全部清空,然后将标记为1的对象全部标记为0,最后再用标记压缩算法,使他们的位置整理好
因为JS是单线程语言,所以在执行垃圾回收机制时,JS执行会被暂停,称之为全停顿;V8机制对此情况做出优化,垃圾回收机制支持多线程并行回收,如此一来,垃圾回收机制的时间加快了,时间变短,最大化保证JS执行,但是这样依然会阻塞JS执行,所以诞生了增量标记,增量标记最大的特点是垃圾回收可以分段执行,也就是说执行一段JS就执行一段垃圾回收机制,交叉执行,最大化地保证了JS执行。新问题:如何记录上一次标记的位置?三色标记法:就是将上次标记到的位置标记为灰色,当下次执行时,从灰色开始重新标记。同时V8机制还支持了并发回收,也就是说,JS在主线程执行,垃圾回收在辅助线程执行。