侃哥

侃哥 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

前端开发

个人动态

侃哥 提出了问题 · 7月20日

前端有什么代码扫描工具吗?

前端有代码扫描工具吗?
后台代码发布之前,经常会使用代码扫描工具扫描代码的质量,例如安全性、语法错误、冗余代码等。
前端有这种工具吗?除了eslint。像阿里、腾讯、平安,他们应该有安全质量部门对代码进行扫描吧,他们使用什么工具呢?

关注 3 回答 1

侃哥 收藏了文章 · 6月11日

Vue项目中出现Loading chunk {n} failed问题的解决方法

最近有个Vue项目中会偶尔出现Loading chunk {n} failed的报错,报错来自于webpack进行code spilt之后某些bundle文件lazy loading失败。但是这个问题的根本原因没有被找到,因为这个问题出现的偶然性太高了,而且有的手机上会出现,有的不会,用模拟器不会出现,用真机又会出现,不知道是网络原因还是webpack的bug。在github、stackoverflow等各种地方也找不到原因和解决方案,这是github上关于这个问题的讨论: Loading chunk {n} failed #742,虽然最后还是不了了之,但是大家可以参考一下。

这个问题出现概率比较小但是一旦出现就会导致页面崩溃,所以还是得解决,下面就贴出我的解决方案:

我的思路是既然找不到报错的原因那么尝试去捕获这个错误并做容错处理,有两种实现,一是在服务端捕获这个错误,一个是在前端捕获。

服务端实现

报错的原因是某些js bundle没有被找到,所以在服务端接收到获取该js文件的请求时先判断该js文件是否存在,如果存在直接返回js文件,如果不存在则返回一个提示信息给前端,让前端处理。假设服务端用express作为静态文件服务器,代码如下:

app.all(/\.js$/, (req, res) => {
    const fileName = req.path.slice(req.path.lastIndexOf('/') + 1);
    const filePath = path.resolve(__dirname, './public/static/js/' + fileName);
    if (fs.existsSync(filePath)) {
        fs.sendFile(filePath);
    } else {
        res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
        res.setHeader('Accept-Ranges', 'bytes')
        res.setHeader('Vary', 'Accept-Encoding')
        res.setHeader('Transfer-Encoding', 'chunked')
        res.setHeader('Last-Modified', new Date().toUTCString())
        res.setHeader('Cache-Control', 'no-cache')
        res.send('window.serverRebuildHook && window.serverRebuildHook();')
    }
});

当js文件未找到时,通过res.send('window.serverRebuildHook && window.serverRebuildHook();')向前端返回一条消息,并执行前端定义的serverRebuildHook方法。

接着我们在前端实现serverRebuildHook方法:

window.serverRebuildHook = function () {
  alert('服务器版本已更新,正在刷新本地缓存,请稍后...');
  location.replace(location.href);
}

方法很简单,提示一下用户服务端更新然后重新刷新当前页面。

这种实现是参考 github上的回答, 相对比较繁琐,而且用户体验并不好,只能刷新当前页面,不能跳转到目标页。

前端实现

由于项目里面用到了vue-router,vue-router的错误处理函数 onError 是不是能够捕获该错误呢?我们来看一下官方文档的说明:

image

当在渲染一个路由的过程中,需要尝试解析一个异步组件时发生错误。 完全符合我们场景,所以在onError方法中我们实现如下代码:

router.onError((error) => {
  const pattern = /Loading chunk (\d)+ failed/g;
  const isChunkLoadFailed = error.message.match(pattern);
  const targetPath = router.history.pending.fullPath;
  if (isChunkLoadFailed) {
    router.replace(targetPath);
  }
});

当捕获到Loading chunk {n} failed的错误时我们重新渲染目标页面,这种实现明显更简单和友好。

后续如果发现了导致Loading chunk {n} failed的本质原因会再更新本文,欢迎关注!

感谢大家阅读,另外,在这边帮朋友推一个爱心众筹,希望大家能够奉献点爱心,朋友母亲,身患直肠癌,目前在北京武警总医院接收治疗,可留言留下您的联系方式,日后感激大家!

clipboard.png

查看原文

侃哥 赞了文章 · 5月29日

浅谈js防抖和节流

防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。(信我,你看完肯定就懂了)

从滚动条监听的例子说起

先说一个常见的功能,很多网站会提供这么一个按钮:用于返回顶部。
返回顶部按钮

这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求-- 监听浏览器滚动事件,返回当前滚条与顶部的距离
这个需求很简单,直接写:

function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll  = showTop

但是!

图片描述

在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次
图片描述

然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

防抖(debounce)

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

当然 上述代码是为了贴合思路,方便理解(这么贴心不给个赞咩?),写完会发现其实 time = setTimeout(fn,delay)是一定会执行的,所以可以稍微简化下:


/*****************************简化后的分割线 ******************************/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置

此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。

到这里,已经把防抖实现了,现在给出定义:

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

节流(throttle)

继续思考,使用上面的防抖方案来处理问题的结果是:

  • 如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?(此处暂且不论哪种方案更合适,既然产品爸爸说话了我们就先考虑怎么实现)
图片描述

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}
/* 请注意,节流函数并不止上面这种实现方案,
   例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
    */

// 以下照旧
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 

运行以上代码的结果是:

  • 如果一直拖着滚动条进行滚动,那么会以1s的时间间隔,持续输出当前位置和顶部的距离

其他应用场景举例

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

  1. 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
  2. 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

思考总结

上述内容基于防抖和节流的核心思路设计了简单的实现算法,但是不代表实际的库(例如undercore js)的源码就直接是这样的,最起码的可以看出,在上述代码实现中,因为showTop本身的很简单,无需考虑作用域和参数传递,所以连apply都没有用到,实际上肯定还要考虑传递argument以及上下文环境(毕竟apply需要用到this对象)。这里的相关知识在本专栏《柯里化》和《this对象》的文章里也有提到。本文依然坚持突出核心代码,尽可能剥离无关功能点的思路行文因此不做赘述。


惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址

查看原文

赞 297 收藏 181 评论 36

侃哥 赞了回答 · 2019-12-03

解决swiper默认显示三个,中间显示全部图片两边显示部分图片,如何实现?

direction: 'horizontal',

    spaceBetween : 10,
    loop: true,
    slidesPerView : 1.2,
    centeredSlides : true

关注 9 回答 8

侃哥 收藏了问题 · 2019-12-03

swiper默认显示三个,中间显示全部图片两边显示部分图片,如何实现?

我用的是SWIPER响应式幻灯插件。如何让它显示如爱范儿网站首页那样的幻灯一样。
如下图:
想要的幻灯展示效果

请问该如何实现?先谢谢啦!~
SWIPER幻灯插件网站:http://www.swiper.com.cn/

侃哥 赞了问题 · 2019-12-03

解决swiper默认显示三个,中间显示全部图片两边显示部分图片,如何实现?

我用的是SWIPER响应式幻灯插件。如何让它显示如爱范儿网站首页那样的幻灯一样。
如下图:
想要的幻灯展示效果

请问该如何实现?先谢谢啦!~
SWIPER幻灯插件网站:http://www.swiper.com.cn/

关注 9 回答 8

侃哥 回答了问题 · 2019-12-03

解决swiper默认显示三个,中间显示全部图片两边显示部分图片,如何实现?

如果对轮播图的宽度没有要求,使用如下回答没问题:

spaceBetween : 10,
loop: true,
slidesPerView : 1.2,
centeredSlides : true

如果要设定当前视口中轮播图的宽度,则需要设置slidesPerView : 'auto'以及.swiper-slide的样式。
例如:.swiper-slide { width: 90%; }
swiper的配置项:

spaceBetween : 10,
loop: true,
slidesPerView : 'auto',
centeredSlides : true

可在如下demo的基础上修改:
一次显示多个slides(110)

关注 9 回答 8

侃哥 赞了回答 · 2019-12-02

解决如何将 windows 下编辑器中的 CRLF 替换为 LF?

用正则表达式替换,将\r\n替换为\n。其中\r指的是CR\n指的是LF

但是一般来说,大部分IDE和编辑器都有换行结束符直接切换的选项,比如我用的SublimeText:
clipboard.png

关注 3 回答 2

侃哥 收藏了文章 · 2019-06-13

移动端头部标签(HTML5 Head Meta)

<!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 -->
<html lang="zh-cmn-Hans"> <!-- 更加标准的 lang 属性写法 http://zhi.hu/XyIa -->
    <!-- 声明文档使用的字符编码 -->
    <meta charset='utf-8'>
    <!-- 优先使用 IE 最新版本和 Chrome -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <!-- 页面描述 -->
    <meta name="description" content="不超过150个字符"/>
    <!-- 页面关键词 -->
    <meta name="keywords" content=""/>
    <!-- 网页作者 -->
    <meta name="author" content="name, email@gmail.com"/>
    <!-- 搜索引擎抓取 -->
    <meta name="robots" content="index,follow"/>
    <!-- 为移动设备添加 viewport -->
    <meta name="viewport" content="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no">
    <!-- `width=device-width` 会导致 iPhone 5 添加到主屏
         后以 WebApp 全屏模式打开页面时出现黑边 http://bigc.at/ios-webapp-viewport-meta.orz -->
    <meta name="apple-mobile-web-app-title" content="标题">
    <!-- 添加到主屏后的标题(iOS 6 新增) -->
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <!-- 是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏 --> 
    <meta name="apple-itunes-app" content="app-id=myAppStoreID,
        affiliate-data=myAffiliateData, app-argument=myURL">
    <!-- 添加智能 App 广告条 Smart App Banner(iOS 6+ Safari) -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    <!-- 设置苹果工具栏颜色 -->
    <meta name="format-detection" content="telphone=no, email=no"/>
    <!-- 忽略页面中的数字识别为电话,忽略email识别 -->
    <!-- 启用360浏览器的极速模式(webkit) -->
    <meta name="renderer" content="webkit">
    <!-- 避免IE使用兼容模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 不让百度转码 -->
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
    <meta name="HandheldFriendly" content="true">
    <!-- 微软的老式浏览器 -->
    <meta name="MobileOptimized" content="320">
    <!-- uc强制竖屏 -->
    <meta name="screen-orientation" content="portrait">
    <!-- QQ强制竖屏 -->
    <meta name="x5-orientation" content="portrait">
    <!-- UC强制全屏 -->
    <meta name="full-screen" content="yes">
    <!-- QQ强制全屏 -->
    <meta name="x5-fullscreen" content="true">
    <!-- UC应用模式 -->
    <meta name="browsermode" content="application">
    <!-- QQ应用模式 -->
    <meta name="x5-page-mode" content="app">
    <!-- windows phone 点击无高光 -->
    <meta name="msapplication-tap-highlight" content="no">
    <!-- iOS 图标 begin -->
    <link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-57x57-precomposed.png"/>
    <!-- iPhone 和 iTouch,默认 57x57 像素,必须有 -->
    <link rel="apple-touch-icon-precomposed" sizes="114x114"
        href="/apple-touch-icon-114x114-precomposed.png"/>
    <!-- Retina iPhone 和 Retina iTouch,114x114 像素,可以没有,但推荐有 -->
    <link rel="apple-touch-icon-precomposed" sizes="144x144"
        href="/apple-touch-icon-144x144-precomposed.png"/>
    <!-- Retina iPad,144x144 像素,可以没有,但推荐有 -->
    <!-- iOS 图标 end -->
    <!-- iOS 启动画面 begin -->
    <link rel="apple-touch-startup-image" sizes="768x1004" href="/splash-screen-768x1004.png"/>
    <!-- iPad 竖屏 768 x 1004(标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="1536x2008" href="/splash-screen-1536x2008.png"/>
    <!-- iPad 竖屏 1536x2008(Retina) -->
    <link rel="apple-touch-startup-image" sizes="1024x748" href="/Default-Portrait-1024x748.png"/>
    <!-- iPad 横屏 1024x748(标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="2048x1496" href="/splash-screen-2048x1496.png"/>
    <!-- iPad 横屏 2048x1496(Retina) -->
 
    <link rel="apple-touch-startup-image" href="/splash-screen-320x480.png"/>
    <!-- iPhone/iPod Touch 竖屏 320x480 (标准分辨率) -->
    <link rel="apple-touch-startup-image" sizes="640x960" href="/splash-screen-640x960.png"/>
    <!-- iPhone/iPod Touch 竖屏 640x960 (Retina) -->
    <link rel="apple-touch-startup-image" sizes="640x1136" href="/splash-screen-640x1136.png"/>
    <!-- iPhone 5/iPod Touch 5 竖屏 640x1136 (Retina) -->
    <!-- iOS 启动画面 end -->
    <!-- iOS 设备 end -->
    <meta name="msapplication-TileColor" content="#000"/>
    <!-- Windows 8 磁贴颜色 -->
    <meta name="msapplication-TileImage" content="icon.png"/>
    <!-- Windows 8 磁贴图标 -->
 
    <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"/>
    <!-- 添加 RSS 订阅 -->
    <link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
    <!-- 添加 favicon icon -->

    <!-- sns 社交标签 begin -->
    <!-- 参考微博API -->
    <meta property="og:type" content="类型" />
    <meta property="og:url" content="URL地址" />
    <meta property="og:title" content="标题" />
    <meta property="og:image" content="图片" />
    <meta property="og:description" content="描述" />
    <!-- sns 社交标签 end -->

原文:前端开发博客 (http://caibaojian.com/mobile-...

查看原文

侃哥 赞了回答 · 2019-05-28

解决前端自动化测试是干嘛的?

从必要性来看,我觉得不测试就像开车闯红灯,不一定会出事,但是代码量越来越大,开车的路越来越长时,总有一天会出事。

假如有个项目:

function Swiper (el, options) {
    if (typeof el === 'string') {
        this.el = document.getElementById(el)
    } else {
        this.el = el
    }
    console.log('a swiper is created')
}

单元测试

内容:测试你项目中的单元。一个函数可以是一个单元,一个子模块可以是一个单元

目的:自动化,驱使你更好的设计(比如耦合性强的代码写单元测试时会发现很难),不耍流氓

如果没有测试类库,我想对上面的代码进行测试的话,可能会这样做:

  1. 写个demo.html,引入上面的代码

  2. 测试传元素id实例化的情况

  3. 测试传元素本身实例化的情况

  4. 测试swiper其他功能

  5. 并且最好能在控制台上console.log一些东西,告诉我当前正在测什么,测试结果是怎么样的

测试框架,就是帮助你完成上面的过程,归类测试用例,输出进度,测试结果,给出报告等。如mocha

断言库,则是让你除了===之外,有很多其他手段去做比较,而且可读性很强,比如:this.obj.should.have.property('id').which.is.a.Number()

相关的库有:chaishould.js,专门测http的superagent等

看Vue源码中怎么写单元测试:

图片描述

Karma是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。

覆盖率

图片描述

单元测试的目的是,将你的项目划分成小单元,每个单元测试中尽量设计case将代码逻辑中的每个分支到运行到,这样所有单元的测试跑下来,项目中的每行代码最好都被跑过一次,即覆盖率尽量去靠近100%。

如果对上面Swiper的代码只测试了:new Swiper(document.getElement('mountNode'))的情况的话,那么覆盖率就只有50%,所以要补上new Swiper('mountNode')的单元测试跑另外一个分支。

对于小项目而言,考虑到所有分支,不是难事,但是对于Vue这样的项目,做到100%的覆盖率,感觉很变态。

工具:istanbul

E2E测试

端到端测试,主要是测业务,绝大部分情况是指在浏览器上对某个网站进行某个的操作,比如:登录。

拿nightwatch首页例子来看:

图片描述

我没有用过nightwatch,但是也很容易就看出来这是在测试登录google并且输入一个关键词进行搜索业务的端到端的过程。能看到这个库提供了很多功能,来模拟人在页面上进行的操作,从而代替人的点击输入操作,来完成自动化的E2E测试。

也可以看看Vue项目中的E2E测试在测些什么东西:

图片描述

有没有很熟悉,这是在对官网上的几个示例进行测试,因为页面,就是Vue的业务。

现实

凤凰传奇:测试不是你想写,想写就能写~

写测试很花时间,可能比写项目代码更久,而且项目代码的变动,测试都要改。因此,国内绝大部分公司,没有开发人员去写测试的环境,只靠测试人员去保证软件质量。如果你在一家要求你勤勤恳恳写测试的公司里,请珍惜。

测试能改善设计,去看Martin Fowler的测试驱动一本书。

关注 12 回答 1

认证与成就

  • 获得 7 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-01-22
个人主页被 278 人浏览