热门搜索 :
考研考公
您的当前位置:首页正文

bug解决方案 & 小笔记

来源:东饰资讯网

该博客用于记录平时工作中遇到的一些小问题、小技巧以及一些最佳实践,不定期更新。

简单说说浏览器缓存

浏览器通过 HTTP 头部的字段来确定是否使用缓存,主要有两种方案:Expires 方案和 Cache-Control 方案,由于 Expires 方案隐患较大,所以推荐用 Cache-Control 方案。
Cache-Control方案 有一个 max-age 字段,这个字段告诉浏览器:
在 max-age 这个时间段内,如果再次请求资源,不需要向服务器发起请求,直接使用浏览器缓存即可;
超过 max-age 这个时间段的话,就需要通过 Last-Modified 和 Etag 这两个字段在服务器进行验证,以确定能否继续使用缓存;如果资源未发生变化,返回 304 告诉浏览器可以继续使用缓存。如果资源已经发生变化,则返回新资源,并更新浏览器缓存。

new String('Hello')字符串直接量 Hello 的区别

两者的本质区别是:Hello 是基本类型;new String('Hello') 是String对象的实例。从具体表现来说

typeof 'Hello' === 'string'
typeof new String('Hello') === 'object'

由于new String('Hello')是String对象的实例,所以它有String对象的所有原型方法和属性,这个没毛病。而 Hello 是基本类型,为什么它也可以使用String类的原型方法和属性呢?

// 以下两行代码都没有问题
(new String('Hello')).toLowerCase(); // hello
// 之所以基本类型也能正常运行,是由于:
// JS引擎遇到这种情况,会基于字符串直接量创建一个String对象
// 等到方法执行结束之后,会将这个String对象丢弃
'Hello'.toLowerCase(); // hello

switch 语句在比较时使用的是全等操作符

switch 语句在比较时使用的是全等操作符,所以并不会发生类型转换

// 由于是全等操作符,所以并不会发生类型转换
// 10 === '10' => false
function showCase(num) {
    switch (num) {
        case 10:
            console.log('case 10');
            break;
        default:
            console.log('case default');
            break;
    }
}

showCase('10'); // case default

如何量化网页加载速度?

我们常说网页加载速度快慢,但是否有一个比较量化的指标呢?这儿提供两个思路:
✦ 通过Chrome Devtools来直观的分析网页打开的速度,重点关注 DOMContentLoaded 事件和 onLoad 事件的时间,也需要关注各资源的timeline,但是,这并不能进行批量的分析,也不能统计出用户实际的数据。

资源加载时间点

✦ 通过 window.performance(),我们可以获得很多指标,而且粒度更细,如果把这些指标会传到服务器,可以得到更细致,更准确的结果。

// 计算加载时间
function getPerformanceTiming() {
    var performance = window.performance;

    if (!performance) {
        // 当前浏览器不支持
        console.log('你的浏览器不支持 performance 接口');
        return;
    }

    var t = performance.timing;
    var times = {};

    //【重要】页面加载完成的时间
    //【原因】这几乎代表了用户等待页面可用的时间
    times.loadPage = t.loadEventEnd - t.navigationStart;

    //【重要】解析 DOM 树结构的时间
    //【原因】反省下你的 DOM 树嵌套是不是太多了!
    times.domReady = t.domComplete - t.responseEnd;

    //【重要】重定向的时间
    //【原因】拒绝重定向! 就不该写成 
    times.redirect = t.redirectEnd - t.redirectStart;

    //【重要】DNS 查询时间
    //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
    // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5             
    times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

    //【重要】读取页面第一个字节的时间
    //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
    // TTFB 即 Time To First Byte 的意思
    // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
    times.ttfb = t.responseStart - t.navigationStart;

    //【重要】内容加载完成的时间
    //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
    times.request = t.responseEnd - t.requestStart;

    //【重要】执行 onload 回调函数的时间
    //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
    times.loadEvent = t.loadEventEnd - t.loadEventStart;

    // DNS 缓存时间
    times.appcache = t.domainLookupStart - t.fetchStart;

    // 卸载页面的时间
    times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;

    // TCP 建立连接完成握手的时间
    times.connect = t.connectEnd - t.connectStart;

    return times;
}

Array.sort 排序原理陷阱

Array.sort 用于对数组的元素进行排序,但它是按照字符编码的顺序进行排序的,而不是我们常规理论的顺序,举个例子。

[11, 22, 9, 4].sort(); 
// 11, 22, 4, 9

[11, 22, 9, 4].sort(function(a, b) {
    return a >= b ? true : false;
});
// 4, 9, 11, 22

也就是说,要对数字进行大小排序,务必加一个比较函数。

font-family 最佳实践

不同平台对字体有不同的支持,为了能够在各个平台windows/Mac/Android/iOS都有不错的表现,我们希望设置一份通用的font-family

    font-family: 
        /* Helvetica改善版,增加了不同粗细和宽度的字形 */
        "Helvetica Neue", 
        /* Mac专有,比Tahoma和Arial更漂亮,推荐 */
        Helvetica, 
        /* 比Arial更清晰,建议写在Arial前面 */
        Tahoma, 
        Arial,
        /* 苹方,苹果推出的最新字体,效果优雅,只有最新系统才能支持(Mac 10.11/ iOS 9)*/
        "PingFang SC", 
        /* 冬青黑体,Mac中间版本(Mac 10.6开始)支持的字体,很受欢迎,小字号时很清晰。*/
        "Hiragino Sans GB", 
        /* 华文黑体,Mac早起版本(Mac 10.6之前)支持的字体,Chrome浏览器默认的字体,效果还不错 */
        "Heiti SC", 
        /* 华文细黑,和华文黑体属于同一字体家族系列 */
        STXihei, 
        /* 微软雅黑,windows支持的字体,显示效果不错 */
        "Microsoft YaHei", 
        /* 黑体,作为一种fallback兼容 */
        SimHei, 
        /* Linux下最佳简体中文字体 */
        "WenQuanYi Micro Hei", 
        /* 当前面所有字体都找不到时,使用系统上存在的非衬线字体 */
        sans-serif;

其他注意事项:
✦ 字体名称建议写成纯英文或者中英混合,以防部分用户设置了系统字体导致中文字体声明不生效。
✦ 绝大部分中文字体里都包含英文字符和数字,反之则不然,为了让英文字符和数字更好看,我们通常会单独设置英文字体,并放到最前面。

word-wrap 和 word-break的区别

单个英文单词默认不进行拆分换行,有时候我们想要是英文单词换行,最佳实践如下:

word-wrap: break-word;
word-break: break-all;

那么 word-wrapword-break 有啥区别呢?一句话来说就是:word-wrap 决定了是否要对单词进行拆分换行,word-break 决定了以什么样的方式对单词进行拆分换行,如下图所示:

word-wrap 和 word-break的区别

微信小程序开发环境和ShadowSocks等VPN冲突

小程序的坑,估计以后还有更多,先记录一下。

GET 
net::ERR_NAME_NOT_RESOLVED
appservice报错截图

关掉ShadowSocks即可。
一个小技巧就是,打开微信web开发者工具之前关闭ShadowSocks,打开开发者工具之后,可以打开ShadowSocks。

input[type="range"] 选择器存在兼容性问题

由于IE9及其以下的浏览器不支持input[type="range"]元素,会自动的将其转换为input[type="text"],这种情况下,使用input[type="range"]选择器并不能获取到元素,慎用。
举一反三,类似的元素也会遇到同样的问题。

浏览器JS能读取MP3的封面图么?

function showTags(url) {
    var tags = ID3.getAllTags(url);
    console.log(tags);
    document.getElementById('title').textContent = tags.title || "";
    document.getElementById('artist').textContent = tags.artist || "";
    document.getElementById('album').textContent = tags.album || "";

    var image = tags.picture;
    if (image) {
        var base64String = "";
        for (var i = 0; i < image.data.length; i++) {
            base64String += String.fromCharCode(image.data[i]);
        }
        var base64 = "data:" + image.format + ";base64," +
            window.btoa(base64String);
        document.getElementById('picture').setAttribute('src', base64);
    } else {
        document.getElementById('picture').style.display = "none";
    }
}

题外话:这个问题是在一个高级群里有人提出,一方面出于对提问者业余的提问技巧的“嫌弃”,另一方面下意识的觉得这个前端做不了,在没有严谨的查询的情况下就告知做不到。但后来这个查询就花了我不到1分钟的时间!最后啪啪啪打脸!打脸不重要,重要的是:要对技术保持谦卑!要对技术保持谦卑!要对技术保持谦卑!

gif 图会频繁的触发jank

gif 图会频繁的触发long times frames,对性能有较大影响

简单粗暴的判断当前设备是否是Mobile

使用以下代码即可:

var regMobile = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i;

isMobile = regMobile.test(navigator.userAgent);

如果想要将ipad也是别为Mobile,添加ipad即可:

var regMobile = /Android|webOS|iPhone|iPod|iPad|BlackBerry|IEMobile|Opera Mini/i;

isMobile = regMobile.test(navigator.userAgent);

package.json 依赖版本号解读

latest:安装最新版本。
指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式规定,安装时只安装指定版本。
波浪号+指定版本:安装时不改变大版本号和次要版本号,比如~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x。
插入号+指定版本:安装时不改变大版本号,比如ˆ1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x。需要注意的是,如果大版本号为0,则插入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来程序的不兼容。

如何原生实现hasClass功能

用正则匹配(/(^|\s)name(\s|$)/i).test(elNode.className)即可,其中name表示需要判断的class名称,elNode表示需要判断的DOM节点。
注意正则中的\b不适用于连字符命名,所以使用\s来匹配,然后兼容一下开头结尾的情况。

如何获取设备像素密度?

使用window.devicePixelRatio可以获取到设备像素密度,也就是常说的N倍屏。
配合设备的物理宽度width,可以计算出设备的像素宽度。然后你就可以根据屏幕的像素宽度获取最适合屏幕的图片。
该属性在IE11才被支持,此外,window的屏幕像素密度不一定是整数,mac浏览器缩放之后,屏幕像素密度也不是整数。所以获取dpr一定要取整:dpr = Math.round(window.devicePixelRatio || 1);

devicePixelRatio兼容性

多次调用RegExp.test()/RegExp.exec(),让你猝不及防!

一个正则regHomePage,一个字符串pathname,连续调用regHomePage.test(pathname),然后得到了两次完全不一样的结果,简单调试了一下,如下图:

连续两次运行,但得到完全不一样的结果

调试过程,我“机智”的发现两次运行的lastIndex是不一样的,然后查了下lastIndex的相关资料,终于找到了原因:
由于我们在正则表达式中使用了g(全局匹配),这就导致RegExp.test()/RegExp.exec()会全局匹配,因此可能会多次匹配成功,返回多个结果,当匹配不到的时候,会自动将lastIndex置为0,而不是想当然的认为RegExp.test()/RegExp.exec()只运行一次。
虽然细想是情理之中的,但第一次遇见的时候还觉得有点蒙。

cssnano和base64 uri冲突,导致base64图片错误

Safari中img的width不能立即准确获得

Safari中,在html代码后面紧追一段js代码,获取图片的width失败,不是0就是1;
用clientWidth能够解决这个问题。

var img = document.getElementById('imageid'),
    width, height;

width = img.clientWidth;
height = img.clientHeight;

统计字符长度,区分中英文

把双字节的替换成两个单字节的然后再获得长度

function getBLen(str) {
    if (str == null) return 0;
    if (typeof str != "string") {
        str += "";
    }
    return str.replace(/[^\x00-\xff]/g, "01").length;
}

英文字符的charCode在[0, 128]之间

function getBLen(str) {
    var realLength = 0,
        len = str.length,
        charCode = -1;

    for (var i = 0; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode >= 0 && charCode <= 128) {
            realLength += 1;
        } else {
            realLength += 2;
        }
    }

    return realLength;
}

arguments对象

arguments是函数的内部变量,表示函数的参数列表。
它是一个 类数组 对象,除了length,他没有Array特有的属性和方法。

// 将arguments对象转换成Array的常用方式
var args = [].slice.call(arguments);
// 尽管上面的代码可行,但是会阻碍JavaScript引起的优化,不是最优方案。
// 从JavaScript引擎优化的角度来说,arguments对象不应泄露或者传递出去
// 知道这个事情就行了,实际项目中仍然推荐这样使用。

// 应该通过遍历arguments对象的方式来构建一个新的数组
function doesntLeakArguments() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; ++i) {
        args[i] = arguments[i];
    }
    return args;
}

此外,arguments还可以方便的实现一些简单的功能,比如实现一个join函数。

function joinWords(sep) {
    var args = [].slice.call(arguments, 1);
    return args.join(sep);
}

// 返回 "red, orange, blue"
myConcat(", ", "red", "orange", "blue");

string 的 replace 方法

replace方法使用替换值(replacement)替换匹配模式(pattern),匹配模式可以是字符串或正则表达式,替换值可以是字符串或函数。
该方法并不改变字符串本身,而是返回替换后的字符串。
由于使用方式过于灵活,建议使用str.replace(regexp, function)这种模式

// 替换所有的apples为oranges
var str = "Apples are round, and apples are juicy.";
var newstr = str.replace("/apples/ig", function(match, offset, origin) {
    return 'oranges';
});
console.log(newstr);

// 交换姓名顺序
var str = "John Smith";
var newstr = str.replace(/(\w+)\s(\w+)/ig, function(match, p1, p2, offset, origin) {
    return p2 + ' ' + p1;
});
console.log(newstr);

base64的使用场景

图片足够小,复用高,基本不会被更新,但用处特殊无法被制作成精灵图。

  • background-image,背景图无法放到精灵图中,但是可以通过循环平铺一小块图片来设置成背景图。
  • 比如一些小gif图,无法放到到精灵图中,发请求又太浪费。

UMD模块脚手架

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {
    //use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

怎么处理gulp里的异步任务

gulp核心概念是对stream做顺序处理,所以通过return stream的方式将stream传递到下一个任务

gulp.task('build-clean', function() {
    // Return the Promise from del()
    return del([BUILD_DIRECTORY]);
//  ^^^^^^
//   This is the key here, to make sure asynchronous tasks are done!
});

gulp.task('build-scripts', function() {
    // Return the stream from gulp
    return gulp.src(SCRIPTS_SRC).pipe(...)...
//  ^^^^^^
//   This is the key here, to make sure tasks run to completion!
});

但是对于异步任务,怎么处理呢?怎么确保顺序执行呢?

gulp.task('callback-example', function(callback) {
    // Use the callback in the async function
    fs.readFile('...', function(err, file) {
        console.log(file);
        callback();
//      ^^^^^^^^^^
//       This is what lets gulp know this task is complete!
    });
});

对,核心就在于callback!

渐变

渐变是CSS3引入的特性,支持在两个或多个指定的颜色之间平稳的过度
主要有线性渐变(向下/向上/向左/向右/对角方向)和径向渐变(由它们的中心定义)
创建线性渐变,必须至少定义两种颜色节点,同时可以设置一个角度

background: linear-gradient(angle, color-stop1, color-stop2, ...);
Paste_Image.png

创建径向渐变,必须至少定义两种颜色节点,同时也可以指定渐变的中信,形状,大小
默认渐变的中心点,渐变的形状是ellipse,渐变的大小是farthest-corner(到最远的角落)

background: radial-gradient(center, shape size, start-color, ..., last-color);

页面内容区域根据浏览器高度自适应

body {
    // 页头
     {
        margin-bottom: -60px;
    }
    // 内容区域
    .page-container {
        min-height: 100%;
        padding-top: 60px;
        padding-bottom: 156px;
        .main-content {
            width: 1000px;
            margin: 0 auto;
        }
    }
    // 页尾
     {
        margin-top: -156px;
    }
}

opacity & rgba

opacity 和 rgba 中均设置的不透明度,即0表示全透明,1表示全不透明

glob 规则

  • * 匹配0个或多个除/以外的字符
  • ? 匹配1个除/以外的字符
  • **匹配0个或多个字符包括/
  • {}应用多个规则,分割
  • !放到规则的开头,表示取反,即匹配不命中该规则的文件
  • [...]匹配某个字符范围,可以用!或者^对该范围取反
  • !(pattern|pattern|pattern)匹配任意pattern,取反
  • ?(pattern|pattern|pattern)匹配0个或1个pattern
  • @(pattern|pattern|pattern)仅匹配1个pattern
  • +(pattern|pattern|pattern)匹配1个或多个pattern
  • *(pattern|pattern|pattern)匹配0个或多个pattern

删除部分目录,保留部分目录

用del删除文件或目录时,下面几种代码差异明显,使用时需注意

// 会删除所有文件,不会保留layouts下面的erb文件,很疑惑
// 猜测是/**/*匹配了layouts目录,会删掉layouts目录,所以保留目录下的文件并不会生效【bug?】
return del([
    config[project].dest + '/**/*',
    '!' + config[project].dest + '/layouts/**/*.erb'
], { force: true });

// 会删除所有文件,保留layouts目录但不保留文件
return del([
    config[project].dest + '/**/',
    '!' + config[project].dest + '/layouts'
], { force: true });

// 会删除除layouts/users/api之外的所有目录,保留layouts/users/api目录及其目录下的文件
return del([
    config[project].dest + '/*',
    '!' + config[project].dest + '/layouts',
    '!' + config[project].dest + '/users',
    '!' + config[project].dest + '/api'
], { force: true });

JavaScript中的递归

递归的本质是调用自身,是一项很重要的编程技巧
比如求正整数N的阶乘

function factorial(num) {
    if (num <= 1) {
        return 1;
    }

    return num * factorial(num - 1);
}
// 这种方式有个隐患,一旦factorial在外层被修改,就会报错
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //报错

要解决函数可能被修改的问题,我们通常使用arguments.callee(已弃用)或者函数表达式来解决。

// 推荐使用的方式
var factorial = (function f(num) {
    if (num <= 1) {
        return 1;
    }

    return num * f(num - 1);
});

动画闪烁解决方案

backface-visibility: hidden;
perspective: 1000;

组件化

Paste_Image.png

提升IE浏览器渲染内核的版本

Top