Vue.js有赞商城(思路以及总结整理)
以下是本次项目的代码链接和预览链接:
代码链接:https://github.com/Leonardo-zyh/Vue-youzanStore
预览链接:https://leonardo-zyh.github.io/Vue-youzanStore/dist/
首先这次重构有赞商城使用的是一个多页面应用的重构思路,因此在进行重构之前要对项目文件进行一些配置和调整,具体的操作的话可以点击以下这个链接进行查看:基于vue-cli搭建一个多页面应用
在完成了多页面应用的基础结构的搭建之后,会出现项目根目录下有一个src文件夹,src文件里有components、modules、pages三个文件夹的情况,而components文件夹是用来放置一些共用的vue组件的,而modules文件夹里是放置一些共用的css、js模块,至于最后的pages文件夹则是用来放置有赞商城的不同页面的文件,每个页面都会在pages内呈一个单独的文件夹,里面会放置关于这个页面的独有的所有文件。
在这里先说明一下,重构过程中所有获取到的数据,都是通过使用在easymock上编写对应的接口(原在数据在rap2上,但是接口数据不稳定且无法搭建在github上),然后通过axios发送异步请求来获取到的模拟的数据,这是模仿真实的开发环境下的操作,具体的实现过程的话可以参考easymock以及我在github上面的源码文件。
1.首页
2.目录分类页
3.商品搜索列表页
4.商品详情页
5.购物车页面
6.个人中心地址管理页面
接下来我们会逐个页面来说说他们的重构思路
axios
swipermint-uiVolecityqs库
一、首页
1、无缝轮播组件
那我们首先来说一下轮播组件,首先我们需要在src目录下的compnents文件夹里新建一个轮播组件文件,轮播的话我们会直接选择使用swiper插件提供的轮播组件库,我们只需把它封装到一个组件文件中即可,具体的操作在这里我就不详细说明了,这里只强调两个需要注意的问题:
1.应不应该在轮播组件放入图片数据呢?
回答:不应该,原因是为了使得轮播组件独立出来,在不同的组件中得以复用,并且使其可以适应不同规格不同数量的图片,因此我们的轮播组件只负责展示数据,不负责拿数据,数据应该通过props从父组件中获取。
<Swipe :lists="bannerLists" name="swpie.vue" v-if="bannerLists"></Swipe>
new Swiper('.swiper-container',{
loop:true,
pagination: '.swiper-pagination',
autoplay: 2000
})
getBanner(){//获取轮播数据
this.$http.get(url.banner).then(res=>{
this.bannerLists = res.data.lists })
2.关于swiper的配置应将其写在轮播组件的生命周期的哪一部分呢?
回答:首先我们需要了解的是swiper是对DOM节点进行操作的,所以swiper的配置应该写在组件的mounted生命周期钩子里,因为在这个阶段已经在页面上生成了该组件对应的DOM节点;另一方面,swiper组件里的数据是swiper的父组件异步获取后传递给swiper的,因此应该等swiper拿到了传递的数据之后再对这个组件进行渲染,因此需要给这个组件添加一个v-if="bannerLists"
的判断,判断swiper组件是否获取到数据,只有获取到了数据才生成这个DOM节点。
2、“最热商品推荐”的商品列表
关于这个“最热商品推荐”的商品列表的重构也非常简单,只需通过axios发送你想获取的商品列表的页数和每页的展示商品的个数的请求到对应的接口中,就可以获取到对应的商品列表的数据,然后再通过v-for
把每个商品的图片、名称和价格渲染到页面中即可。
同样的,这里有两个值得注意的问题:
1.获取到的价格的格式并不统一,如何来使得这些价格的格式统一起来?
回答:这里需要用到vue实例里的一个自带属性filters
来对数据进行过滤,在vue1.0的时候,filters里面会有自带的过滤器,不过在vue2.0时被移除了,因此需要我们来自己写所需的过滤器的过滤方式:
filters:{
currency(num){
num=num+''
let arr=num.split('.')
if (arr.length===1){
return num+'.00'
} else {
if (arr[1].length===1){
return num+'0'
} else return num
}
}
}
只有在渲染页面时,只要对你想进行的数据后加上该过滤器即可:
<div class="price">¥{{list.price | currency}}</div>
2.如何做到下来商品列表就发送对应的请求来更新一页新的商品列表?
回答:这里我们使用到了mint-ui,一个移动端分页效果库,然后我们使用它文档上面对应的infinite scroll的api来达到我们想要的效果,具体代码如下:
<ul class="js-list js-lazy" data-src=""
v-infinite-scroll="getList"
infinite-scroll-disabled="loading"
infinite-scroll-distance="50"
>
<li v-for="list in lists" :key="list.id">
<div class="goods-item">
<a :href="'/goods.html?id='+list.id">
<div class="thumb img-box">
<img class="fadeIn" v-bind:src="list.img">
</div>
<div class="detail">
<div class="title">{{list.name}}</div>
<div class="price">¥{{list.price | currency}}</div>
</div>
</a>
</div>
</li>
</ul>
上述代码中,v-infinite-scroll="getList"
表示每当下拉到一定距离时就触发methods里面的getList方法;getList方法的具体代码如下所示:
getList(){
if (this.allLoad) return
this.loading=true
axios.post(url.hostLists,{
pageNum:this.pageNum,
pageSize:this.pageSize
}).then((response)=>{
let currentList=response.data.lists
if (currentList.length<this.pageSize) this.allLoad=true
if (this.lists) {
this.lists=this.lists.concat(currentList)
} else {
this.lists=currentList
}
this.pageNum +=1
this.loading=false
})
}
infinite-scroll-disabled="loading"
表示效果触发的条件,若loading为false则表示可以触发,若loading为true则表示不能触发,因此当loading为true时我们可以给底部添加一个加载效果,当数据获取完毕,loading变为false时,我们可以通过v-show="loading"
来让加载效果消失;infinite-scroll-distance="50"
表示下拉的触发距离,设置的数值越大,表示滚动条离底部的触发距离越大,越容易触发。
3.底部导航栏组件
底部导航栏和轮播组件一样,由于可以在其他地方进行复用,因此会把该组件放于components文件夹中,这里值得一提的是,底部导航栏组件由于点击不同的图标,它会跳转到不同的页面,因此会导致导航栏状态的重新加载,因此,若想要在不同的页面让导航栏呈现不同的状态,我们需要在跳转的时候传入对应的查询参数,然后在跳转到不同的页面时读取这个参数来呈现对应的不同的状态,具体的代码片段如下:
let {index}=qs.parse(window.location.search.substring(1))
export default {
data(){
return {
navConfig,
curIndex:parseInt(index,10) || 0
}
},
methods:{
changeNav(index,list){
location.href=`${list.href}?index=${index}`
}
}
}
值得一提的是,在这里我们使用到了一个qs库,这个库可以方便我们提取出当前url后面的查询参数。
最后,由于在其他页面中,filters属性和底部导航栏组件都可以进行复用,所以这里我们利用mixins属性,来对filters属性和底部导航栏组件的注入进行打包,打包在一个js文件夹下的mixin.js文件中:
import Footnav from 'components/FootNav.vue'
let mixin={
filters:{
currency(num){
num=num+''
let arr=num.split('.')
if (arr.length===1){
return num+'.00'
} else {
if (arr[1].length===1){
return num+'0'
} else return num
}
}
},
components:{
Footnav
}
}
export default mixin
当你的页面需要使用到该过滤器,或者底部导航栏时,只要对这个模块进行引入,并在mixins属性中添加它即可:
new Vue({
...
mixins:[mixin]
})
二、目录分类页
目录分类页并无新的操作,和首页的部分操作类似,就是利用axios从接口中获取数据并渲染到页面中,并对页面中的一些焦点状态进行v-show的处理,以及一些类名和焦点的处理,我们可以从目录分类页中通过点击热销商品进入商品详情页,通过点击热门品牌进入商品搜索列表页,在进行这些页面的跳转时,把一些关键的数据传入查询参数中以便跳转页面获取即可。
let { index } = qs.parse(location.search.substr(1)); changeNav(list, index) {
//this.curIndex = index;
location.href = `${list.href}?index=${index}`;
//页面跳转
event.preventDefault();
}
三、商品搜索列表页
//引入Velocity
import Velocity from 'velocity-animate/velocity.js' //在methods中加入对应方法
methods:{
scrollMove(){
if (window.scrollY>=290){
this.isShow=true
} else {
this.isShow=false
}
},
goToTop(){
Velocity(document.body, 'scroll', { duration: 500, easing: "easeOutQuart" });
this.isShow=false //回到顶部图标消失
}
}
四、商品详情页
在商品详情页中,除了有对数据的获取和页面的渲染外,这里主要涉及到了三个新的操作:
- sku算法的应用
- 页面的载入和消失的动画效果
- 页面展示时的穿透滚动问题的解决
首先是sku算法,由于此次商品详情页的选择并不需要使用到它,因为商品的可选属性只有一个,但是在实际情况下,由于很多商品的可选属性不止一个,因此是需要使用到sku算法的。 SKU=Stock Keeping Unit(库存量单位),同一型号的产品,或者说是同一个产品项目(产品条形码是针对企业的产品)。有兴趣可以自行搜索:“淘宝sku算法解析”或看这篇博客
然后如何制作sku页面载入和消失时的动画效果呢?这里我们使用到了vue提供的自带transition的封装组件,可以通过这个组件来给任何元素和组件添加进入或者离开时的过渡。这个组件提供了八个JavaScript钩子函数以及六个过渡类名的切换,利用这些钩子函数以及类名的切换就可以完成组件的过渡动画了,这里列举一个vue文档上的典型例子给大家参考一下吧:
transition过渡:
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
position:fixed;width:100%;
这样内容层就不会再滚动了,之后我们再通过设置:scrollTop = document.scrollingElement.scrollTop
document.body.style.top = -scrollTop + 'px'
height:100%;overflow:hidden;
,在关闭遮罩层和弹出层后,还原这些修改样式,即可使得滚动穿透的问题得以解决。需要注意的是,还原这些样式之后,原本内容层滚动的高度就会丢失,因此我们要通过之前记录下来内容层滚动的高度,在还原样式时将滚动高度也一并还原。document.scrollingElement.scrollTop = scrollTop
这样滚动穿透的问题就算是彻底解决了,下面是全部的这部分的全部代码片段:
chooseSku(type) {//显示购买菜单
this.skuType = type
this.showModal = true
},
changeSku(num) {//增减数量
if (num < 0 && this.skuNum === 1) return
this.skuNum += num
}, addCart() {//加入购物车
$.ajax($.url.cartAdd, {
id,
skuNum: this.skuNum
}).then((data) => {
if (data.status === 200) {
this.showModal = false
this.showAddMsg = true //添加成功的信息
this.isAddedCart = true //显示购物车图标
setTimeout(() => this.showAddMsg = false, 1200)
}
})
}
},
watch:{
showSku(val,oldVal){
if (val){
scrollTop = document.scrollingElement.scrollTop
document.body.style.top = -scrollTop + 'px'
}
document.body.style.position=val?'fixed':'static'
// document.body.style.margin=val?`0 0 ${window.scrollY}px 0`:'0px'
document.querySelector('html').style.overflow=val?'hidden':'auto'
document.body.style.width=val?'100%':'auto'
document.querySelector('html').style.height=val?'100%':'auto'
if (!val){
document.scrollingElement.scrollTop = scrollTop
}
}
}
五、购物车页面
商品的获取渲染以及增加是否被选中属性
获取后台数据加载处理或动态响应式处理
商品选中店铺选中全选,影响价格三级联动。
编辑状态,其余不可切换。对数量操作,加减更改。删除,单商品删除,选中(多个)删除,商品删除店铺删除。
原生事件,滑动删除页面,Volecity。
删除多个商品进行过滤处理
fetch层封装,
同一个场景下思维层封装
问题呈现,左滑删除样式继承。[0].style.left='0px' this.$refs[`goods-${shopIndex}-${goodIndex}`][0].style.left='0px'
- ref 是非响应式的,不建议在模板中进行数据绑定,即使用唯一标识绑定
- v-for 模式使用“就地复用”策略,简单理解就是会复用原有的dom结构,尽量减少dom重排来提高性能 ( 解决方案:还原dom样式 )
- key 为每个节点提供身份标识,数据改变时会重排,最好绑定唯一标识,如果用index标识可能得不到想要的效果(绑定唯一识别key)
- 网页性能管理详解
首先获取数据,渲染到页面这些是基本的操作
获取到数据之后,由于有一些属性数据中没有,并且我们想要它在页面中是呈响应式存在的,因此从接口获取到数据之后不应该直接赋值给data里,而是应该先给数据增添属性,再把增添后的数据赋值到data处,具体代码如下:
getLists(){
cart.getCartLists().then((response)=>{
let list=response.data.cartList
list.forEach(shop=>{
shop.checked=true
shop.editingStatus=false
shop.editingMsg='编辑'
shop.removeChecked=false
shop.goodsList.forEach(good=>{
good.checked=true
good.removeChecked=false
good.touchDelete=false
})
})
this.cartLists=list
})
}
全选与否则利用计算属性来处理,利用计算属性的getter来获取此时购物车的状态来判断是否被全选,利用计算属性的setter来处理点击全选时商店及商店下商品的状态的改变。
同样的,利用计算属性来计算正常状态下选中商品的总价,并返回选中商品的列表。同理,利用计算属性来计算编辑状态下的选中的商品的列表,并以数组的形式返回。
在编辑状态下,我利用了计算属性来对商品的数量的数据进行了监测,若判断出数量中存在非数字或者负数的情况,则会自动把数量的数据变成1。
利用
touchstart
和touchend
两个事件来实现商品左拉删除的功能,这两个事件分别绑定start
和end
的方法,方法的具体代码如下所示:start(e,good){
good.startX=e.changedTouches[0].clientX
},
end(e,good,goodIndex,shopIndex,shop){
let endX=e.changedTouches[0].clientX
let left='0px'
if (good.startX-endX>100){
good.touchDelete=true
left='-60px'
Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left})
shop.goodsList.forEach((otherGood,index)=>{
if (otherGood.touchDelete && index!==goodIndex) {
otherGood.touchDelete=false
Velocity(this.$refs[`goods-${shopIndex}-${index}`],{left:'0px'})
}
})
} else if (endX-good.startX>100) {
good.touchDelete=false
left='0px'
Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left})
}
}
当添加了左拉删除的功能之后,页面会出现一个BUG,就是左拉之后,点击该商品对应的商店下的编辑按钮,删除的按钮会继续被左拉,呈现一个比其他删除按钮长的BUG状态。
shop.editingStatus=!shop.editingStatus
if (shop.editingStatus){
shop.goodsList.forEach((good,index)=>{
if (good.touchDelete){
good.touchDelete=false
this.$refs[`goods-${shopIndex}-${index}`][0].style.left='0px'
}
})
}
六、个人中心地址管理页面
最后是个人中心地址管理页面,在这个页面中,我们会封装addressService层和fetch层,addressService层主要是负责页面中前后端交互的方法,如添加地址、删除地址、编辑地址和获取地址等,然后fetch层主要是负责从RAP接口中获取数据并返回一个promise对象到service层中,具体的封装方式和使用方式请自行查看源码。
另外在这个页面中,我们使用到了vue-router和vuex,接下来我将会简要介绍它们在个人中心地址管理页面中的使用方式。
首先是vue-router,他是用于构建单页面应用的,是基于路由和组件,路由用于访问特定的路径,然后特定的路径与特定的组件相联系相映射,传统页面中,是通过超链接来实现页面的跳转和切换的,但在vue-router中,则是路由的切换,即组件的切换。
我们先来看看是如何配置一个routes、创建一个router实例并把它注入到vue实例中去的:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router) //构造配置
let routes=[{
path:'/',
components: require('../components/member.vue')
},{
path:'/address',
components:require('../components/address.vue'),
children:[{
path:'',
redirect: 'all'
},{
path:'all',
components:require('../components/all.vue')
},{
path:'form',
name:'form',
components:require('../components/form.vue')
}]
}] //创建router实例
let router=new Router({
routes
}) export default router import Vue from 'vue'
import router from './router'
import store from './vuex' //根组件注入
let view=new Vue({
el:'#app',
router,
store
})
//router-view标签作为配置路由后组件的容器
<div id="app">
<router-view></router-view>
</div>
通过这样路由的配置和注入,我们就可以实现单页面下多组件的切换和嵌套了,如果上述有不懂的地方,请到vue-router的官网处查看文档和说明。
接着我们来讲一下vuex,vuex是对SPA即单页面应用进行数据的状态管理,如果想了解具体vuex是什么还有它的用途,请点击这篇文章:Vuex新手入门指南
vuex其实也是组件间通信的一种方式,说起组件间的通信,我们不如来一一列举一下他们的方式有哪些:
1.引用类型数据
用法:如果父组件有一个数据类型是引用类型的数据,当这个数据直接传递给子组件以后,在子组件对这个数据源进行修改的时候,父组件中该数据也会同步修改。
2.自定义事件
即子组件内部定义了一个自定义事件,可以用父组件在子组件上进行监听:
//子组件
this.$emit('change',18)
//父组件
<foo :obj="obj" @change="changeAge"></foo>
//父组件
methods:{
changeAge(age){
this.obj.age=age
}
}
3.全局事件(global bus)
//bus.js
import Vue from 'vue'
const bus=new Vue()
export default bus
//触发组件
import bus from 'js/bus.js'
bus.$emit('change',18)
//订阅组件
import bus from 'js/bus.js'
bus.$on('change',(age)=>{
this.obj.age=age
})
4.vuex状态管理
vuex的使用与vue-router有一点相似,具体代码如下:
import Vue from 'vue' //使用vuex插件
import Vuex from 'vuex'
Vue.use(Vuex) import address from 'js/addressService.js' //创建Store实例
const store=new Vuex.Store({
state:{
lists:null
},
mutations: {
init(state,lists){
state.lists=lists
}
},
actions: {
getLists({commit}){
address.getList().then(response=>{
commit('init',response.data.lists)
})
}
}
}) export default store
之后同样的在跟组件对store实例进行注入即可,在上述实例中,state属性表示的是实例的状态,类似vue实例里的data,需要高度注意的是,不允许直接修改state里面的值,只允许定义一系列的类似事件的mutations来触发进行state的管理。而mutations属性里面存放的是同步事件,因此是对数据进行同步管理,要进行异步操作的话必须使用actions属性;actions属性里面存放一些异步的操作,在异步的操作进行完成之后再触发mutations里面的同步事件来对state里面的数据的状态进行同步的操作。
在组件中,我们一般通过dispatch来触发actions里面的异步事件进行异步操作,一般使用计算属性来获取state中的数据,之所以使用计算属性,是因为状态管理里的数据可能是变化的,因此我们希望它在页面中是响应式的,因此我们选择使用计算属性来对数据进行依赖的绑定。
具体代码如下:
computed:{
list(){
if(this.$store.state.lists){
return this.$store.state.lists
}
return false
}
},
created(){
if (!this.list){ //防止在新增地址或修改地址后多次触发mutations中的init
this.$store.dispatch('getList')
}
}
总之,vuex中状态管理的过程可总结为以下流程:
(1).通过dispatch(actionFnName)分发来触发actions中的异步操作=>
(2).待异步操作结束之后通过commit(mutationsFnName,data)来触发mutations中的同步事件来进行同步操作=>
(3).通过同步操作改变state中的数据的状态=>
(4).状态改变后,组件中的计算属性因为绑定了该数据作为依赖,因此数据的改变会响应式地展示在页面中,即页面展示的数据也会得到同步的改变
Vue.js有赞商城(思路以及总结整理)的更多相关文章
- Vue.js中,如何自己维护路由跳转记录?
在Vue的项目中,如果我们想要做返回.回退操作时,一般会调用router.go(n)这个api,但是实际操作中,使用这个api有风险,就是会让用户跳出当前应用,因为它记录的是浏览器的访问记录,而不是你 ...
- Vue.js 开发实践:实现精巧的无限加载与分页功能
本篇文章是一篇Vue.js的教程,目标在于用一种常见的业务场景--分页/无限加载,帮助读者更好的理解Vue.js中的一些设计思想.与许多Todo List类的入门教程相比,更全面的展示使用Vue.js ...
- 现有 Vue.js 项目快速实现多语言切换的一种思路
Web 项目多语言(i18n,即国际化)是比较常见的需求,常规的做法大概有以下几种: 每种语言单独开发页面,适用于 CMS 之类的网站 多语言文本和页面结构分离,运行时动态替换.适用于单页应用(SPA ...
- Vue.js动画在项目使用的两个示例
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 李萌,16年毕业,Web前端开发从业者,目前就职于腾讯,喜欢node.js.vue.js等技术,热爱新技术,热 ...
- Vue (一) --- vue.js的快速入门使用
=-----------------------------------把现在的工作做好,才能幻想将来的事情,专注于眼前的事情,对于尚未发生的事情而陷入无休止的忧虑之中,对事情毫无帮助,反而为自己凭添 ...
- Vue.js——60分钟组件快速入门(上篇)
组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HTML ...
- Vue.js——60分钟快速入门
Vue.js介绍 Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API,使得我们 ...
- Vue.js——vue-router 60分钟快速入门
概述 vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用.vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来.传统的 ...
- Vue.js——60分钟webpack项目模板快速入门
概述 browserify是一个 CommonJS风格的模块管理和打包工具,上一篇我们简单地介绍了Vue.js官方基于browserify构筑的一套开发模板.webpack提供了和browserify ...
随机推荐
- jupyter notebook 将当前目录设置为工作目录
生成配置文件首先打开你的CMD或者是终端(Linux),在你配置过环境变量的基础下,你直接输入以下命令: jupyter notebook --generate-config 然后打开生成的配置文件, ...
- moviepy音视频剪辑:视频基类VideoClip子类DataVideoClip、UpdatedVideoClip、ImageClip、ColorClip、TextClip类详解
☞ ░ 前往老猿Python博文目录 ░ 一.概述 在<moviepy音视频剪辑:moviepy中的剪辑相关类及关系>介绍了剪辑相关类及关系,其中VideoClip有多个直接子类和间接子类 ...
- PyQt(Python+Qt)学习随笔:QListWidget的currentRow属性
QListWidget的currentRow属性保存当前项的位置,为整型,从0开始计数,在某些选择模式下,当前项可能也是选中项. currentRow属性可以通过方法currentRow().setC ...
- PyQt(Python+Qt)学习随笔:视图中类QAbstractItemView的dragDropOverwriteMode属性不能覆盖写的问题
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在<PyQt(Python+Qt)学习随笔:视图中类QAbstractItemView的dra ...
- Python中错误之 TypeError: object() takes no parameters、TypeError: this constructor takes no arguments
TypeError: object() takes no parameters TypeError: this constructor takes no arguments 如下是学习python类时 ...
- sklearn决策树应用及可视化
from sklearn import datasets from sklearn.tree import DecisionTreeClassifier 1.载入iris数据集(from sklear ...
- JDBC(二)—— 获取连接池方式
## 获取数据库连接的方式 ### 方式一 ```javaDriver driver = new com.mysql.cj.jdbc.Driver(); String url = "jdbc ...
- NameSilo的DDNS动态域名解析
用Java写的,一个实时检测IP变化并更新DNS状态的工具,适用于在NameSilo购买的域名,如果你的域名是在其他商家购买的,修改为你自己的api就行.代码我放github了,地址: https:/ ...
- 浏览器小程序(Browser Applet)闪亮登场
2017 年 1 月 9 日,微信小程序横空出世.随后,支付宝小程序.今日头条小程序.百度智能小程序.360小程序等纷纷推出,自此国内软件功能扩展领域进入到了小程序时代,小程序为丰富其宿主软件的功能和 ...
- C#数据结构-线索化二叉树
为什么线索化二叉树? 对于二叉树的遍历,我们知道每个节点的前驱与后继,但是这是建立在遍历的基础上,否则我们只知道后续的左右子树.现在我们充分利用二叉树左右子树的空节点,分别指向当前节点的前驱.后继,便 ...