偶然在GitHub论坛看到一个“基于Vue2.0高仿微信App”,点入一看,效果果真非常逼真。于是,立马入手学习。也记录对一些地方的理解:
1.过滤器
该项目中用到了时间过滤器,能将时间戳以固定的格式打印输出。过滤器官方文档在此,正如文档中所给出定义过滤器的两种方法,分为全局和局部,用在两个地方双括号插值和v-bind表达式,且过滤器应该添置在js表达式的尾部,由“管道”即“|”指示。该项目中时间过滤器的定义是全局形式,且单独放置在名为filters的文件夹下,由main.js中引入,进而全局注册使用。
filters/index.js 详细代码:

const filters = {
    /**
     * 功能:将时间戳按照给定的 时间/日期 格式进行处理
     * @param {Number} date 时间戳 
     * @param {String} fmtExp 时间格式 'hh:ss'
     * @returns {String} 规范后的 时间/日期 字符串
     */
    fmtDate: function(date, fmtExp) {
        var date = new Date(date)
        var o = {
            "M+": date.getMonth() + 1, //月份
            "d+": date.getDate(), //日
            "h+": date.getHours(), //小时
            "m+": date.getMinutes(), //分
            "s+": date.getSeconds(), //秒
            "q+": Math.floor((date.getMonth() + 3) / 3), //季度
            "S": date.getMilliseconds() //毫秒
        };
      //此处的匹配项是如果日期中需要输出年,对年份的处理!
        if (/(y+)/.test(fmtExp))
            fmtExp = fmtExp.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
        //后面就是截取时间的处理,将h+和s+即小时和分钟数截取出来
        for (var k in o)
            if (new RegExp("(" + k + ")").test(fmtExp))
                fmtExp = fmtExp.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmtExp;
    }
}
export default (Vue) => {
    Object.keys(filters).forEach(key => {
        Vue.filter(key, filters[key])
    })
}

最后的导出部分改写为常规的函数写法:

export default function(vue){
   //Object.keys()方法返回对象的可枚举属性和方法,因为可能会有多个过滤器
   //对每一个过滤器都进行全局定义,参见文档中全局的方法
    return Object.keys(filters).forEach(
        function(key){
            return vue.filter(key,filters[key])
        }
        );
}

再写详细一点,以免日后自己回过头半天不能领悟。上面最后的意思,首先Object.keys( )方法将当前的filters对象中的或者属性以key-value的数组形式返回,这里即是fmtDate( )返回。接着对数组中的每一项,以key为关键,返回过滤器的定义,即vue.filter(key,function),这里就是返回

vue.filter(fmtDate,function(date, fmtExp) {
  var date = new Date(date)
        var o = {
            "M+": date.getMonth() + 1, //月份
            "d+": date.getDate(), //日
            "h+": date.getHours(), //小时
            "m+": date.getMinutes(), //分
            "s+": date.getSeconds(), //秒
            "q+": Math.floor((date.getMonth() + 3) / 3), //季度
            "S": date.getMilliseconds() //毫秒
        };
  ……
})

然后在main.js中引入注册。(PS:不过这里的写法有点点不清楚。)

import filters from './filters' //将全部过滤器放在 filters/index.js 中便于管理
    // 注册全局过滤器
filters(Vue)

2.正则表达式
也是上面的时间格式化中对时和分的提取,最初看时一头雾水,结果!理解了意思之后,发现自己好笨。自己的难点是上面代码中的replace( )参数是RegExp. 1 1 , 参 数 是 哪 里 来 的 ( 笑 哭 ) 网 上 解 释 说 “ 1是匹配到的正则表达式的第一个字表达式”,而且一直在纠结第一个if里的内容,打印也不输出。事实证明调试是非常有用,于是单步调试。
这里写图片描述
正如上图所示if里的y+,最初因为项目中没有输出年份,格式化为hh:ss,因此if始终不执行。在理解了下面对小时分钟的处理后,再格式化前面加上年 yyyy:hh:ss,果然如所料,这时候就匹配到了fmtExp的字符串yyyy,也就能对时间戳的年给取出来。
这里写图片描述
同理,上面的第一次h+,匹配到了hh,于是对时间戳进行处理。分两位还是一位,如1或者11,如果只有一位时,返回值就直接是1(h);如果两位,首先处理为“0011”,接着对“0011”取“11”的字符串长度即2个,也就是11(h)。
第二次s+,返回就为分钟数。
通过上面类似的处理就能将时间戳格式化为我们想要的格式。

3.自定义指令
对消息的滑动处理进行了自定义指令实现,自定义指令文档在此,同样也分为全局和局部两种。在定义指令时一般有好些钩子函数:bind、inserted、update等,且会该钩子函数会被传入以下参数,el、binding、name……详细参见文档。
???????疑问:关于文档中后面的例子,参数‘vnode keys’不知道是如何获取的。

本项目里的自定义指令,代码如下:

        directives: {
            swiper: {
                bind: function(element, binding) {
                    var isTouchMove, startTx, startTy
                 //addEventListener(event,function,useCapture)用于向指定元素添加事件句,
                 // 相应的有removeEventLisener移除所添加的 
                 // 第三个参数指定事件在捕获或者冒泡阶段的执行(前者为true,后者false)
                    element.addEventListener('touchstart', function(e) {
                        var touches = e.touches[0]
                        startTx = touches.clientX
                        startTy = touches.clientY
                        isTouchMove = false;
                    }, false);
                    element.addEventListener('touchmove', function(e) {
                        var touches = e.changedTouches[0],
                            endTx = touches.clientX,
                            endTy = touches.clientY,
                            distanceX = startTx - endTx,
                            distanceY = startTy - endTy;
                        if (distanceX < 0) { //右滑
                            if (Math.abs(distanceX) >= Math.abs(distanceY)) {
                                if (Math.abs(distanceX) > 20) {
                                    element.style.transition = "0.3s"
                                    element.style.marginLeft = 0 + "px"
                                }
                            }
                        } else { //左滑
                            if (Math.abs(distanceX) >= Math.abs(distanceY)) {
                                if (distanceX < 156 && distanceX > 20) {
                                    e.preventDefault()
                                    element.style.transition = "0s"
                                    element.style.marginLeft = -distanceX + "px"
                                    isTouchMove = true
                                }
                            }
                        }
                        // e.preventDefault()
                    }, false);
                    element.addEventListener('touchend', function(e) {
                        if (!isTouchMove) {
                            return;
                        }
                        var touches = e.changedTouches[0],
                            endTx = touches.clientX,
                            endTy = touches.clientY,
                            distanceX = startTx - endTx,
                            distanceY = startTy - endTy,
                            isSwipe = false
                        if (Math.abs(distanceX) >= Math.abs(distanceY)) {
                            if (distanceX < 0) {
                                return;
                            }
                            if (Math.abs(distanceX) < 60) {
                                isSwipe = true
                                element.style.transition = "0.3s"
                                element.style.marginLeft = 0 + "px"
                            } else {
                                element.style.transition = "0.3s"
                                element.style.marginLeft = "-156px"
                            }
                        }
                    }, false);
                }
            }
        }

在上面的自定义指令的实现中,对绑定元素的dom操作用到了addEventListener( )方法,相应的还有移除该方法添加的事件句柄的方法removeEventListener(event,function(){},useCapture )。其中三个参数中,第一个是时间名称,第三个则是布尔值用于指定时间在捕获或者冒泡阶段执行。

<!DOCTYPE html>
<html>
<head>
  <title>测试事件冒泡捕获阶段</title>
</head>
<body>
   <div class="a">
      <li class="b">
        <a href="#" class="c">buhuo</a>
      </li>
   </div>
</body>
<script type="text/javascript">
   var div = document.getElementsByClassName('a')[0];
   var li = document.getElementsByClassName('b')[0];
   var a = document.getElementsByClassName('c')[0];

//对事件冒泡过程添加函数处理,默认是false
   div.addEventListener('click',function(event){
      console.log('div');
   });
   li.addEventListener('click',function(event){
      console.log('li');
   });
   a.addEventListener('click',function(event){
      console.log('a');
   })


//对事件捕获过程处理,第三个参数设为true
    div.addEventListener('click',function(event){
      console.log('div');
   },true);
   li.addEventListener('click',function(event){
      console.log('li');
   },true);
   a.addEventListener('click',function(event){
      console.log('a');
   },true)

</script>
</html>

下面则是验证的效果:
这里写图片描述

再使用event.stopPropagation()方法——终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。在捕获事件方法中,div标签的监听事件中调用该方法,明显的只有div输出。
这里为什么div还会输出呢?因为所谓的捕获与冒泡并不是相对本元素而是腹肌或者子级的元素,因此冒泡也是不会冒泡到兄弟元素的!
这里写图片描述
将stopPropagation()方法下移到li标签:
这里写图片描述
将stopPropagation()方法下移到a标签时,输出了两个a是因为一个是捕获阶段捕获的,下面的那个则是冒泡阶段,但又由于调用了stopProgagation方法,冒泡事件被终止于a标签。
这里写图片描述
关于事件的捕获阶段和冒泡阶段的划分详见下面的图,参考自网上,不过出处一时找不到了,暂且自己画一下。
这里写图片描述

!!!关于这部分内容,先记着一条有用的总结,若想要捕获事件就要在目标元素事件发生之前,也就是捕获阶段。
不过对其中的很多地方尚且不清楚(后补!)。

4.router-link中的一系列标签to、tag、exact
(1)tag标签:

//原未改时
<router-link to="/foo" >foo</router-link>
  <!-- 渲染结果 -->
  <a href="/foo">foo</a>

 //修改后
 <router-link to="/foo" tag="li">foo</router-link>
  <!-- 渲染结果 -->
  <li>foo</li>

(2)exact标签
原文档中解释:路由的激活在exact匹配模式下,会严格按照给出的路径,只有完全匹配时才激活该路径!官方效果实例
“/” :第一个“/”无论是点击后面哪个li标签均会被激活,因为无论哪个路径都包含”/”;
而第二个”/”只会在完全匹配,也就是仅仅在点击前两个li标签时会被激活;
同理,后面的user系列也一样,第一个/users的li标签只要在后面的users系列被点击即被激活。而包含了exact的仅仅前两个/users被激活;
类似的其他的渲染类似!

Logo

前往低代码交流专区

更多推荐