Vue底层架构及其应用(上)转

https://mp.weixin.qq.com/s?__biz=MzIzNjcwNzA2Mw==&mid=2247486427&idx=1&sn=61f9579bbe1dfe26da4b53eb538fee13&chksm=e8d28643dfa50f557c56ce8b5bc9b0597a157a20791e21b1812fe2a30ff4cf2c813608473b43&mpshare=1&scene=23&srcid=#rd

一、前言

市面上有很多基于vue的core和compile做出的优化开源框架,为非Web场景引入了Vue的能力,因此学习成本低,受到广大开发者的欢迎,下面大体列一下我所了解到的,有更优秀的欢迎大家评论指出

跨平台native

weex

小程序

mpvue

服务端渲染

Vue SSR

小程序多端统一框架

uni-app

 

至于提供类Vue开发体验的框架就数不胜数了,如小程序框架--wepy. 从其他的方面看,github日榜,Vue每天都有过100的star,足见其火热程度,这也是为什么大家都争先恐后的在非web领域提供Vue的支持。那么Vue的底层架构及其应用就尤为重要了

二、Vue底层架构

了解Vue的底层架构,是为非web领域提供Vue能力的大前提。Vue核心分为三大块:core,compiler,platform,下面本文将详细介绍其中Core的部分。

core是Vue的灵魂所在,正是core实现了通过vnode方式,递归生成指定平台视图并在数据变动时,自动diff更新视图,也正是因为VNode机制,使得core是平台无关的,就算core的功能在于UI渲染。

我将从如下几个方面来说明core

  • 挂载

  • 指令

  • Vnode----划重点

  • 组件实例vm及vm间的关系

  • nextTick

  • Watcher----划重点

  • vnode diff算法----划重点

  • core总结

1.1 挂载

将vnode生成的具体平台元素append到已知节点上。我们拿web平台举例,用vnode通过document.createElement生成dom,然后在append到文档树中某个节点上。后面我们也会经常说到挂载组件,它指的就是执行组件对应render生成vnode,然后遍历vnode生成具体平台元素,组件的根节点元素会被append到父元素上。

1.2 指令

指令在Vue中是具有特定含义的属性,指令分两类,一类是编译时处理,在生成的render函数上体现,如:v-if,v-for,另外一类是运行时使用,更多的是对生成的具体平台元素操作,web平台的话就是对dom的操作

1.3 VNode——划重点

vnode是虚拟node节点,是具体平台元素对象的进一步抽象(简化版),每一个平台元素对应一个vnode,可通过vnode结构完整还原具体平台元素结构。
下面以web平台来解释vnode。对于web,假定有如下结构:

<div class="box" @click="onClick">------------------对应一个vnode <p class="content">哈哈</p>-------对应一个vnode <TestComps></TestComps>----------自定义组件同样对应一个vnode <div></div>-----------------------对应一个vnode</div>

经过Vue的compile模块将生成渲染函数,执行这个渲染函数就会生成对应的vnode结构:

//这里我只列出关键的vnode信息{  tag:'div',  data:{attr:{},staticClass:'box',on:{click:onClick}},  children:[{    tag:'p',    data:{attr:{},staticClass:'content',on:{}},    children:[{      tag:'',      data:{},      text:'哈哈'    }]  },{    tag:'div',    data:{attr:{},on:{}},  },{    tag:'TestComps',    data:{        attr:{},        hook:{            init:fn,                       prepatch:fn,            insert:fn,           destroy:fn        }     },  }]  }

最外层的div对应一个vnode,包含三个孩子vnode,注意自定义组件也对应一个vnode,不过这个vnode上挂着组件实例

1.4 组件实例vm及vm间的关系——划重点

组件实例其实就是Vue实例对象,只有自定义组件才会有,平台相关元素是没有的,要看懂Vue的core,明白下面这个关系很重要。现在,让我们来直观感受下:

假定有如下结构的模板,元素上的vnode表示生成的对应vnode名称:

// new Vue的template,对应的实例记为vm1```<div vnode1>  <p vnode2></p>  <TestComps vnode3             testAttr="hahha"             @click="clicked"             :username="username"             :password="password"></TestComps></div>```// TestComps的template,对应的实例记为vm2```<div vnode4>  <span vnode5></span>  <p vnode6></p></div>```// 生成的vnode关系树为```vnode1={  tag:'div',  children:[vnode2,vnode3]}vnode3={  tag:'TestComps',  children:undefined,  parent:undefined}vnode4={  tag:'div',  children:[vnode5,vnode6],  parent:vnode3             //这一点关系很重要}```// 生成的vm关系树为```vm1={  $data:{password: "123456",username: "aliarmo"}, //组件对应state  $props:{} //使用组件时候传下来到模板里面的数据  $attrs:{},  $children:[vm2],               $listeners:{}  $options: {    components: {}    parent: undefined   //父组件实例    propsData: undefined    //使用组件时候传下来到模板里面的数据    _parentVnode: undefined   }  $parent:undefiend               //当前组件的父组件实例  $refs:{}                 //当前组件里面包含的dom引用  $root:vm1                 //根组件实例  $vnode:undefined                 //组件被引用时候的那个vnode,比如<TestComps></TestComps>  _vnode:vnode1       //当前组件模板根元素所对应的vnode对象}
vm2={ $data:{} //组件对应state $props:{password: "123456",username: "aliarmo"} //使用组件时候传下来到模板里面的数据 $attrs:{testAttr:'hahha'}, $children:[], $listeners:{click:fn} $options: { components: {} parent: vm1 //父组件实例 propsData: {password: "123456",username: "aliarmo"} //使用组件时候传下来到模板里面的数据 _parentVnode: vnode3 } $parent:vm1 //当前组件的父组件实例 $refs:{} //当前组件里面包含的dom引用 $root:vm1 //根组件实例 $vnode:vnode3 //组件被引用时候的那个vnode,比如<TestComps></TestComps> _vnode:vnode4 //当前组件模板根元素所对应的vnode对象}```

 

1.5 nextTick

它可以让我们在下一个事件循环做一些操作,而非在本次循环,用于异步更新,原理在于microtask和macrotask

让我们来看段代码:

new Promise(resolve=>{  return 123}).then(data=>{  console.log('step2',data)})console.log('step1')

结果是先输出 step1,然后在step2,resolve的promise是一个microtask,同步代码是macrotask

// 在Vue中this.username='aliarmo'   // 可以触发更新this.pwd='123'                  // 同样可以触发更新

那同时改变两个state,是否会触发两次更新呢,并不会,因为this.username触发更新的回调会被放入一个通过Promise或者MessageChannel实现的microtask中,亦或是setTimeout实现的macrotask,总之到了下一个事件循环。

1.6 Watcher——划重点

一个组件对应一个watcher,在挂载组件的时候创建这个观察者,组件的state,包含data,props都是被观察者,被观察者的任何变化会被通知到观察者,被观察者的变动导致观察者执行的动作是vm._update(vm._render(), hydrating),组件重新render生成vnode并patch。

明白这个关系很重要:观察者包含对变动做出响应的定义,一个组件对应一个观察者对应组件里面的所有被观察者,被观察者可能被用于其他组件,那么一个被观察者会对应多个观察者,当被观察者发生变动时,通知到所有观察者做出更新响应。

组件A的state1发生了变化,那会导致观察了这个state1的watcher收到变动通知,会导致组件A重新渲染生成新的vnode,在组件A新vnode和老的vnode patch的过程中,会updateChildrenComponent,也就是导致子组件B的props被重新设置一个新值,因为子组件B是有观察传入的state1的,因此会通知到相应watcher,导致子组件B的更新

整个watcher体系的建立过程:

  1. 创建组件实例的时候会对data和props进行observer,

  2. 对传入的props进行浅遍历,重新设定属性的属性描述符get和set,如果props的某个属性值为对象,那么这个对象在父组件是被深度observe过的,所以props是浅遍历

  3. observer会深度遍历data,对data所包含属性重新定义,即defineReactive,重新设定属性描述符的get和set

  4. 在mountComponent的时候,会new Wacther,当前watcher实例会被pushTarget,设定为目标watcher,然后执行vm._update(vm._render(), hydrating),执行render函数导致属性的get函数被调用,每个属性会对应一个dep实例,在这个时候,dep实例关联到组件对应的watcher,实现依赖收集,关联后popTarget。

  5. 如果有子组件,会导致子组件的实例化,重新执行上述步骤

state变动响应过程:

  1. 当state变动后,调用属性描述符的set函数,dep会通知到关联的watcher进入到nextTick任务里面,这个watcher实例的run函数包含vm._update(vm._render(), hydrating),执行这个run函数,导致重新生成vnode,进行patch,经过diff,达到更新UI目的

父组件state变化如何导致子组件也发生变化?

父组件state更新后,会导致渲染函数重新执行,生成新的vnode,在oldVnode和newVnode patch的过程中,如果遇到的是组件vnode,会updateChildrenComponent,这里面做的操作就是更新子组件的props,因为子组件是有监听props属性的变动的,导致子组件re-render

父组件传入一个对象给子组件,子组件改变传入的对象props,父组件又是如何被更新到的?
大前提:如果父组件传给子组件的props中有对象,那么子组件接收到的是这个对象的引用。也就是ParentComps中的this.person和SubComps中的this.person指向同一个对象

// 假定父组件传person对象给子组件SubCompsVue.component('ParentComps',{  data(){    return {      person:{        username:'aliarmo',        pwd:123      }    }  },  template:`    <div>      <p>{{person.username}}</p>      <SubComps :person="person" />    </div>  `})

 

现在我们在SubComps里面,更新person对象的某个属性,如:this.person.username='wmy' 这样会导致ParentComps和SubComps的更新,为什么呢?

因为Vue在ParentComps中会深度递归观察对象的每个属性,在第一次执行ParentComps的render的时候,绑定ParentComps的Watcher,传入到SubComps后,不会对传入的对象在进行观察,在第一次执行SubComps的render的时候,会绑定到SubComps的Watcher,因此当SubComps改变了this.person.username的值,会通知到两个Watcher,导致更新。这很好的解释了凭空在传入的props属性对象上挂载新的属性不触发渲染,因为传入的props属性对象是在父组件被观察的。

1.7 vnode diff算法——划重点

当组件的state发生变化,重新执行渲染函数生成新的vnode,然后将新生成的vnode与老的vnode进行对比,以最小的代价更新原有视图。diff算法的原理是通过移动、新增、删除和替换oldChildrenVnodes对应的结构来生成newChildrenVnodes对应的结构,并且每个老的元素只能被复用一次,老元素最终的位置取决于当前新的vnode。要明确传入diff算法的是两个sameVnode的孩子节点,从两者的开头和结尾位置,同时往中间靠,直到两者中的一个到达中间。

PS:oldChildrenVnodes表示老的孩子vnode节点集合,newChildrenVnodes表示state变化后生成的新的孩子vnode节点集合

说这个算法之前,先得明白如何判断两个vnode为sameVnode,我只大体列一下:

  1. vnode的key值相等,例如 <Comps1 key="key1" />,<Comps2 key="key2" />,key值就不相等,<Comps1 key="key1" />,<Comps2 key="key1" />,key值就是相等的,<div></div>,<p></p>,这两个的key值是undefined,key值相等,这个是sameVnode的大前提。

  2. vnode的tag相同,都是注释或者都不是注释,同时定义或未定义data,标签为input则type必须相同,还有些其他的条件跟我们不太相关就不列出来了。

整个vnode diff流程

大前提,要看懂这个vnode diff,务必先明白vnode是啥,如何生成的,vnode与elm的关系,详情请看上面的vnode概念

  1. 如果两个vnode是sameVnode,则进行patch vnode

  2. patch vnode过程

    (1)首先vnode的elm指向oldVnode的elm

    (2)使用vnode的数据更新elm的attr,class,style,domProps,events等

    (3)如果vnode是文本节点,则直接设置elm的text,结束

    (4)如果vnode是非文本节点&&有孩子&&oldVnode没有孩子,则elm直接append

    (5)如果vnode是非文本节点&&没有孩子&&oldVnode有孩子,则直接移除elm的孩子节点

    (6)如果非文本节点&&都有孩子节点,则updateChildren,进入diff 算法,前面5个步骤排除了不能进行diff情况

  3. diff 算法,这里以web平台为例

这里还有强调下,传入diff算法的是两个sameVnode的孩子节点,那么如何用newChildrenVnodes替换oldChildrenVnodes,最简单的方式莫过于,遍历newChildrenVnodes,直接重新生成这个html片段,皆大欢喜。但是这样做会 不断的createElement,对性能有影响,于是前辈们就想出了这个diff算法。

(1)取两者最左边的节点,判断是否为sameVnode,如果是则进行上述的第二步patch vnode过程,整个流程走完后,此时elm的class,style,events等已经更新了,elm的children结构也通过前面说的整个流程得到了更新,这时候就看是否需要移动这个elm了,因为都是孩子的最左边节点,因此位置不变,最左边节点位置向前移动一步 

(2)如果不是(1)所述case,取两者最右边的节点,跟(1)的判定流程一样,不过是最右边节点位置向前移动一步 

(3)如果不是(1)(2)所述case,取oldChildrenVnodes最左边节点和newChildrenVnodes最右边节点,跟(1)的判定流程一样,不过,elm的位置需要移动到oldVnode最右边elm的右边,因为vnode取的是最右边节点,如果与oldVnode的最右边节点是sameVnode的话,位置是不用改变的,因此newChildrenVnodes的最右节点和oldChildrenVnodes的最右节点位置是对应的,但由于是复用的oldChildrenVnodes的最左边节点,oldChildrenVnodes最右边节点还没有被复用,因此不能替换掉,所以移动到oldChildrenVnodes最右边elm的右边。然后oldChildrenVnodes最左边节点位置向前移动一步,newChildrenVnodes最右边节点位置向前移动一步 

(4)如果不是(1)(2)(3)所述case,取oldChildrenVnodes最右边节点和newChildrenVnodes最左边节点,跟(1)的判定流程一样,不过,elm的位置需要移动到oldChildrenVnodes最左边elm的左边,因为vnode取的是最左边节点,如果与oldChildrenVnodes的最左边节点是sameVnode的话,位置是不用改变的,因此newChildrenVnodes的最左节点和oldChildrenVnodes的最左节点位置是对应的,但由于是复用的oldChildrenVnodes的最右边节点,oldChildrenVnodes最左边节点还没有被复用,因此不能替换掉,所以移动到oldChildrenVnodes最左边elm的左边。然后oldChildrenVnodes最右边节点位置向前移动一步,newChildrenVnodes最左边节点位置向前移动一步 

(5)如果不是(1)(2)(3)(4)所述case,在oldChildrenVnodes中寻找与newChildrenVnodes最左边节点是sameVnode的oldVnode,如果没有找到,则用这个新的vnode创建一个新element,插入位置如后所述,如果找到了,则跟(1)的判定流程一样,不过插入的位置是oldChildrenVnodes的最左边节点的左边,因为如果newChildrenVnodes最左边节点与oldChildrenVnodes最左边节点是sameVnode的话,位置是不用变的,并且复用的是oldChildrenVnodes中找到的oldVNode的elm。被复用过的oldVnode后面不会再被取出来。然后newChildrenVnodes最左边节点位置向前移动一步 

(6)经过上述步骤,oldChildrenVnodes或者newChildrenVnodes的最左节点与最右节点重合,退出循坏 

(7)如果是oldChildrenVnodes的最左节点与最右节点先重合,说明newChildrenVNodes还有节点没有被插入,递归创建这些节点对应元素,然后插入到oldChildrenVnodes的最左节点的右边或者最右节点的左边,因为是从两者的开始和结束位置向中间靠拢,想想,如果newChildrenVNodes剩余的第一个节点与oldChildrenVnodes的最左边节点为sameVnode的话,位置是不用变的 

(8)如果是newChildrenVnodes的最左节点与最右节点先重合,说明oldChildrenVnodes中有一段结构没有被复用,开始和结束位置向中间靠拢,因此没有被复用的位置是oldChildrenVnodes的最左边和最右边之间节点,删除节点对应的elm即可。

举个栗子来描述下具体的diff过程(web平台):

// 有Vue模板如下<div>    ------ oldVnode1,newVnode1,element1  <span v-if="isShow1"></span> -------oldVnode2,newVnode2,element2  <div :key="key"></div> -------oldVnode3,newVnode3,element3  <p></p> -------oldVnode4,newVnode4,element4  <div v-if="isShow2"></div> -------oldVnode5,newVnode5,element5</div>
// 如果 isShow1=true,isShow2=true,key="aliarmo"那么模板将会渲染成如下:<div> <span></span>--------------element2 <div key="aliarmo"></div>----------element3 <p></p>-------------element4 <div></div>----------element5</div>
// 改变state,isShow1=false,isShow2=true,key="wmy",那么模板将会渲染成如下:<div> <div key="wmy"></div>------------element6 <p></p>-------------------element4 <div></div>---------element5</div>

那么,改变state后的dom结构是如何生成的?

如上图,在isShow1=true,isShow2=true,key="aliarmo"条件下,生成的vnode结构是:
oldVnode1,oldVnodeChildren=[oldVnode2,oldVnode3,oldVnode4,oldVnode5]

对应的dom结构为:

改变state为isShow1=false,isShow2=true,key="wmy"后,生成的新vnode结构是

newVnode1,newVnodeChildren=[newVnode3,newVnode4,newVnode5]

最左边两个新老vnode对比,也就是oldVnode2,newVnode3,不是sameVnode,

那最右边两个新老vnode对比,也就是oldVnode5newVnode5,是sameVnode,不用移动原来的Element5所在位置,原有dom结构未发生变化,

最左边两个新老vnode对比,也就是oldVnode2,newVnode3,不是sameVnode,

那最右边两个新老vnode对比,也就是oldVnode4newVnode4是sameVnode,不用移动原来的Element4所在位置,原有dom结构未发生变化,

最左边两个新老vnode对比,也就是oldVnode2,newVnode3,不是sameVnode,

那最右边两个新老vnode对比,也就是oldVnode3newVnode3,由于key值不同,不是sameVnode,

当前最左边和最右边对比,oldVnode2,newVnode3,不是sameVnode

当前最右边和最左边对比,oldVnode5,newVnode3,不是sameVnode

在遍历oldVnodeChildren,寻找与newVnode3为sameVnode的oldVnode,没有找到,则用newVnode3创建一个新的元素Element6,插入到当前oldVnode2所对应元素的最左边,dom结构发生变化

newVnodeChildren两头重合,退出循环,删除剩余未被复用元素Element2,Element3

1.8 core总结

现在我们终于可以理一下,从new Vue()开始,core里面发生了些什么

  1. new Vue()或者new自定义组件构造函数(继承自Vue)

  2. 初始化,props,methods,computed,data,watch,并给state加上Observe,调用生命周期created

  3. 开始mount组件,mount之前确保render函数的生成

  4. new Watcher,导致render和patch,注意一个watcher对应一个组件,watcher对变化的响应是重新执行render生成vnode进行patch

  5. render在当前组件上下文(组件实例)执行,生成对应的vnode结构

  6. 如果没有oldVnode,那patch就是深度遍历vnode,生成具体的平台元素,给具体的平台元素添加属性和绑定事件,调用自定义指令提供的钩子函数,并append到已存在的元素上,在遍历的过程中,如果遇到的是自定义组件,则从**步骤1**开始重复

  7. 如果有oldVnode,那patch就是利用vnode diff算法在原有的平台元素上进行修修补补,不到万不得已不创建新的平台元素

  8. state发生变化,通知到state所在组件对应的watcher,重新执行render生成vnode进行patch,也就是回到**步骤4**

 

鉴于篇幅,本次分享的上半部分到此结束。Compiler与部分以及应用与总结见下半部分的文章。敬请期待!

转载于:https://www.cnblogs.com/rubyxie/articles/10950050.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jquery笔记一:下载安装、语法、选择器、遍历选择元素的方法、jQuery动画

目前 jQuery 兼容于所有主流浏览器, 包括 IE 6&#xff01;开发时常用 jquery.js&#xff0c;上线用 jquery.min.js。 jq插件 目前jQuery有三个大版本&#xff1a; &#xff08;1&#xff09;1.x.x: 兼容ie6,7,8&#xff0c;使用最为广泛&#xff0c;官网只做BUG维护&#xff…

jquery简介 each遍历 prop attr

一、JQ简介 jQuery是一个快速、简洁的JavaScript框架&#xff0c;它封装了JavaScript常用的功能代码&#xff0c;提供一种简便的JavaScript设计模式&#xff0c;优化HTML文档操作、事件处理、动画设计和Ajax交互。 装载的先后次序&#xff1a;  jQuery封装库在上&#xff0…

如何让Visitor变得可爱1

本文转自&#xff1a;http://www.cnblogs.com/idior/archive/2005/01/19/94280.html 在wayfarer的文章中提到了如何利用visitor模式实现添加新的功能。如他所说&#xff0c;在实际过程中显的不是那么可爱。不过他为我们提供了一个可行的解决方案&#xff0c;本文将在此基础上使…

EJB 3.x:生命周期和并发模型(第1部分)

Java EE组件生命周期和与并发相关的详细信息对于经验丰富的专业人员而言可能不是新知识&#xff0c;但是对于初学者而言&#xff0c;这可能需要花费一些时间。 就EJB而言&#xff0c;了解其生命周期 &#xff08;以及相关的并发场景&#xff09;对于确保使用EJB的正确用法和解…

单独使用 laydate 日期时间组件

layui 日期和时间组件官方文档 需要注意几点&#xff1a; 下载压缩包后&#xff0c;复制整个 laydate 文件夹放到项目中&#xff0c;里面的文件不要改动位置&#xff0c;否则会报错&#xff1b;在移动端使用时&#xff0c;要给 input 添加 readonly 属性&#xff0c;否则点击…

从零开始学习SVG

1 什么是SVG&#xff1f; MDN中的定义是&#xff1a;SVG即可缩放矢量图形&#xff08;Scalable Vector Graphics&#xff0c;SVG)&#xff0c;是一种用来描述二维矢量图形的 XML 标记语言。 简单地说&#xff0c;SVG 面向图形&#xff0c;HTML 面向文本。SVG 与 Flash 类似&am…

layui 关于layDate设置时间限制问题

前面是尝试结果&#xff0c;有兴趣的可以康康&#xff0c;赶时间的可以直接翻到底下 ----->直达车 因为只有一个页面&#xff0c;没什么标准&#xff0c;所以自己就使用了layui的时间控件&#xff0c;并且作为独立组件。 第一次尝试 - 使用 min 一开始只想让结束时间有个…

QQ聊天记录快速迁移

QQ聊天记录快速迁移 在工作中大家经常会用到QQ来沟通&#xff0c;但是很多时候在其它设备上登录QQ就无法查看到之前的聊天记录和图片&#xff0c;这是因为电脑上的QQ聊天记录一般都是保存在电脑本地硬盘里&#xff0c;所以我们在换设备登录QQ后&#xff0c;是无法查看到之前电脑…

Hawtio和Jolokia的休眠统计

企业Java的很大一部分处理数据。 在企业设置中使用数据的所有不同方式中&#xff0c;仍然存在使用任何种类的O / R映射的行之有效且广泛教授的方法。 JPA标准使每个人都可以轻松使用它&#xff0c;并且它也应该是可移植的。 但是&#xff0c;我们不要谈论迁移细节。 O / R映射的…

laydate 时间控件去掉秒以及解决在移动端不能滑动的问题

一、时间控件去掉秒&#xff0c;保留时分 二、时间控件在移动端不能滚动 一、时间控件去掉秒&#xff0c;保留时分 方法一&#xff1a;使用 ready 回调函数 ready 控件在打开时触发。打开控件时让秒消失。 <script> laydate.render({elem: #endTime, //指定元素trigg…

Entity Framework Code First属性映射约定 转载https://www.cnblogs.com/libingql/p/3352058.html

Entity Framework Code First属性映射约定 Entity Framework Code First与数据表之间的映射方式有两种实现&#xff1a;Data Annotation和Fluent API。本文中采用创建Product类为例来说明tity Framework Code First属性映射约定的具体方式。 1. 表名及所有者 在默认约定的情况下…

layui 时间控件二次渲染,点击一个自定义按钮清空/重置时间控件

标题小标题一、错误尝试二、时间控件二次渲染的3种方法2.1 直接删除原来绑定时间控件的DOM&#xff0c;再重新渲染&#xff08;推荐&#xff09;&#xff1b;2.2 使用 clone() &#xff0c;并且改变 lay-key&#xff1b;2.3 使用 clone() &#xff0c;并且删除 lay-key&#xf…

2019年春第二次课程设计实验报告

一、试验项目名 贪吃蛇 二、试验功能介绍 通过数组构造小蛇&#xff0c;在通过数组中的变化控制小蛇移动和变长。 三、项目模块结构介绍 构造小蛇 小蛇移动 移动中碰到边框或自己失败 小蛇长大 四、实现界面展示 五、代码托管链接https://gitee.com/t001023/software_class_1_t…

Elasticsearch用例:全文搜索

在本系列有关Elasticsearch用例的最后一篇文章中&#xff0c;我们介绍了Elasticsearch提供的用于存储甚至大量文档的功能 。 在这篇文章中&#xff0c;我们将研究其另一个核心功能&#xff1a;搜索。 我正在利用上一篇文章中的某些信息&#xff0c;因此&#xff0c;如果您还没有…

使用echarts时,鼠标首次移入屏幕会闪动,屏幕会出现滚动条

当我刷新&#xff0c;鼠标经过图表时&#xff0c;页面会出现滚动条 原因&#xff1a; 在echarts图表中出现 tooltip 时&#xff0c;画布的父标签&#xff08;即&#xff1a;echarts.init()的标签&#xff09;的宽高有时会发生变化&#xff0c;导致相对布局的div可能大小发生…

移动端 flexible.js 布局详解

原本想直接引入原文链接&#xff0c;但是又担心作者哪天想不开注销账号&#xff0c;这么好的一篇文章看不到了&#xff0c;还是转载一下吧(/ω&#xff3c;)。 另外推荐一篇好文&#xff1a;移动端rem自适应实操讲解 本文讲的通过 flexible.js 实现了rem自适应&#xff0c;有了…

Gradle善良:获得更多的依赖性见解

在我们的大多数项目中&#xff0c;我们都依赖于其他代码&#xff0c;例如库或其他项目。 Gradle有一个不错的DSL来定义依赖关系。 依赖性在依赖性配置中分组。 这些配置可以自己创建&#xff0c;也可以通过插件添加。 一旦定义了依赖项&#xff0c;我们就可以通过dependencies任…

js css模仿打字效果

1.效果 2.源码 <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><style type"text/css">#myDiv{display: inline-block;width:500px;height:300px;background-color:rgba(0,0,0,0.3);colo…

iframe 高度根据子页面来确定

标题描述一、解决方法解决代码二、关于高度问题简单讲一下jquery中的 height()&#xff0c;innerHeight()、outHeight()&#xff0c;js中的offsetHeight、clientHeight、scrollHeight。如何获取没有给出高度的元素的高度&#xff1f;详细介绍offsetHeight,clientHeight,scrollH…

layui 子页面写弹出框覆盖父页面,以及给弹框中的表单赋值

咋说呢&#xff0c;因为对 layui 不太熟悉&#xff0c;这个弹出框搞了好久&#xff0c;看了好多解决方案&#xff0c;大致尝试了一下其中几种&#xff0c;在坑中无法自拔。。。总之终于搞出来了&#xff0c;在这里分享一下我的笔记。 着急的直接 戳这里 看解决代码。 尝试 1、…