首页 > 技术文章 > 微信小程序的双线程模型,生命周期和事件(捕获和冒泡)

jiangxianseng 2020-03-10 23:16 原文

双线程模型

 

 

 

 

1 在渲染层将wxml文件与wxss文件转成js对象,也就是虚拟的dom

2 逻辑层生成数据,把数据与虚拟的dom相结合,得到真实的dmo,然后在交给渲染层渲染

3 当有数据变化的时候,逻辑层负责更新数据,js对象发生改变,这种改变方式采用的是diff算法进行比较,只改变变化的部分
4 将更新的数据,进行反馈,再次得到虚拟的dmo中,从而
更新页面。
diff算法:
Diff算法的作用是用来计算出 **Virtual DOM** 中被改变的部分,然后针对该部分进行原生DOM操作,而不用重新渲染整个页面。

小程序的启动流程:

注意:app.json文件中只会产生一个全局的app对象,先执行app中的生命周期函数,然后加载自定义组件,然后开始加载页面的json配置文件,产生对象,通过双线程模型来渲染页面,并且开始执行页面的生命周期

 

 

 

小程序中app.js中的生命周期

//app.js,小程序全局只有一个App对象
// 下文中所说的后台是指:用户将小程序切换到手机的后台运行。这种操作我们叫做从前台切换到后台。和我们平时开发中的后台没有关系
//下文中所说的前台是指:用户将小程序重手机后台运行,调用到用户手机的显示界面,我们叫做从后台切换到前台。
App({

 /*
  当小程序初始话完成,会触发onlaunch(全局只触发一次),onlaunch在手机后台到前台切换是不会执行的。如果
  要要他再次执行,除非在手机中关闭(关闭不是切换到后台,而是直接删除小程序的后台运行)小程序,然后重新打开,才能执行,
*/
 onLaunch: function () {

   console.log("小程序的初始话:onlaunch")
},
 /**
  * 当小程序启动,或者是从后台进入到前台的时候,会执行onshow,
  * 那我们可以通过这个option中的scene值来判断不同进入场景
  {path: "pages/test/test", query: {…}, scene: 1001, shareTicket: undefined, referrerInfo: {…}}
path: "pages/test/test"
query: {}
referrerInfo: {}
scene: 1001
shareTicket: undefined
__proto__: Object
  */
 onShow:function(option){
   console.log("小程序onshow,:onShow",option)
},
 /*小程序重前台进入到后台的时候,会触发:onHide*/
 onHide:function(){
   console.log("小程序重前台进入到后台的时候,会触发:onHide")

},
 /**可以在全局使用 */
 globalData: {
   userInfo: null
}
})

执行App.js生命周期的时候以及在整个App对象我们可以做什么?

1 在注册app时候,我们可以通过onshow来判断用户进入小程序的场景
2 我们可以在生命周期函数中,做一些数据请求。
3 我们可以在app中设置一个全局的对象,让所有的页面都可以使用,比如上面的globalData

小程序的页面的生命周期

// pages/test/test.js
Page({

 /**
  * 页面的初始数据
  */
 data: {
   msg:'own is sb',
   name:"tank",
   num :12,
   num1:1,
   flag:false,
   name_list:[{"name":"jason",love:"piao"},{name:"tank",love:"own"},{name:"egon",love:"洗脚"}],
   tank:{name:"tank",love:"own"},
   color:"blue"
},

 /**
  * 生命周期函数--监听页面加载,页面如果不关闭,这个onload只执行一次
  */
 onLoad: function (options) {
     console.log("onLoad")
},

  /**
  * 生命周期函数--监听页面显示,页面从底下切到上面的时候也会执行
  */
 onShow: function () {
   console.log("onShow")
},




 /**
  * 生命周期函数--监听页面隐藏
  */
 onHide: function () {
   console.log("onHide")
},

 /**
  * 生命周期函数--监听页面卸载
  */
 onUnload: function () {
   console.log("onHide")
},

  /**
  * 生命周期函数--监听页面初次渲染完成
  */
 onReady: function () {

},

 /**
  * 页面相关事件处理函数--监听用户下拉动作
  */
 onPullDownRefresh: function () {
     console.log("onPullDownRefresh")
},

 /**
  * 页面上拉触底事件的处理函数
  */
 onReachBottom: function () {
   console.log("onReachBottom")
},

})// pages/test/test.js
Page({

 /**
  * 页面的初始数据
  */
 data: {
   msg:'own is sb',
},

 /**
  * 生命周期函数--监听页面加载,页面如果不关闭,这个onload只执行一次
  */
 onLoad: function (options) {
     console.log("onLoad")
},

  /**
  * 生命周期函数--监听页面显示,页面从底下切到上面的时候也会执行
  */
 onShow: function () {
   console.log("onShow")
},
 /**
  * 生命周期函数--监听页面隐藏
  */
 onHide: function () {
   console.log("onHide")
},

 /**
  * 生命周期函数--监听页面卸载
  */
 onUnload: function () {
   console.log("onHide")
},

  /**
  * 生命周期函数--监听页面初次渲染完成,这个真实dom要渲染的时候,就会执行(onReady是唯一wxml触发的钩子函数)
  */
 onReady: function () {

},

 /**
  * 页面相关事件处理函数--监听用户下拉动作,如果你要监听这个动作,然后触发底下的函数的话,你必须"enablePullDownRefresh" :true配置成可以下拉刷新。
  */
 onPullDownRefresh: function () {
     console.log("onPullDownRefresh")
},

 /**
  * 页面上拉触底事件的处理函数,如果要成功触发这个事件,必须页面显示不够一页显示(一般要求距离底部50px)
  */
 onReachBottom: function () {
   console.log("onReachBottom")
},

})

在页面的page对象中可以做哪些事情

1 在生命周期函数中,向服务器请求数据
2 在data中初始话数据,给wxml使用
3 监听wxml事件,绑定对应的事件
4 监听页面的上拉,下拉等

 

 

小程序的事件

事件绑定

wxml文件

<!--pages/test1/test1.wxml-->

<view bindtap="click1">我是事件</view>
<button bind:tap="click1" data-name="{{name}}" data-age="18" id="bt">我是按钮</button>

<view id="outter" bindtap="click3" data-a="o">外面

 <view id="innder" bindtap="click2" data-a="i">
  里面
 </view>
</view>

js文件

Page({

 /**
  * 页面的初始数据
  */
 data: {
   name:"owen"
},
 //e为事件对象,事件所有产生的数据都在e中
 click1:function(e){
     
   console.log("你点我了",e)
     
},
)}
   
   
#点击后e中得到的数据
{type: "tap", timeStamp: 1572, target: {…}, currentTarget: {…}, mark: {…}, …}
changedTouches: [{…}]

currentTarget:

dataset: {age: "18", name: "owen"}
id: "bt"
offsetLeft: 68
offsetTop: 21
__proto__: Object
detail: {x: 192, y: 50}
mark: {}
mut: false

target:

dataset: {age: "18", name: "owen"}
id: "bt"
offsetLeft: 68
offsetTop: 21
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
nv_constructor: "Object"
nv_toString: ƒ ()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
timeStamp: 1572
touches: [{…}]
type: "tap"
__proto__: Object

总结:

1 响应函数直接写在page对象中就可以了,不需要和vue一样写在methods里面
2 <view bind:事件名称 = "响应函数的函数名" data-参数名 = "值">
3 获取2中传过来的值,在事件对象中。例如:e.currentTarget.dataset中

事件补充

js文件

// pages/test1/test1.js
Page({

 /**
  * 页面的初始数据
  */
 data: {
   name:"owen"
   

},
 click1:function(e){
   console.log("你点我了",e)
},
 click2:function(e){
   console.log("里面",e)
},
 click3:function(e){
   console.log("外面",e)
},

 click4:function(e){
   console.log("捕获外")
},
 click5:function(e){
   console.log("捕获中")
},
 click6:function(e){
   console.log("捕获里")
}
,
 click7:function(e){
   console.log("冒泡外")
},
 click8:function(e){
   console.log("冒泡中")
},
 click9:function(e){
   console.log("冒泡里")
}

})

wxml文件


<!-- capture-bind:tap 事件的捕获,从外面到里面-->
<!-- bind:tap就是事件的冒泡,重里面到外面传递 -->
<view class="outter" capture-bind:tap="click4" bind:tap="click7" data-a="o">外面

 <view class="midd" capture-bind:tap="click5" bind:tap="click8" data-a="i">
   <view class="innder"  capture-bind:tap="click6" bind:tap="click9">
    里面
   </view>

  中间
 </view>

</view>

<!-- 如何阻止事件捕获 将 capture-bind:tap改成 capture-catch:tap-->
<view class="outter" capture-bind:tap="click4" bind:tap="click7" data-a="o">外面

 <view class="midd" capture-catch:tap="click5" bind:tap="click8" data-a="i">
   <view class="innder"  capture-bind:tap="click6" bind:tap="click9">
    里面
   </view>

  中间
 </view>

</view>


<!-- 如何阻止事件冒泡 将bind:tap 改成 catch:tap-->
<view class="outter" capture-bind:tap="click4" bind:tap="click7" data-a="o">外面

 <view class="midd" capture-bind:tap="click5" bind:tap="click8" data-a="i">
   <view class="innder"  capture-bind:tap="click6" catch:tap="click9">
    里面
   </view>

  中间
 </view>

</view>

wxss文件

/* pages/test1/test1.wxss */
#outter{
 width: 400rpx;
 height: 400rpx;
 background-color: red;
}
#innder{
 width: 200rpx;
 height: 200rpx;
 background-color: yellow;
}


.outter{
 width: 600rpx;
 height: 600rpx;
 background-color: red;
}
.midd{
 width: 400rpx;
 height: 400rpx;
 background-color: blue;
}
.innder{
 width: 200rpx;
 height: 200rpx;
 background-color: yellow;
}

常见的事件有:

类型触发条件最低版本
touchstart 手指触摸动作开始  
touchmove 手指触摸后移动  
touchcancel 手指触摸动作被打断,如来电提醒,弹窗  
touchend 手指触摸动作结束  
tap 手指触摸后马上离开  
longpress 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 1.5.0
longtap 手指触摸后,超过350ms再离开(推荐使用longpress事件代替)  
transitionend 会在 WXSS transition 或 wx.createAnimation 动画结束后触发  
animationstart 会在一个 WXSS animation 动画开始时触发  
animationiteration 会在一个 WXSS animation 一次迭代结束时触发  
animationend 会在一个 WXSS animation 动画完成时触发  
touchforcechange 在支持 3D Touch 的 iPhone 设备,重按时会触发  

有两个注意点

    Touchcancle: 在某些特定场景下才会触发(比如来电打断等) 

tap事件和longpress事件通常只会触发其中一个

currentTarget和target的区别:当点击内层的时候,内层和外层都会触发,并且外层触发的target是内层的信息而不是外层的,因此一般获取数据通过.current的方式,更加准确

 

 

事件传递参数

当视图层发生事件时,某些情况需要事件携带一些参数到执行的函数中, 这个时候就可以通过

data-属性来完成:

1 格式:data-属性的名称

2 获取:e.currentTarget.dataset.属性的名称

img

touches和changedTouches的区别

img

 

推荐阅读