欢迎来到陈起的博客

寄意寒星荃不察,我以我血荐轩辕。

黎明就在眼前

软件工程实践总结&个人技术博客(2/2)


这个作业属于哪个课程 2021春软件工程实践 W班 (福州大学)
这个作业要求在哪里 软件工程实践总结&个人技术博客
这个作业的目标 课程回顾与总结 个人技术总结
其他参考文献 邹欣老师的博客园讲义Vue官方教程对Vue中的MVVM原理解析和实现--小羽羽

@


课程回顾与总结

对寒假作业二的问题有什么新的看法吗?

问题一回顾

一、我看了第三章结对编程中的这一段文字:
“在结对编程模式下,一对程序员肩并肩地、平等地、互补地进行开发工作。两个程序员并排坐在一台电脑前,面对同一个显示器,使用同一个键盘,同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起单元测试,一起集成测试,一起写文档等。”

于我而言,之前参加团队项目时,在完成自己分工部分工作后,有去还未完成同学那帮忙,当时就是采用类似结对编程的方式,一人编写代码,一人检查代码手代码的逻辑、语法有无出错,并提示下一步骤。我认为在进行同一模块时,按照书本中所说结对编程确实能提高代码的质量和效率,但是感觉在实际中还是可能会出现以下问题:
1、当还有很多模块需要完成时,相比同一个模块由两个人负责,是否两人负责不同的模块效率会更高?
2、在结对编程的过程中,领航员若存在不理解驾驶员的代码时,又如何做到领航?若领航员无法起到领航的作用,那结对编程又有何意义?

新的理解

根据先前查了的资料:
工程师结对编程能否大幅提高工作效率?
国内为何很少有人做结对编程呢?是确实不好还是属于中国特色?

这些文章中提到:

“ 结对编程有以下优点:
1,代码质量提升巨大(不好意思写烂代码啊!)
2,开发速度提升巨大(两个人的知识和智慧联合)
3,分工明确,"心流"不易被打断(一人敲键盘一人查手册,或应付其他琐碎事)
4,可以互相学习,没有什么比看别人工作更能让自己提高的了。但结对需要条件,尤其是对程序员素质要求较高。”

“ 对结对人员的条件如下:
1,水平相当接近,不然容易造成心理不适
2,使用工具集一致,一个vi一个emacs光打架了不干活了
3,对编程的品味和态度一致,不要为了一些各有所好的东西争论
4,不一定是很好的朋友,但至少互相尊重”

结合上我一学期以来的学习经验,我个人的理解是:
两个人的知识和智慧联合,相互监督,一定程度上可以提升代码质量、提升开发速度,但是我还是存在有以下疑惑:
对于结对人员的要求条件实际上是非常严苛的,而且还存在很多工程师不喜欢交流、不习惯结对的情况,针对于此,结对编程是否是必要的?在公司里面,怎么判断什么工作需要结对?什么组合可以结对?

我认为是有必要的,在公司里面,可能在接口对接的时候需要结对编程,结对可能是高手带新手,也可能是高手带高手

问题二回顾

二、我看了第四章关于明星模式的这一段文字:
“主治医师模式运用到极点, 可以蜕化为明星模式, 在这里明星的光芒盖过了团队其他人, 前一阵子喧嚣一时的“翔之队”就是一个例子。明星也是人, 也会受伤, 犯错误, 如何让团队的利益最大化, 而不是明星的利益最大化?”

于我而言,之前大一参加团队项目时,当时我的编程能力还是比较弱的,队里有个大佬,能力比较强。于是在整个项目过程中,我基本上就处于所谓“躺”的状态,大多数工作都是由那个大佬完成的,但可能对整个项目的完成而言,完全由大佬来完成的话,质量和效率可能比分工还高 /(ㄒoㄒ)/~~。以这段经历为例来说,在类似这种情况下,明星的利益最大化的同时就是团队的利益最大化的时候,这时是应该把资源全分配给明星,突出强调明星模式的优点,还是把资源兼顾给每个人?

新的理解

根据先前查了的资料:
几种常见的软件团队模式优缺点总结

文章中提到:

“ 2.主治医师模式:
优点:初衷很好,一个软件团队中,有首席程序员,负责主要模块的设计和编码,其他人尽可能从各个方面支持他的工作
缺点:在一些学校的软工课上,这种模式逐渐退化成“一个学生干活,其他学生打酱油”
3.明星模式:主治医师模式运用到极点
优点:对“明星”个人的成长进步可能会有所帮助
缺点:团队模式强调的是团队的作用,而不是个人的独角戏,这种模式显然违背了团队模式的初衷,效率也很低”

结合上我一学期以来的学习经验,我个人的理解是:
明星模式说到底还是团队模式,对于团队模式而言,团队才是核心,但是我还是存在有疑惑:有的情况下,如果以纯明星为核心的情况下,团队利益可以最大化,此时怎么进行明星和其他成员的利益取舍的度量?

个人感觉对于企业当然还是讲究效率质量最重要,得看具体情况哪个合适,没有定论。

问题三回顾

三、我看了第四章敏捷方法中的这一段文字:
“Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage 翻译:敏捷流程欢迎需求的变化, 并利用这种变化来提高用户的竞争优势。 ”

产品、策划需求频繁变更,这经常发生,却实在是令人头皮发麻。以我个人的理解,敏捷开发作为一种应对快速变化的需求的一种软件开发能力,通过快速迭代的方法来适应需求变化的原理,但是,对于那些已经实现完成的需求的变化,
应该是无条件接受吗?或者说对于需求变化的欢迎的极限应该在哪里?该怎么样去度量呢?

新的理解

根据先前查了的资料:
你如何理解敏捷开发?

文章中提到:

“在敏捷开发中,每个新特性必须有独立的测试。每个生产环境的变更必须通过严格的测试测试(在CD中通过单元测试、集成测试、性能测试等)。在不影响其他部分、不影响大版本规划的前提下,各个部分可以按需部署,用于快速响应游客的诉求、修复缺陷。 ”

结合上我一学期以来的学习经验,我个人的理解是:
对于需求变化是否接受,那应该说满足用户要求是相对的而不是绝对,因为每个客户的需求都不一样,在相对程度上尽量满足客户的需求。同时,产品、策划需求频繁变更不可避免,但是响应游客的诉求、修复缺陷可以不必一蹴而就,可以通过完成各个部分,循序渐进,一定程度上减小其造成的影响。
对于那些已经实现完成的需求的变化,应该是无条件接受吗?
当然是,因为客户的需求是最重要的,不能满足客户需求,怎么会有用户来用,更别说提示软件质量了。

问题四回顾

四、我看了第六章用户调研中的这一段文字:
“Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage 翻译:敏捷流程欢迎需求的变化, 并利用这种变化来提高用户的竞争优势。 ”

以我个人的理解,之前进行认知实习课程时,调查数据时就有用到问卷这一手段,但是当时遇到有的人填写时心不在焉,乱点一气,全选A、B等情况的发生,这对于后续的数据分析和得出结论会造成很大的影响。以这段经历为例来说,作为一名程序员,在开发软件的时候,想知道用户到底想的是什么,但是收集到这种乱填的无效的甚至有误导性的数据,该怎么合理的处理这些数据?

新的理解

根据先前查了的资料:
遇到乱写调查问卷的人怎么办?

文章中提到:
“筛选无效问卷是必要的一个工作,有一个筛选标准,筛选掉就好了。至于从根本上解决问题,除非你控制他们的思想……”

“调查过程中会有很多这种人,所以我们在收集完已经填完的问卷中,还要进行筛选。”

结合上我一学期以来的学习经验,我个人的理解是:
这些经验能给解决以上问题很大帮助。确实,乱填问卷这种情况不可避免,但是可以通过如设计陷阱题的方法解决,一定程度上减小其造成的影响,同时,对于收集进来的数据,要设计一定具体方法来进行筛选,从而使得数据准确性得以提高。
个人感觉当数据数多的时候,需要有专门的工具来筛排效率会更高。

问题五回顾

五、我看了第九章创新的出路 - 走进作坊中的这一段文字:
“作坊这么好, 那中国的许多作坊为什么不能兴旺? 一个大家常说的重要原因, 就是 “环境对知识产权的尊重和保护不够”, 其实哪里都有盗版, 哪里都有抄袭, 哪里都有竞争。有能力的作坊, 往往找到合适的渠道, 合适的空间, 实现自己的价值。 ”

提出这个问题,是感觉这段话与我的理解有所偏差。自己辛辛苦苦开发数载的软件,结果被别人恶意抄袭,恶意盗版,又或者刚有起色被某些垄断性大互联网企业要求收并或者盗用创意,以小工作室来说,无论是公关、宣传能力和研发水平,肯定是没法和这些大公司相比,在市场上与之竞争,生存空间必然会被挤压。“有能力的作坊, 往往找到合适的渠道, 合适的空间, 实现自己的价值。”这种话,相对而言,也太理想化了。同时,在这种情况下,小工作室到底该何去何从?

新的理解

根据先前查了的资料:
为什么多有小工作室被大公司收购后最终解散,如何看这个现象?
你如何避免软件被抄袭,包括它的商业模式?

文章中提到:
“被收购是小的工作室资本化的唯一途径。也别太高估了他们的盈利能力,不然干嘛卖身大厂呢,很可能就死亡和苟活的选择。有些人资本化了拿到钱就不好好做东西了,也别都算在大厂头上。玩家跟从业人员之间有一条巨大的鸿沟,就是对游戏成本认知的差异,基本上是6倍的关系... 按某些玩家的思路,大家都只能选择死亡了”
“不恰当的比方,跟买股票一个道理:表现好就长期持有,表现不好就抛掉止损。游戏研发商最大的特点是不管之前产品多么成功,不保证之后产品一定大麦。所以,可把控的只有这几项:
1.IP强不强
2.渠道质量高不高
3.量够不够
4.能不能搭边儿强势品牌
5.事件营销玩得6不6
6.配套运营有没有亮点(视频、电影微电影、直播、社区、赛事)很可惜,一旦被收购,这些都tm不是研发团队能管的。Sigh。”

“说实话这个事情很难: 我们国家的巨头可以通过很多手段让你的软件废掉 例如腾讯就可以依靠它强大的社交软件垄断,封锁你的软件,然后推出它的。。。。这个最有名的还是微软,扶持你的竞争对手来打压你等。。。就目前中国的局势来看,很难不被巨头所左右,真的还不如将版权或者软件做好了卖给那些巨头,特别是在最成功的时候”

结合上我一学期以来的学习经验,我个人的理解是:
确实,软件被恶意抄袭、盗用,这是可能发生的存在,作为一名工程师,在涉及到专利,著作权的,一定要提前做好专利申请,原创保护。信息化爆炸的时代,垄断问题再所难免,真正遇上了就仁者见仁,智者见智,做出对自己最有利的选择,相信随着国家法律的完善,此类问题也能一定程度上减少。
至于这种情况的改善,不是没有资本的人该想能想的。

请问你在项目的需求/设计/实现/测试/发布阶段(一共5个阶段)中,每个阶段收获最大的知识或能力是什么?

需求:用户调研
设计:数据库的应用
实现:vue的编写
测试:编写测试代码
发布阶段:团队协作

结合自己在个人项目/结对编程/团队项目的经历,谈谈自己的理解或心得

个人项目

在实现个人项目的过程中,锻炼了自己编写Java程序的能力,同时训练了自己的思维能力,也尝试并学会去利用Junit等工具对程序进行单元测试,同时在不断的测试中更加意识到性能优化的重要性,最重要的是学会使用PSP进行时间管理和总结,以后我会好好利用今日所学知识,努力做到最好,同时也学会不少新的知识,提高了自身能力,在锻炼的过程中成长、学习并进步,对我而言,是一次很有意义的实践。

结对编程

在结对编程中,因为我的项目经验比较少,实现的过程中有的概念会比较模糊,在此之前,对于一些框架也不了解,所以在项目前期,我比较没有头绪,大部分时间都在进行学习新的技术的过程中,但是后面投入实践中,我的队友总是会很耐心的跟我解释,让我加深理解了什么是框架,也学会不少新的知识,提高了自身能力。因为是两个人结对合作,所以也锻炼了书写规范的代码。这次结对合作,我们在磨合的过程中锻炼并成长,在合作的过程中学习并进步,对我们而言,是一次很有帮助的实践。

团队项目

这次团队项目,让我受益匪浅,也更体会到了自己还需继续努力,同时加深了我对团队合作的概念的理解。感谢队友们在开发过程中的相互理解和包容,让我更好地去适应职责的更替,我也可以在不断完成实践的过程中增长知识与本领。当然要学的还有很多很多,要完成的地方也还存在,但我相信,有了前面团队协作的经验,后续开发的效率会提高很多。感谢老师和助教们的用心,提供给我们这么好的一个机会去真正地提升自己在软件工程方面的能力。总而言之,这次软工实践团队项目对我而言是一次非常有意义的锻炼。

制定了学习路线,现在学习了怎么样了?

学习路线
血现在学习路线中的5-6月的总结回顾阶段,还在继续学习。

你在团队开发中是否担任了开发角色,你在开发中解决了哪些技术问题?获得了哪些技术进展?

技术概述

Vue中实现一个mvvm模式。
一个常规软件或者 APP 一般都是服务于某种商业或者非商业述求,我们平常称为“业务需求”。随着业务需求的扩张、一个软件会变得越来越庞大,越来越复杂。所以一般都会有一套完整的架构设计、研发流程以及质量管理体系来保证整个研发过程,其中的一个基础环节——MVVM模式。MVVM 模式顾名思义是由 Model、View 和 ViewModel(下称 VM )组成,之前的 VC 变成了 VM。

技术详述

我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点:

1、实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者。

2、实现一个解析器Compile解析页面节点指令,初始化视图。

3、实现一个观察者Watcher,订阅数据变化同时绑定相关更新函数。并且将自己放入观察者集合Dep中。Dep是Observer和Watcher的桥梁,数据改变通知到Dep,然后Dep通知相应的Watcher去更新视图。

在这里插入图片描述
下面是我学习时实现的简单案例

实现html页面

<body>
  <div id="app">
    <h2>{{person.name}} --- {{person.age}}</h2>
    <h3>{{person.fav}}</h3>
    <h3>{{person.a.b}}</h3>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <h3>{{msg}}</h3>
    <div v-text="msg"></div>
    <div v-text="person.fav"></div>
    <div v-html="htmlStr"></div>
    <input type="text" v-model="msg">
    <button v-on:click="click111">按钮on</button>
    <button @click="click111">按钮@</button>
  </div>
  <script src="./MVue.js"></script>
  <script src="./Observer.js"></script>
  <script>
    let vm = new MVue({
      el: '#app',
      data: {
        person: {
          name: '星哥',
          age: 18,
          fav: '姑娘',
          a: {
            b: '787878'
          }
        },
        msg: '学习MVVM实现原理',
        htmlStr: '<h4>大家学的怎么样</h4>',
      },
      methods: {
        click111() {
          console.log(this)
          this.person.name = '学习MVVM'
          // this.$data.person.name = '学习MVVM'
        }
      }
    })
  </script>

</body>

实现解析器和观察者

1 // 先创建一个MVue类,它是一个入口
  2 Class MVue {
  3     constructor(options) {  4         this.$el = options.el
  5         this.$data = options.data
  6         this.$options = options
  7     }
  8     if(this.$el) {
  9         // 1.实现一个数据的观察者     --先看解析器,再看Obeserver
 10         new Observer(this.$data)
 11         // 2.实现一个指令解析器
 12         new Compile(this.$el,this)
 13     }
 14 }
 15 ​
 16 // 定义一个Compile类解析元素节点和指令
 17 class Compile {
 18     constructor(el,vm) {
 19         // 判断el是否是元素节点对象,不是就通过DOM获取
 20         this.el = this.isElementNode(el) ? el : document.querySelector(el)
 21         this.vm = vm
 22         // 1.获取文档碎片对象,放入内存中可以减少页面的回流和重绘
 23         const fragment = this.node2Fragment(this.el)
 24         
 25         // 2.编辑模板
 26         this.compile(fragment)
 27         
 28         // 3.追加子元素到根元素(还原页面)
 29         this.el.appendChild(fragment)
 30     }
 31     
 32     // 将元素插入到文档碎片中
 33     node2Fragment(el) {
 34         const f = document.createDocumnetFragment();
 35         let firstChild
 36         while(firstChild = el.firstChild) {
 37             // appendChild 
 38             // 将已经存在的节点再次插入,那么原来位置的节点自动删除,并在新的位置重新插入。
 39             f.appendChild(firstChild)
 40         }
 41         // 此处执行完,页面已经没有元素节点了
 42         return f
 43     }
 44     
 45     // 解析模板
 46     compile(frafment) {
 47         // 1.获取子节点
 48         conts childNodes = fragment.childNodes;
 49         [...childNodes].forEach(child => {
 50             if(this.isElementNode(child)) {
 51                 // 是元素节点
 52                 // 编译元素节点
 53                 this.compileElement(child)
 54             } else {
 55                 // 文本节点
 56                 // 编译文本节点
 57                 this.compileText(child)
 58             }
 59             
 60             // 嵌套子节点进行遍历解析
 61             if(child.childNodes && child.childNodes.length) {
 62                 this.compule(child)
 63             }
 64         })
 65     }
 66     
 67     // 判断是元素节点还是属性节点
 68     isElementNode(node) {
 69         // nodeType属性返回 以数字值返回指定节点的节点类型。1-元素节点 2-属性节点
 70         return node.nodeType === 1
 71     }
 72     
 73     // 编译元素节点
 74     compileElement(node) {
 75         // 获得元素属性集合
 76         const attributes = node.attributes
 77         [...attributes].forEach(attr => {
 78             const {name, value} = attr
 79             if(this.isDirective(name)) { // 判断属性是不是以v-开头的指令
 80                 // 解析指令(v-mode v-text v-on:click 等...)
 81                 const [, dirctive] = name.split('-')
 82                 const [dirName, eventName] = dirctive.split(':')
 83                 // 初始化视图 将数据渲染到视图上
 84                 compileUtil[dirName](node, value, this.vm, eventName)
 85                 
 86                 // 删除有指令的标签上的属性
 87                 node.removeAttribute('v-' + dirctive)
 88             } else if (this.isEventName(name)) { //判断属性是不是以@开头的指令
 89                 // 解析指令
 90                 let [, eventName] = name.split('@')
 91                 compileUtil['on'](node,val,this.vm, eventName)
 92                 
 93                 // 删除有指令的标签上的属性
 94                 node.removeAttribute('@' + eventName)
 95             } else if(this.isBindName(name)) { //判断属性是不是以:开头的指令
 96                 // 解析指令
 97                 let [, attrName] = name.split(':')
 98                 compileUtil['bind'](node,val,this.vm, attrName)
 99                 
100                 // 删除有指令的标签上的属性
101                 node.removeAttribute(':' + attrName)
102             }
103         }) 
104     }
105     
106     // 编译文本节点
107     compileText(node) {
108         const content = node.textContent
109         if(/\{\{(.+?)\}\}/.test(content)) {
110             compileUtil['text'](node, content, this.vm)
111         }
112     }
113     
114     // 判断属性是不是指令
115     isDirective(attrName) {
116         return attrName.startsWith('v-')
117     }
118     // 判断属性是不是以@开头的事件指令
119     isEventName(attrName) {
120         return attrName.startsWith('@')
121     }
122     // 判断属性是不是以:开头的事件指令
123     isBindName(attrName) {
124         return attrName.startsWith(':')
125     }
126 }
127 ​
128 ​
129 // 定义一个对象,针对不同指令执行不同操作
130 const compileUtil = {
131     // 解析参数(包含嵌套参数解析),获取其对应的值
132     getVal(expre, vm) {
133         return expre.split('.').reduce((data, currentVal) => {
134             return data[currentVal]
135         }, vm.$data)
136     },
137     // 获取当前节点内参数对应的值
138     getgetContentVal(expre,vm) {
139         return expre.replace(/\{\{(.+?)\}\}/g, (...arges) => {
140             return this.getVal(arges[1], vm)
141         })
142     },
143     // 设置新值
144     setVal(expre, vm, inputVal) {
145         return expre.split('.').reduce((data, currentVal) => {
146             return data[currentVal] = inputVal
147         }, vm.$data)
148     },
149     
150     // 指令解析:v-test
151     test(node, expre, vm) {
152         let value;
153         if(expre.indexOf('{{') !== -1) {
154             // 正则匹配{{}}里的内容
155             value = expre.replace(/\{\{(.+?)\}\}/g, (...arges) => {
156                 
157                 // new watcher这里相关的先可以不看,等后面讲解写到观察者再回头看。这里是绑定观察者实现     的效果是通过改变数据会触发视图,即数据=》视图。
158                 // 没有new watcher 不影响视图初始化(页面参数的替换渲染)。
159                 // 订阅数据变化,绑定更新函数。
160                 new watcher(vm, arges[1], () => {
161                     // 确保 {{person.name}}----{{person.fav}} 不会因为一个参数变化都被成新值 
162                     this.updater.textUpdater(node, this.getgetContentVal(expre,vm))
163                 })
164                 
165                 return this.getVal(arges[1],vm)
166             })
167         } else {
168             // 同上,先不看
169             // 数据=》视图
170             new watcher(vm, expre, (newVal) => {
171             // 找不到{}说明是test指令,所以当前节点只有一个参数变化,直接用回调函数传入的新值
172         this.updater.textUpdater(node, newVal)
173           })
174             
175             value = this.getVal(expre,vm)
176         }
177         
178         // 将数据替换,更新到视图上
179         this.updater.textUpdater(node,value)
180     },
181     //指令解析: v-html
182     html(node, expre, vm) {
183         const value = this.getVal(expre, vm)
184         
185         // 同上,先不看
186         // 绑定观察者 数据=》视图
187         new watcher(vm, expre (newVal) => {
188             this.updater.htmlUpdater(node, newVal)
189         })
190         
191         // 将数据替换,更新到视图上
192         this.updater.htmlUpdater(node, newVal)
193     },
194     // 指令解析:v-mode
195     model(node,expre, vm) {
196         const value = this.getVal(expre, vm)
197         
198         // 同上,先不看
199         // 绑定观察者 数据=》视图
200         new watcher(vm, expre, (newVal) => {
201             this.updater.modelUpdater(node, newVal)
202         })
203         
204         // input框  视图=》数据=》视图
205         node.addEventListener('input', (e) => {
206             //设置新值 - 将input值赋值到v-model绑定的参数上
207             this.setVal(expre, vm, e.traget.value)
208         })
209         // 将数据替换,更新到视图上
210         this.updater.modelUpdater(node, value)
211     },
212     // 指令解析: v-on
213     on(node, expre, vm, eventName) {
214         // 或者指令绑定的事件函数
215         let fn = vm.$option.methods && vm.$options.methods[expre]
216         // 监听函数并调用
217         node.addEventListener(eventName,fn.bind(vm),false)
218     },
219     // 指令解析: v-bind
220     bind(node, expre, vm, attrName) {
221         const value = this.getVal(expre,vm)
222         this.updater.bindUpdate(node, attrName, value)
223     }
224     
225 // updater对象,管理不同指令对应的更新方法
226 updater: {
227         // v-text指令对应更新方法
228         textUpdater(node, value) {
229             node.textContent = value
230         },
231         // v-html指令对应更新方法
232         htmlUpdater(node, value) {
233             node.innerHTML = value
234         },
235         // v-model指令对应更新方法
236         modelUpdater(node,value) {
237             node.value = value
238         },
239         // v-bind指令对应更新方法
240         bindUpdate(node, attrName, value) {
241             node[attrName] = value
242         }
243     },
244 }

实现数据劫持监听:
有了数据监听,还需要一个观察者可以触发更新视图。因为需要数据改变才能触发更新,所有还需要一个桥梁Dep收集所有观察者(观察者集合),连接Observer和Watcher。数据改变通知Dep,Dep通知相应的观察者进行视图更新。

// 定义一个观察者
class watcher {
    constructor(vm, expre, cb) {
        this.vm = vm
        this.expre = expre
        this.cb =cb
        // 把旧值保存起来
        this.oldVal = this.getOldVal()
    }
    // 获取旧值
    getOldVal() {
        // 将watcher放到targe值中
        Dep.target = this
        // 获取旧值
        const oldVal = compileUtil.getVal(this.expre, this.vm)
        // 将target值清空
        Dep.target = null
        return oldVal
    }
    // 更新函数
    update() {
        const newVal =  compileUtil.getVal(this.expre, this.vm)
        if(newVal !== this.oldVal) {
            this.cb(newVal)
        }
    }
}
​
​
// 定义一个观察者集合
class Dep {
    constructor() {
        this.subs = []
    }
    // 收集观察者
    addSub(watcher) {
        this.subs.push(watcher)
    }
    //通知观察者去更新
    notify() {
        this.subs.forEach(w => w.update())
    }
}
​
​
​
// 定义一个Observer类通过gettr,setter实现数据的监听绑定
class Observer {
    constructor(data) {
        this.observer(data)
    }

    // 定义函数解析data,实现数据劫持
    observer (data) {
        if(data && typeof data === 'object') {
            // 是对象遍历对象写入getter,setter方法
            Reflect.ownKeys(data).forEach(key => {
                this.defineReactive(data, key, data[key]);
            })
        }
    }

    // 数据劫持方法
    defineReactive(obj,key, value) {
        // 递归遍历
        this.observer(data)
        // 实例化一个dep对象
        const dep = new Dep()
        // 通过ES5的API实现数据劫持
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get() {
                // 当读当前值的时候,会触发。
                // 订阅数据变化时,往Dep中添加观察者
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newValue) => {
                // 对新数据进行劫持监听
                this.observer(newValue)
                if(newValue !== value) {
                    value = newValue
                }
                // 告诉dep通知变化
                dep.notify()
            }
        })
    }

}

技术使用中遇到的问题和解决过程

遇到主要问题是滥用了vue中的watch,因为使用中有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是在之前使用过 AngularJS的时候。
解决方法为:Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>



var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

确实一眼就能看出来好多了

总结

感谢老师和助教们的用心,提供给我们这么好的一个机会去真正地提升自己在软件工程方面的能力。总而言之,这次软工实践对我而言是一次非常有意义的锻炼。学得越多也发现自己有越多要学,继续努力吧!

参考文献、参考博客

Vue官方教程
对Vue中的MVVM原理解析和实现--小羽羽

posted @ 2021-06-26 13:50  FZU-CQ  阅读(169)  评论(3编辑  收藏  举报