Aran 发布的文章

检测设备是否是触屏设备的方法,Modernizr是这样实现的:

// Modernizr现在使用的方法
('ontouchstart' in window)
|| window.TouchEvent
|| window.DocumentTouch && document instanceof DocumentTouch

// Modernizr早期使用的方法
function isTouchDevice() {
  try {
    document.createEvent('TouchEvent');
    return true;
  } catch (e) {
    return false;
  }
}

但是随便在Mac上的Chrome78.0中验证了一下,发现是存在window.TouchEvent的,所以所谓的触屏检测方法其实并不可靠,可以看一下这篇You Can't Detect A Touchscreen

参考链接:Modernizr源码

下面这道js面试题目,主要考察对闭包概念的理解,用了好几年,在一面中能完全答对的面试者寥寥。这几天恰巧又在面试,想起这道题目,贴出来分析一下。

var a = 0, b = 0;
function A(a) {
  A = function B(b) {    
    alert(a+b++);
  }
  alert(a++);
}
A(1);
A(2);

把代码粘到浏览器控制台执行,很容易得出输出结果是1和4。

首先,这段代码只有两次输出,说明每次调用函数A,只执行了一次alert。很多同学会认为有四次输出,这里涉及的考点是函数声明和命名函数表达式。第3行的赋值语句,使用函数表达式更新了函数A为函数B,于是第二次调用函数A,实际执行的是函数B中的代码,所以每次调用只有一个alert执行。

其次是本道题目的重点,闭包变量a。第一次调用函数A,第6行的alert输出1,参数a在函数执行结束后的值是2,而这个a此时作为闭包变量留在了命名函数表达式中。第二次调用函数A,传入的2实际对应函数B的参数b,而此时函数B中的变量a的值也是2,于是第4行的alert执行,输出4。

最后,考察对i++++i的理解,还有一元运算符与二元运算符的运算顺序,这里就不细说了。

计算机中的字符串排序算法是直接比较字符的ASCII顺序,这样得出的排序结果有时候并不符合人类思维。比如下面这个文件名排序的例子。

一组文件1.jpg 2.jpg 10.jpg 100.jpg,排序后会得到这样的结果1.jpg 10.jpg 100.jpg 2.jpg,显然2没有10和100大,应该排在它们的前面才对。Windows资源管理器中的按名称排序就是这样实现的(Mac修正了这个问题)。

对字符串排序时能够智能地识别出其中的数字,归属于自然排序问题。对于自然排序问题,大多数编程语言都没有原生实现。PHP中有原生方法natsort()strnatcmp()提供支持,但是JavaScript却无能为力。

localeCompare方法可以按照本地语言习惯对字符串进行比较,能够实现汉字按音序排序,但是直接使用依然无法解决包含数字的字符串的自然排序问题。幸好,新的localeCompare标准增加了numeric属性,用于指定是否启用数字排序。虽然这一标准目前还没有得到所有浏览器的支持,但前景是好的,语法如下:

strA.localeCompare(strB, undefined, {numeric: true})

利用localeCompare这个新特性,我们可以很容易实现包含数字的字符串的自然排序,代码如下。

function naturalCmp(a, b) {
    return a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'});
}
var a = ["1.a", "100.a", "10.a", "2.a.10.b", "2.a.2.b"];
a.sort(naturalCmp);
// ["1.a", "2.a.2.b", "2.a.10.b", "10.a", "100.a"]

这种方法的局限性是,它只能分段识别字符串中的独立数字并进行比较,如果想更智能地区分浮点数、科学计数法或者日期的话,则可以使用这个javascript-natural-sort

在收集资料的过程中发现,早在1997年,David Koelle实现了Alphanum算法,解决了字母数字组合字符串的自然排序问题,相比上文localeCompare的实现方式,该算法把点号无条件当做数字中的小数点来参与数值比较,而不是智能地适时当做分隔符来处理,最终的效果不尽完美。毕竟是20多年前的实现嘛,但该算法的思想还是很值得我们借鉴的。Brian Huisman基于该算法的JS实现代码摘录如下。

Array.prototype.alphanumSort = function(caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z] = new Array();
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t.charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z][++y] = "";
        n = m;
      }
      this[z][y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a[x]) && (bb = b[x]); x++) {
      if (caseInsensitive) {
        aa = aa.toLowerCase();
        bb = bb.toLowerCase();
      }
      if (aa !== bb) {
        var c = Number(aa), d = Number(bb);
        if (c == aa && d == bb) {
          return c - d;
        } else return (aa > bb) ? 1 : -1;
      }
    }
    return a.length - b.length;
  });

  for (var z = 0; z < this.length; z++)
    this[z] = this[z].join("");
}

参考链接:MDN上的localeCompare文档 Alphanum Algorithm

最近写了个目录直读的相册程序,用于展示日常拍摄的照片,直接通过FTP上传,免去后台开发和维护的成本,使用起来也很方便。

大图展示采用lightbox灯箱插件,效果很不错。但是索引页的图片怎么布局好看呢,参考图虫网的实现方式,每行图片数目固定,行内图片左右对齐,行高固定,单张图片的宽度根据实际比例计算得出。做出来的效果确实不错,计算过程也不复杂。效果图如下。

相册效果图

程序是用PHP实现的,后端直接计算好尺寸输出HTML,但实际上这个布局实现过程由前端来实现更科学,于是用js改写了计算方法,代码如下。

const containerWidth = 1200;
const column = 4;
const imagesRow = [
    {width: 1200, height: 750, info: {}},
    {width: 801, height: 1200, info: {}},
    {width: 1200, height: 1200, info: {}},
    {width: 1200, height: 675, info: {}}
];

function fixImageSize(imagesRow, containerWidth, column) {
    let heights = imagesRow.map(image => image.height);
    let minHeight = Math.min.apply(null, heights);
    let minWidth = containerWidth / column;
    // 第一次循环,统一高度
    for (let i = 0; i < imagesRow.length; i++) {
        if (imagesRow[i].height === minHeight) {
            minWidth = imagesRow[i].width;
        } else if (imagesRow[i].height > minHeight) {
            imagesRow[i].width = imagesRow[i].width * minHeight / imagesRow[i].height;
            imagesRow[i].height = minHeight;
        }
    }
    containerWidth = containerWidth * imagesRow.length / column;
    let widths = imagesRow.map(image => image.width);
    let zoom = widths.reduce((prev, next) => prev + next) / containerWidth;
    let fixedHeight = minHeight / zoom;
    // 第二次循环,压缩宽度
    for (let i = 0; i < imagesRow.length; i++) {
        imagesRow[i].width = parseInt(imagesRow[i].width / zoom, 10);
        imagesRow[i].height = parseInt(fixedHeight, 10);
    }
    return imagesRow;
}

let ret = JSON.stringify(fixImageSize(imagesRow, containerWidth, column), null ,2);
/*[
    {
      "width": 380,
      "height": 237,
      "info": {}
    },
    {
      "width": 158,
      "height": 237,
      "info": {}
    },
    {
      "width": 237,
      "height": 237,
      "info": {}
    },
    {
      "width": 422,
      "height": 237,
      "info": {}
    }
  ]*/

相册写好后,先把大学期间上传到人人网的照片备份了一下,说不定哪天人人网又不行了,还能留个怀念,地址