01 概述
MindSpore Insight是昇思MindSpore的可视化调试调优工具。作为开发者,我参与了MindSpore Insight工具部分功能的开发。本文将对MindSpore Insight进行简要介绍、其次介绍在开发中所用开发框架Vue的响应式原理。
02 MindSpore Insight介绍
MindSpore Insight为昇思MindSpore提供了简单易用的调优调试能力。在训练过程中,可以将标量、张量、图像、计算图、模型超参、训练耗时等数据记录到文件中,通过MindSpore Insight可视化页面进行查看及分析。MindSpore Insight的宏观上的架构如下图所示。其中Summary log是使用昇思MindSpore训练的模型日志,通过python进行对日志进行解码,将数据处理为json格式的API,前端通过请求该API进行相应功能的展示。详细的介绍可查看官网(https://www.mindspore.cn/mindinsight/docs/zh-CN/master/index.html)或点击下方“阅读原文”。
03 浅谈Vue2响应式原理
Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。它的核心功能有声明式渲染和响应式,Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。如下图所示,当data属性中的数据发生变化时,其视图就会更新,不需要用户操作DOM就可让视图更新。那么Vue是如何知道更新的是这些数据呢?这就涉及到Vue响应式的实现原理。
Vue的响应式是指以数据驱动视图的,也就是数据发生变化,会重新渲染页面。该过程实现要考虑的有以下三点:
● 数据劫持:追踪数据的变化
● 依赖收集:收集视图依赖的数据
● 通知视图:数据变化时,通知视图部分更新
3.1 数据劫持和依赖收集
数据劫持:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。在下面的代码很好地诠释了数据劫持。当给obj中的name属性赋值的时候,会触发defineProperty方法中的set函数,而访问obj中的name属性值的时候会触发get函数,在这里set函数和get函数就拦截了访问和修改对象属性的这个行为,在get中和set中可以写上额外的操作,这就是数据劫持。
/*
Object.defineProperty(obj,prop,descriptor)
obj 要定义属性的对象
prop 要定义或修改的属性的名称
descriptor 要定义或修改的属性描述符
输出结果:
数据劫持 set name : 张三
数据劫持 get name : 张三
张三
*/
const obj={}
Object.defineProperty(obj,"name",{
get(){
console.log("数据劫持 get name :",nameVal);
return nameVal;
},
set(newVal){
console.log("数据劫持 set name :",newVal)
nameVal = newVal
}
});
obj.name="张三";
console.log(obj.name)
依赖收集:视图中用到了哪个数据,视图就依赖哪个数据,把变化的数据收集起来,这个过程就是依赖收集。
3.2实现原理介绍
上图(参考:https://blog.csdn.net/Mikon_0703/article/details/111367773)是Vue实现响应式原理的流程图,主要有三个重要部分:
监听器(Observer):对数据对象进行遍历,利用Object.defineProperty()给属性都加上 setter和getter。这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
事件中心(Dep ):用来收集依赖和通知订阅者。如果属性发生变化,需要通知订阅者Watcher,看是否需要更新。因为订阅者有多个,所以需要一个消息订阅器(发布者)Dep(订阅者集合的管理数组)来专门收集这些订阅者,在Observer和Watcher之间进行统一管理。每个Observer实例都有一个Dep实例。
订阅者(Watcher): 将View的相关指令初始化为一个订阅者Watcher,并替换模板数据或绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
3.3 Vue源码中的体现
Vue2中响应式实现的源码链接:https://github.com/vuejs/vue/blob/v2.6.14/src/core/observer
监听器(Observer)的实现
//https://github.com/vuejs/vue/blob/v2.6.14/src/core/observer/index.js
walk (obj: Object) {
const keys = Object.keys(obj) // 遍历数据对象利用
Object.defineProperty()给属性都加上 setter和getter。
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const 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) {
const 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 (process.env.NODE_ENV !== 'production' && 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() // 当数据发生变化时,事件中心Dep通知订阅者
}
})
}
Dep的实现
//https://github.com/vuejs/vue/blob/v2.6.14/src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() //当数据发生变化时,通知订阅者更新视图
}
}
}
Watcher的实现
//https://github.com/vuejs/vue/blob/v2.6.14/src/core/observer/watcher.js
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () { // 订阅者更新视图方法
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
04 总结
首先感谢昇思MindSpore社区提供的这次开源机会,这是我第一次参加开源项目,从这次开源经历中让我掌握了项目的开发流程,以及开源社区中的成员如何协同开发维护一个项目,另外此次开源经历,让我了解了小白如何参与开源、如何正确的提交PR。最后,我引用Apache APISIX PMC成员王院生的一句话:”参与开源,让我觉得自己终于与这个世界融为一体,不再是孤立的个体“ ,我也会继续参与开源,持续为开源贡献自己的一份力量。