分类
html

使用 <input type=”file”> 上传 ZIP/RAR 文件

上传文件要用到 <input type="file">,这个元素有个 accept 属性,可以用来筛选文件类型,方便用户选择。按照 MDN 的说法,这个属性的值支持以下几种形式:

  1. 合法的文件扩展名,大小写不敏感,以 . 作为开头,比如 .jpg.pdf
  2. 合法的 MIME type,不需要扩展名
  3. 媒体文件,audio/* 适配任意声音文件,video/* 适配任意视频,image/* 适配任意图片

同时,属性值可以使用 , 连接,表示“或”的意思。注意,这里只能是单独的 ,,不能有空格,不然有空格的部分会失效。所以,比如,一个上传图片、以及 PDF 的元素就可以写成:

<input type="file" accept="image/*,.pdf">

使用扩展名比较方便,但是扩展名太多不方便管理,比如 .jpg.jpeg,容易漏掉,所以我更喜欢 MIME type。那么压缩文件的 MIME type 是什么呢?经过一番搜索和尝试,是:.zip,.rar,application/x-rar-compressed,application/zip,application/x-zip-compressed,application/octet-stream

分类
js 技术

基于 @vue/cli 的项目配置 browserslist

前些日子虽然写了 最近折腾 @babel/preset-env 的一些小心得,但其实没有正确的理解和配置 browserslist,所以今天问题又来了。

分类
js

Nuxt.js 支持 core-js 3

Vue CLI 升级到 v4 之后,将内部的 core-js 依赖升级到 v3,关于 core-js v3 和 core-js v2 之间的区别,我在 最近折腾 @babel/preset-env 的一些小心得 里简单介绍过。

升级完 Vue CLI 之后,在调用 nuxt generate 生成静态页,就会报错,因为 Nuxt.js 默认使用 core-js 2。这个时候,如果 Nuxt.js 版本在 2.6.0 之后,就只需要修改 nuxt.config.js 里的配置,指定 core-js 的版本:

module.exports = {
  build: {
    babel: {
      presets({ isServer }) {
        return [
          [
            require.resolve('@nuxt/babel-preset-app'),
            // require.resolve('@nuxt/babel-preset-app-edge'), // For nuxt-edge users
            {
              buildTarget: isServer ? 'server' : 'client',
              corejs: { version: 3 }
            }
          ]
        ]
      }
    }
  }
}

参考文档:https://nuxtjs.org/guide/release-notes#v2.6.0

说起来 Nuxt.js,用它发布静态页比想象中复杂,如果你想快速掌握这个技能,不妨看下我的这本小书:

分类
js

解决 Firefox 下的 race 问题

我厂有几个产品,需要从后端获取大量的信息,为了让用户能够近乎实时的看到这些信息,大部分数据都是通过 WebSocket 发给前端。这些产品在 Chrome 下表现正常,但是在 Firefox 下经常把数据格式搞乱,最终渲染失败。

因为 Firefox DevTools 没法解析 WebSocket 数据,而且市场占有率比较低,所以我一直没有解决这个问题。前几天终于把最小可复现实例搞出来,正准备研究,结果同事已经修好了。

预览版的 Firefox 终于可以在 DevTools 里查看 WebSocket 每一帧的数据,所以她尝试看了一下,发现从解析二进制数据的角度来看,Firefox 应该没问题。于是又回到代码,发现了一个可能产生 race 的点:

if (data instanceof Blob) {
  data = await new Response(data).arrayBuffer();
}

因为服务器返回的数据是二进制,所以我需要进行一次转换,把它变成 ArrayBuffer,然后再通过 TextDecoder 转换成文本,然后处理。Response.arrayBuffer 返回的是 Promise,所以我就很自然的用 await,并且在 Chrome 上运行良好。

但是在 Firefox 里,某些帧会后发先完成转换,a b c 变成 a c b,于是数据格式错乱,无法正常解析。我怀疑 Chrome 并没有真的把这一步保留到用户,而是同时存了两份数据,这样转换的时候直接给出数据就好,所以是微任务,不走 Event loop,不会产生 race。而 Firefox 则是实时转换,所以是宏任务,所以出问题。

我尝试去翻了一下源码,无奈平时没看过,所以没能找到证据。如果有哪位同学刚好知道,可以在评论里告诉我。

分类
招聘

代友招聘:每日优鲜 BI 部门招前端技术经理

有个关系很好的朋友近期加盟每日优鲜,负责 BI 系统,据他描述,组内有三名初级前端,需要一个高级前端做技术经理,负责带团队。

薪资范围:30K~40K,16个月

工作地点:北京望京

分类
招聘

面试题:如何理解加班

前阵子参加 SF 的征文活动 一起分享你的故事,我的文章 我的编程职业生涯 有幸得到大家的支持,最终得奖:汤青松老师的《PHP Web 安全开发实战》。书前两天寄到了,还没来得及细看,也没法写书评,所以想了想,再分享一段职场经验吧。

分类
perl

Perl 笔记

主要记录在 Windows 下使用 Perl 的经验。

WSL

  1. 内建 Perl 环境

安装

我安装的是 Strawberry 版,没啥好说的,下载安装即可。

分类
前端工具链

使用 Webpack 动态打包文件夹

各式各样功能强大的小语言是我厂机器编程的特色,为了让更多的同学提前感受到 DSL 的威力,我们开发了 demo 应用:https://demo.openresty.com.cn/

Demo 里面,需要添加一些范例代码,方便用户直接体验。这些代码,可以通过后端 API 获取,也可以直接编译到前端代码里。目前范例很少,直接打包到一起应该是最简单的做法。

但是如何打包是个问题。经过一些研究,我觉得这样做好:

  1. 负责小语言的同事直接把范例写在项目仓库里,Edgelang 就用 .edge 扩展名,Navlang 就用 .nav 扩展名
  2. 前端在 webpack 里实现一个 requireAll 的功能,把所有代码当成纯文本用 raw-loader 加载进来
  3. 这样添加了代码后,重新 build 一下就好。范例文件可以用 lazy-load 的方式加载

Webpack 提供了一个方法叫 require.context,可以深度遍历一个目录,返回一个 context module,用这个 API 我们就可以动态的加载这个目录下的文件——具体的讲解和参数说明大家看下文档吧,这个部分中文资料不太多,我不太确定译名。我们可以在范例代码的目录下放一个 index.js,负责加载所有代码范例:

function requireAll(r) {
  r.keys().forEach(r);
}

const Languages = [
  'edgelang',
  'navlang',
];

let context = require.context('./edgelang', true, /\.(edge|css)$/);
requireAll(context);
context = require.context('./navlang', true, /\.(nav|css)$/);
requireAll(context);

export default Languages;

然后我们改一下 webpack.config.js 就可以了。

module.exports = {
  module: {
    rules: [
      {
        test: /\.(nav|edge|fan)$/,
        use: 'raw-loader',
      },
    ],
  },
};
分类
技术

我的编程职业生涯

经常爬论坛,时常看到年轻的同学对职业生涯有各种迷茫。赶上这次 SF 征文,索性聊一聊我个人的编程职业生涯,给大家一些参考吧。

分类
技术

不要用战术上的勤奋掩盖站略上的懒惰

前两天,有个同学在群里问一道面试题:

  1. 写一个函数,返回一个长度为5的数组
  2. 数组里的元素都是 2-32 不重复的整数
  3. 需要用递归的方式写,不超过15行

这道题其实蛮简单,而且我觉得出的并不好,不过最后再说,先回到群里。然后就有热心同学动手写代码了,但是他犯了两个严重错误:

第一个错误

我们都知道 Math.random() 会返回一个 [0, 1) 的随机数,我们可以乘上一个数然后取整来获取一定范围内的随机数,比如 Math.random() * 100 >> 0 就能取到 0 ~ 99。那位同学也想到了,但是这次范围不太规整,2~32正好是31个数,跟平时不太一样。于是他可能突然脑子抽筋,先试了 Math.floor,又试了 Math.ceil,都不行,然后,他就用了 Math.round

这是他第一个错误。四舍五入之后,随机数就不再均匀,此时他有很多种选择,但他偏偏选择了一定不对的方案。

第二个错误

然后我就指出“你这种做法是错的”,接着他就犯了第二个也是更严重的错误,他写了个循环,跑100w次,检查结果是否符合预期。

这里的问题在于,首先他只检查能否覆盖到 2 ~ 32,其次他完全没有考虑到概率。

结论

这就是典型的,用战术上的勤奋,掩盖战略上的懒惰。看起来似乎也很努力,响应很及时,实际上纯粹瞎搞。

明明应该先想清楚,或者先查清楚,再去做好的事情。变成了“上来就瞎JB搞”,搞出一个疑似有效的方案,就心满意足。遇到挑战的时候,则顺手抄起一套方案去验证,也不管这套方案的设计思路和覆盖面。

这样做的结果,运气好的时候,勉强不出错;运气不好,事故错误一个不会少。类似的例子,在我们周围不胜枚举,比如 996,不管产品卖不卖的出去,先按住大家把活儿干了再说。看起来为了明天拼拼拼,其实只是老板一厢情愿的豪赌(或者小赌)。

解决方案

不要只顾着埋头苦干,也要学会抬头看路。不要在错误的道路上努力耕耘,赶紧找到正确的道路。

比如找工作。有些同学海投,能投则投,所有都投,然后没有面试机会,回来抱怨说三本没人权。实际一看简历,非常烂,没有重点,没有区分,看不出他擅长什么,能做什么岗位。

准备简历并不简单。

  1. 要找几家公司作为主攻。比如拥有自己的产品、有大牛坐镇、赛道比较喜欢、技术成长空间比较大。
  2. 针对这几家公司的 JD,针对性的修改简历。比如做后台系统,那你就突出 Vue/React 等框架经验;比如做小程序,你就突出小程序开发。
  3. 海投简历也应该根据岗位调整。前端岗位有一些区分度,需要突出不同技术能力和经验。这样做可以让你显得更加适合一些岗位。比如:
    1. 数据可视化:Canvas、SVG、WebGL
    2. JS 框架、语言开发:编译原理、执行基础
    3. 后台等工具类开发:三大框架
    4. Hybrid 开发,比如 RN
    5. 小程序开发:小程序、普适类框架

随便做个简历,乱投,有面试就去面,也不考察公司,最后要么找不到工作,要么找不到好工作,都是典型的“用战术的勤奋掩盖战略的懒惰”问题。

另外,如果一件事是正确的,但是暂时没有收获到正确的结果,那么不要着急变换思路,再想想再看看再查查,排除错误,要在正确的道路上找到正解。

吐槽面试题

这道题出的不好。看得出来想考察候选人是否会使用递归(可能还要考察尾递归优化),但是题目本身不用递归就能做,而且效果更好。其实随便出个归并排序之类的就好了嘛。

总结

我们从小接受到很多歌颂勤奋的教育,所以大家喜欢“没有功劳,也有苦劳”,用勤奋来感动自己。现在一定要明白,闷头苦干是不对的,一定要看请道路,选好方向。

与诸君共勉。