• 前端工程化之低代码体系

    前言

    一千个人的眼中肯定有不止一千个哈姆雷特,本文探讨的并不仅仅局限可视化搭建,而是提高研发效率的低代码体系。

    下文将围绕低代码体系,分享一下对这套体系的理解。如有不同的意见,欢迎探讨!

    什么是低代码

    定义

    一切能通过少写代码来完成业务的方式都可以纳入低代码体系。

    低代码是介于无代码与全代码之间的体系,借助工具、约束、配置生成的通用业务代码,在此基础进行少量改动以便快速进行不同的定制化业务开发。

    对比

    无代码的优势:

    1. 使用、配置的过程简单
    2. 无开发经验的成员(如运营、产品、设计等)即可通过此体系完成功能业务的开发

    无代码的劣势:

    1. 业务模型、边界单一,无法进行特殊功能定制
    2. 研发介入的时候,由于要考虑兼容、特殊化定制等问题,导致更新滞后性且耗时严重

    低代码的优势:

    1. 可以借助搭建、配置等手段快速生成基础业务代码
    2. 业务模型、边界的范围扩大,且可以再基础代码上进行二次定制开发
    3. 二次定制开发的过程中可以进行物料积累,反馈到低代码体系中,完善流程

    低代码的劣势:

    1. 搭建、配置的过程,需要了解一定的开发体系知识,上手成本超过无代码
    2. 业务模型、边界虽然扩大但依然有一定的范围限制(如:电商行业、物联网模型等)

    简单来看,低代码是在无代码的基础上做了一定的容错与改进,以便开发能够更好地介入,提高整个项目周期的效率。

    低代码的技术意义与商业价值

    通过上面的简单对比分享之后,再继续在技术与商业上面对低代码体系来更深层次的探讨一下

    技术意义

    1. 同种类型业务的快速迭代是不用多说的,提高了效率,减少了成本。
    2. 可视化的技术可以将低代码体系从研发的角色延伸到设计、产品、运营等角色,在项目开发初期的时候对项目就能做出一定的分析与构建
    3. 通过低代码生成的项目,可以统一进行基础库的升级。埋点、检测等一系列的有规则的统一配置都使用相对应的低代码生成。
    4. 减少人工开发,测试回归、问题定位就会更加精准。

    商业价值

    将技术上的意义全部体现在商业中可以总结为下面 3 点:

    1. 速度:可以快读搭建基础项目、进行个性化定制
    2. 成本:减少中级研发的投入
    3. 安全:机器永远比人安全,产生的问题会更少,且更容易定位

    大家可能会感觉,为什么减少的是中级研发的,而不是初级。在低代码体系完成之后其实最艰难的是熟练工,熟练的去开发业务而不是工具的这帮人。初级工可以借助这套体系快速成为熟练工,那么之前能够熟练开发业务的人员的价值就势必会减少。要不继续再往上走,成为定制体系的人,要不就减少自己的价值以符合企业的成本

    举一个例子:电动车的出现倒逼很多中小型的自行车公司的破产,但是顶层自行车的车场感受会小一点,但也没有好多少。换在开发也是一样,在大背景的碾压下,即使顶级做自行车的都很吃力,那么剩下的就只能转到电动车上了。

    低代码体系的搭建

    这里的搭建并不仅仅局限于低代码的搭建工程,而是指的是怎么样去搭建一套适合自己当前业务的低代码体系。

    业务 + 物料库

    低代码的体系完成是一定是贴合当前业务,或者是贴合某一类通用的业务模型。

    所以在进行低代码体系搭建的过程中,首先一定会积累大量业务模型,进行不断的训练。

    那么第一步的产出就是对业务的提炼,从基础的组件库上升到物料库。

    这里将物料库的组成再放大一点(暂时没有更好的词语形容)

    物料库是由基础组件、业务组件、业务模板、业务框架、基础代码区块、业务代码区块等等一系列组成的一个庞大的物料体系。

    基本上可以通过上述的物料碎片快速构建出完成当前业务 70 - 90% 以上的代码。

    当完成上述的要求之后,就可以考虑下一步的怎么样利用工具或者规范来提高效率。

    交互 + 效率

    低代码体系的第二步是交互与效率。

    1. 从最基础的 CLi 工具开始着手,搭建符合自己业务的 CLi 工具,通过简单的交互,即可添加对应的代码区块、选择对应的业务模板
    2. 联合后台,通过接口产物,直接生成对应的表单、表格模板。
    3. 通过 Sketch 解析或者 AI 识图,产生对应的结构代码。(Sketch 可以通过工具解析成树状接口,通过添加对应的节点标记,可以拿到你想要的元数据)

    如果大家接触前端的年限比较早的话,那么一定听过或使用过 Dreamweaver 这款前端工具。实时预览 + 拖拽配置很酷炫,但没有业务模型约束,也没有上述物料库的支持,生成出来的代码还不如手写,那么这款产品只有在 demo 阶段还是有很不错的效果,但在实际开发中就失去了所具备的优势。

    这是一个反面的例子,如果你设计的低代码体系在实际运用中的效率远低于直接编程带来的结果,那么这个体系一定是失败的。

    产品不一定是差的,但是使用这个产品带来的结果也不一定是好的。一切流程、工具都是需要紧贴合业务走,才能发挥最大的价值。

    从第二点可以看出,不仅仅是有界面交互的过程才被称为低代码。后端可以通过工具将对应的接口参数抽离、组合,直接生成对应的 CURD 页面,只要符合一定的规则与样式,也就是表单、表格的基础组件具备的情况下,可以直接输出业务页面甚至是一个简单的 CURD 项目。低代码体系的构建不仅仅限于可视化搭建这一块,而是贯穿整个业务研发流程的。

    当然可视化搭建工程一直都是最直观、简单的低代码手段,但还是要强调,拖拽搭建也只是一种手段并不是唯一

    能够以最少的人力介入开发的流程、工具都可以归属到低代码体系。

    流程设计

    大多数低代码设计是抽象了整体的业务模型通过 JSON 配置,以一定的解析规则来生成具体的代码。

    那么如图所示(二次开发带来的是个性化定制,足够丰富的物料库在面对简单业务的时候甚至可以省去这一步),配置生成即代表了具有一定的约束。

    项目配置大体可以分为 2 级:

    1. 基础类型选择:移动端、PC、跨端、RN、小程序等
    2. 业务类型选择:电商、广告、大屏、财务等

    从选择项目类型决定最后的组件配置,每种项目类型的选择带来的不仅仅是基础组件的渲染更是业务组件的选择。

    具体的 JSON 配置与可视化搭建相关的内容,有很多的博客都介绍到,后续有部分的博文推荐,大家可以看下。

    总结

    可能部分同学对低代码有些误会就是低代码平台的受众一定是非程序员。这个先入为主的情况感觉坑了不少的研究低代码搭建的同学。

    其实应该是低代码平台产出的产品的受众是非程序员,而使用低代码平台的一定是程序员或者懂一些编程思想的人

    如果低代码体系是贴合目前的业务情况下,通过可视化搭建这种简易交互能将部分的业务能力转嫁到对应的运营、产品、设计身上。

    将部分转嫁出去的业务封装的足够简便是可以降低部分研发时间,但是这一部分对他们来说是无代码接触。不能将低代码的编程思想带入太多而造成额外的心智负担。(无代码想要达到低代码的结果是配置过程极其复杂。)

    这是为什么前文一直在强调的要有业务模型与边界,当脱离了业务模型与边界之后,你会发现可视化搭建的心智成本带来的副作用会远超单纯的工具组合开发。

    写在最后

    会开发工具的研发不一定是好研发但不会开发业务的研发一定不是好研发。

    以技术辅助业务,以业务反馈技术。

  • 超简单的网站暗黑模式,它真的超简单!

    暗黑模式是网站颇受欢迎的功能,用 HTML、CSS、JS 即可实现。但为什么你没有在你的个人网站实现暗黑功能呢?只要这简单的三个步骤,你就可以拥有暗黑模式。实操开始!(译:并不是所有的人都会 CSS,所以这是为什么我会翻译本文的原因,它真的超简单!)

    暗黑模式 Step 1:

    如果你还没有个人网站,先简单地创建一个 HTML 文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- index.html -->

    <!DOCTYPE html>
    <head>
    <title>Dark Mode Feature</title>
    <meta charset="UTF-8">
    <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>

    <body>
    ...
    <body>
    </html>

    有了网站之后,下面来实现 HTML 和 CSS。

    暗黑模式 Step 2:

    开始往 HTML 里添加我们想要的东西,先来添加链接 JS 和 CSS 文件的方法,就像 ADD CSS FILEADD JS FILE 注释下的那样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- index.html -->

    <!DOCTYPE html>
    <head>
    <title>Dark Mode Feature</title>
    <meta charset="UTF-8">
    <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- ADD CSS FILE -->
    <link rel="stylesheet" href="main.css">

    <!-- ADD JS FILE -->
    <script src="main.js"></script>
    </head>

    <body>
    ...
    <body>
    </html>

    现在我们要开始创建 JS 和 CSS 文件了。你可以随意更改你的 CSS 文件,在这里,我添加了一些代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* main.css */
    body {
    background-color: white;
    color: black;
    }

    .dark-mode {
    background-color: black;
    color: white;
    }

    body 模块,我们设定默认网页背景色为白色、文本为黑色,而在 dark-mode 模块,我们将网页背景色变为黑色、文本则是白色。

    好了,我们要创建 main.js 文件了,这是实现暗黑功能的关键;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //main.js

    function darkmode() {
    const wasDarkmode = localStorage.getItem('darkmode') === 'true';
    localStorage.setItem('darkmode', !wasDarkmode);
    const element = document.body;
    element.classList.toggle('dark-mode', !wasDarkmode);
    }

    function onload() {
    document.body.classList.toggle('dark-mode', localStorage.getItem('darkmode') === 'true');
    }

    成功创建 CSS 和 JS 文件后,你现在只用做最后一件事。

    暗黑模式 Step 3:

    经过上面 2 个步骤,你可能认为暗黑模式已经实现,但其实不是。来问自己一个问题:如果我的网站有多个页面要咋整?如何在每个页面启用黑暗模式而不是默认的白色背景?答案比你想的要简单得多。在每个页面的初始 body tag 中添加:

    1
    onload="onload()"

    就这么简单,希望它对你有用,谢谢阅读本文^^

  • Facebook重构:抛弃Sass/Less,迎接原子化CSS时代

    随着 Facebook 和 Twitter 最近的产品部署,我认为一个新的趋势正在缓慢增长:Atomic CSS-in-JS

    在这篇文章中,我们将看到什么是Atomic CSS(原子 CSS),它如何与 Tailwind CSS 这种实用工具优先的样式库联系起来,目前很多大公司在 React 代码仓库中使用它们。

    由于我不是这方面的专家,所以我不会去深入探讨它的利弊。我只是希望能帮助你了解它的大致内容。

    先抛出一个令人开心的结论,新的 CSS 编写和构建方式让 Facebook 的主页减少了 80% 的 CSS 体积

    什么是原子 CSS?

    你可能听说过各种 CSS 方法,如 BEM, OOCSS…

    1
    <button class="button button--state-danger">Danger button</button>

    现在,人们真的很喜欢 Tailwind CSS 和它的 实用工具优先(utility-first) 的概念。这与 Functional CSS 和 Tachyon 这个库的理念非常接近。

    1
    2
    3
    4
    5
    <button
    class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
    >
    Button
    </button>

    用海量的实用工具类(utility classes)组成的样式表,让我们可以在网页里大显身手。

    原子 CSS 就像是实用工具优先(utility-first)CSS 的一个极端版本: 所有 CSS 类都有一个唯一的 CSS 规则。原子 CSS 最初是由 Thierry Koblentz (Yahoo!)在 2013 年挑战 CSS 最佳实践时使用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /* 原子 CSS */
    .bw-2x {
    border-width: 2px;
    }
    .bss {
    border-style: solid;
    }
    .sans {
    font-style: sans-serif;
    }
    .p-1x {
    padding: 10px;
    }
    /* 不是原子 CSS 因为这个类包含了两个规则 */
    .p-1x-sans {
    padding: 10px;
    font-style: sans-serif;
    }

    使用实用工具/原子 CSS,我们可以把结构层和表示层结合起来:当我们需要改变按钮颜色时,我们直接修改 HTML,而不是 CSS!

    这种紧密耦合在现代 CSS-in-JS 的 React 代码库中也得到了承认,但似乎 是 CSS 世界里最先对传统的关注点分离有一些异议。

    CSS 权重也不是什么问题,因为我们使用的是最简单的类选择器。

    我们现在通过 html 标签来添加样式,发现了一些有趣的事儿:

    • 我们增加新功能的时候,样式表的增长减缓了。
    • 我们可以到处移动 html 标签,并且能确保样式也同样生效。
    • 我们可以删除新特性,并且确保样式也同时被删掉了。

    可以肯定的缺点是,html 有点臃肿。对于服务器渲染的 web 应用程序来说可能是个缺点,但是类名中的高冗余使得 gzip 可以压缩得很好。同时它可以很好地处理之前重复的 css 规则。

    一旦你的实用工具/原子 CSS 准备好了,它将不会有太大的变化或增长。可以更有效地缓存它(你可以将它附加到 vendor.css 中,重新部署的时候它也不会失效)。它还具有相当好的可移植性,可以在任意其他应用程序中使用。

    实用工具/原子 CSS 的限制

    实用工具/原子 CSS 看起来很有趣,但它们也带来了一些挑战。

    人们通常手工编写实用工具/原子 CSS,精心制定命名约定。但是很难保证这个约定易于使用、保持一致性,而且不会随着时间的推移而变得臃肿。

    这个 CSS 可以团队协作开发并保持一致性吗?它受巴士因子的影响吗?

    巴士系数是软件开发中关于软件专案成员之间讯息与能力集中、未被共享的衡量指标,也有些人称作“货车因子”、“卡车因子”(lottery factor/truck factor)。一个专案或计划至少失去若干关键成员的参与(“被巴士撞了”,指代职业和生活方式变动、婚育、意外伤亡等任意导致缺席的缘由)即导致专案陷入混乱、瘫痪而无法存续时,这些成员的数量即为巴士系数。

    你还需要预先开发好一个不错的实用工具/原子样式表,然后才能开始开发新功能。

    如果实用工具/原子 CSS 是由别人制作的,你将不得不首先学习类命名约定(即使你知道 CSS 的一切)。这种约定是有主观性的,很可能你不喜欢它。

    有时,你需要一些额外的 CSS,而实用工具/原子 CSS 并不提供这些 CSS。没有约定好的方法来提供这些一次性样式。

    Tailwind 赶来支援

    Tailwind 使用的方法是非常便捷的,并且解决了上述一些问题。

    它通过 Utility-First 的理念来解决 CSS 的一些缺点,通过抽象出一组类名 -> 原子功能的集合,来避免你为每个 div 都写一个专有的 class,然后整个网站重复写很多重复的样式。

    传统卡片样式写法:

    Tailwind 卡片样式写法:

    它并不是真的为所有网站提供一些唯一的实用工具 CSS,取而代之的是,它提供了一些公用的命名约定。通过一个配置文件,你可以为你的网站生成一套专属的实用工具 CSS。

    ssh 注:这里原作者没有深入介绍,为什么说是一套命名约定呢而不是生成一些定死的 CSS 呢?

    举几个例子让大家感受一些,Tailwind 提供了一套强大的构建系统,比如默认情况下它提供了一些响应式的断点值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // tailwind.config.js
    module.exports = {
    theme: {
    screens: {
    'sm': '640px',
    // => @media (min-width: 640px) { ... }

    'md': '768px',
    // => @media (min-width: 768px) { ... }

    'lg': '1024px',
    // => @media (min-width: 1024px) { ... }

    'xl': '1280px',
    // => @media (min-width: 1280px) { ... }
    }
    }
    }

    你可以随时在配置文件中更改这些断点,比如你所需要的小屏幕 sm 可能指的是更小的 320px,那么你想要在小屏幕时候采用 flex 布局,还是照常写 sm:flex,遵循同样的约定,只是这个 sm 已经被你修改成适合于项目需求的值了。

    在比如说,Tailwind 里的 spacing 掌管了 margin、padding、width 等各个属性里的代表空间占用的值,默认是采用了 rem 单位,当你在配置里这样覆写后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // tailwind.config.js
    module.exports = {
    theme: {
    spacing: {
    '1': '8px',
    '2': '12px',
    '3': '16px',
    '4': '24px',
    '5': '32px',
    '6': '48px',
    }
    }
    }

    你再去写 h-6(height), m-2(margin), mb-4(margin-bottom),后面数字的意义就被你改变了。

    也许从桌面端换到移动端项目,这个 6 代表的含义变成了 6rem,但是这套约定却深深的印在你的脑海里,成为你知识的一部分了。

    Tailwind 的知识可以迁移到其他应用程序,即使它们使用的类名并不完全相同。这让我想起了 React 的「一次学习,到处编写」理念。

    我看到一些用户反馈说,Tailwind 提供的类名能覆盖他们 90% - 95% 的需求。这个覆盖面似乎已经足够广了,并不需要经常写一次性的 CSS 了。

    此时,你可能想知道为什么要使用原子 CSS 而不是 Tailwind CSS?强制执行原子 CSS 规则的一个规则,一个类名,有什么好处?你最终会得到更大的 html 标签和更烦人的命名约定吗?Tailwind 已经有了足够多的原子类了啊。

    那么,我们是否应该放弃原子 CSS 的想法,而仅仅使用 Tailwind CSS?

    Tailwind 是一个优秀的解决方案,但仍然有一些问题没有解决:

    • 需要学习一套主观的命名约定
    • CSS 规则插入顺序仍然很重要
    • 未使用的规则可以轻松删除吗?
    • 我们如何处理剩下的一次性样式?

    与 Tailwind 相比,手写原子 CSS 可能不是最方便的。

    和 CSS-in-JS 比较

    CSS-in-JS 和实用工具/原子 CSS 有密切关系。这两种方法都提倡使用标签进行样式化。以某种方式试图模仿内联样式,这让它们有了很多相似的特性(比如在移动某些功能的时候更有信心)。

    Christopher Chedeau 一直致力于推广 React 生态系统中 CSS-in-JS 理念。在很多次演讲中,他都解释了 CSS 的问题:

    1. 全局命名空间
    2. 依赖
    3. 无用代码消除
    4. 代码压缩
    5. 共享常量
    6. 非确定性(Non-Deterministic)解析
    7. 隔离

    实用工具/原子 CSS 也解决了其中的一些问题,但也确实没法解决所有问题(特别是样式的非确定性解析)。

    如果它们有很多相似之处,那我们能否同时使用它们呢?

    探索原子 CSS-in-JS

    原子 CSS-in-JS 可以被视为是“自动化的原子 CSS”:

    • 你不再需要创建一个 class 类名约定
    • 通用样式和一次性样式的处理方式是一样的
    • 能够提取页面所需要的的关键 CSS,并进行代码拆分
    • 有机会修复 JS 中 CSS 规则插入顺序的问题

    我想强调两个特定的解决方案,它们最近推动了两个大规模的原子 CSS-in-JS 的部署使用,来源于以下两个演讲。

    也可以看看这些库:

    • Styletron
    • Fela
    • Style-Sheet
    • cxs
    • otion
    • css-zero
    • ui-box
    • style9
    • stitches
    • catom

    React-Native-Web

    React-Native-Web 是一个非常有趣的库,让浏览器也可以渲染 React-Native 原语。不过我们这里并不讨论跨平台开发(演讲里有更多细节)。

    作为 web 开发人员,你只需要理解 React-Native-Web 是一个常规的 CSS-in-JS 库,它自带一些原始的 React 组件。所有你写 View 组件的地方,都可以用 div 替换。

    React-Native-Web 的作者是 Nicolas Gallagher,他致力于开发 Twitter 移动版。他们逐渐把它部署到移动设备上,不太确定具体时间,大概在 2017/2018 年左右。

    从那以后,很多公司都在用它(美国职业足球大联盟、Flipkart、Uber、纽约时报……),但最重要的一次部署,则是由 Paul Armstrong 领导的团队在 2019 年推出的新的 Twitter 桌面应用。

    Stylex

    Stylex 是一个新的 CSS-in-JS 库,Facebook 团队为了 2020 年的 Facebook 应用重构而开发它。未来可能会开源,有可能用另一个名字。

    值得一提的是,React-Native-Web 的作者 Nicolas Gallagher 被 Facebook 招安。所以里面出现一些熟悉的概念一点也不奇怪。

    我的所有信息都来自演讲 :),还需要等待更多的细节。

    可扩展性

    不出所料,在 Atomic CSS 的加成下,Twitter 和 Facebook 的 CSS体积都大幅减少了,现在它的增长遵循的是对数曲线。不过,简单的应用则会多了一些 初始体积

    Facebook 分享了具体数字:

    • 旧的网站仅仅首页就用了 413Kb 的 CSS
    • 新的网站整个站点只用了 74Kb,包括暗黑模式

    源码和输出

    这两个库的 API 看起来很相似,但也很难说,因为我们对 Stylex 了解不多。

    值得强调的是,React-Native-Web 会扩展 CSS 语法糖,比如 margin: 0,会被输出为 4 个方向的 margin 原子规则。

    以一个组件为例,来看看旧版传统 CSS 和新版原子 CSS 输出的区别。

    1
    <Component1 classNames="class1" /> <Component2 classNames="class2" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .class1 {
    background-color: mediumseagreen;
    cursor: default;
    margin-left: 0px;
    }
    .class2 {
    background-color: thistle;
    cursor: default;
    jusify-content: flex-start;
    margin-left: 0px;
    }

    可以看出这两个样式中 cursormargin-left 是一模一样的,但它在输出中都会占体积。

    再来看看原子 CSS 的输出:

    1
    2
    <Component1 classNames="classA classC classD" />
    <Component2 classNames="classA classB classD classE" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class a {
    cursor: default;
    }
    class b {
    background-color: mediumseagreen;
    }
    class C {
    background-color: thistle;
    }
    class D {
    margin-left: 0px;
    }
    class E {
    jusify-content: flex-start;
    }

    可以看出,虽然标签上的类名变多了,但是 CSS 的输出体积会随着功能的增多而减缓增长,因为出现过一次的 CSS Rule 就不会再重复出现了。

    生产环境验证

    我们看看 Twitter 上的标签是什么样子的:

    现在,让我们来看看新 Facebook:

    很多人可能会被吓到,但是其实它很好用,而且保持了 可访问性

    在 Chrome 里检查样式可能有点难,但 devtools 里就看得很清楚了:

    CSS 规则顺序

    与手写的工具/原子 CSS 不同,JS 库能让样式不依赖于 CSS 规则的插入顺序。

    在规则冲突的情况下,生效的不是标签上 class attribute 中的最后一个类,而是样式表中最后插入的规则。

    以这张图为例,我们期望的是书写在后blue 类覆盖前面的类,但实际上 CSS 会以样式表中的顺序来决定优先级,最后我们看到的是红色的文字。

    在实际场景中,这些库避免在同一个元素上写入多个规则冲突的类。它们会确保标签上书写在最后的类名生效。其他的被覆盖的类名则被规律掉,甚至压根不会出现在 DOM 上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const styles = pseudoLib.create({
    red: {color: "red"},
    blue: {color: "blue"},
    });

    // 只会输出 blue 相关的 CSS
    <div style={[styles.red, styles.blue]}>
    Always blue!
    </div>

    // 只会输出 red 相关的 CSS
    <div style={[styles.blue, styles.red]}>
    Always red!
    </div>

    注意:只有使用最严格的原子 CSS 库才能实现这种可预测的行为。

    如果一个类里有多个 CSS 规则,并且只有其中的一个 CSS 规则被覆盖,那么 CSS-in-JS 库没办法进行相关的过滤,这也是原子 CSS 的优势之一。

    如果一个类只有一个简单的 CSS 规则,如 margin: 0,而覆盖的是 marginTop: 10。像 margin: 0 这样的简写语法被扩展为 4 个不同的原子类,这个库就能更加轻松的过滤掉不该出现在 DOM 上的类名。

    仍然喜欢 Tailwind?

    只要你熟悉所有的 Tailwind 命名约定,你就可以很高效的完成 UI 编写。一旦你熟悉了这个设定,就很难回到手写每个 CSS 规则的时代了,就像你写 CSS-in-JS 那样。

    没什么能阻止你在原子 CSS-in-JS 的框架上建立你自己的抽象 CSS 规则,Styled-system 就能在 CSS-in-JS 库里完成一些类似的事情。它基于一些约定创造出一些原子规则,在 emotion 中使用它试试:

    1
    2
    3
    4
    import styled from '@emotion/styled';
    import { typography, space, color } from 'styled-system';

    const Box = styled('div')(typography, space, color);

    等效于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <Box
    fontSize={4}
    fontWeight="bold"
    p={3}
    mb={[4, 5]}
    color="white"
    bg="primary"
    >
    Hello
    </Box>

    甚至有可能在 JS 里复用一些 Tailwind 的命名约定,如果你喜欢的话。

    先看些 Tailwind 的代码:

    1
    <div className="absolute inset-0 p-4 bg-blue-500" />

    我们在谷歌上随便找一个方案,比如我刚刚发现 react-native-web-tailwindcss

    1
    2
    3
    import { t } from 'react-native-tailwindcss';

    <View style={[t.absolute, t.inset0, t.p4, t.bgBlue500]} />;

    就生产力的角度而言,并没有太大的不同。甚至可以用 TS 来避免错别字。

    结论

    这就是我要说的关于原子 CSS-in-JS 所有内容。

    我从来没有在任何大型生产部署中使用过原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能在某些方面是错的,请随时纠正我。

    我觉得在 React 生态系统中,原子 CSS-in-JS 是一个非常值得关注的趋势,我希望你能从这篇文章中学到一些有用的东西。

  • Javascript数据类型的检测

    作为 JavaScript 的入门级知识点,JS 数据类型在整个 JavaScript 的学习过程中其实尤为重要。因为在 JavaScript 编程中,我们经常会遇到边界数据类型条件判断问题,很多代码只有在某种特定的数据类型下,才能可靠地执行

    数据类型的基本概念

    数据类型包含undefined,null,Boolean,String,Number,Symbol,Biglnt,Object等八种,前 7 种类型为基础类型,最后 1 种(Object)为引用类型,也是需要重点关注的,因为它在日常工作中是使用得最频繁,也是需要关注最多技术细节的数据类型。 而引用数据类型(Object)又分为图上这几种常见的类型:Array - 数组对象、RegExp - 正则对象、Date - 日期对象、Math - 数学函数、Function - 函数对象。

    在这里,重点了解下面两点,因为各种 JavaScript 的数据类型最后都会在初始化之后放在不同的内存中,因此上面的数据类型大致可以分成两类来进行存储:

    • 基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量;
    • 引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念。

    js检测数据类型的方法有很多种,包括 typeof、instanceof、 Object.prototype.toString、constructor下面分别介绍下这几种方法

    第一种判断方法:typeof

    这是比较常用的一种,那么我们通过一段代码来快速回顾一下这个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typeof 1    // 'number'
    typeof '1' // 'string'
    typeof undefined // 'undefined'
    typeof true // 'boolean'
    typeof Symbol() // 'symbol'
    typeof null // 'object'
    typeof [] // 'object'
    typeof {} // 'object'
    typeof console // 'object'
    typeof console.log // 'function'

    注意

    • 该方法检测null会返回object,不代表 null 就是引用数据类型,并且 null 本身也不是对象,如果使用在判断语句中可以直接用”===null”来判断
    • 引用数据类型 Object,用 typeof 来判断的话,除了 function 会判断为 OK 以外,其余都是 ‘object’,是无法判断出来的

    第二种判断方法:instanceof

    instanceof 方法,我们 new 一个对象,那么这个新对象就是它原型链继承上面的对象了,通过 instanceof 我们能判断这个对象是否是之前那个构造函数生成的对象,这样就基本可以判断出这个新对象的数据类型

    1
    2
    3
    4
    5
    6
    7
    let Fn = function() {}
    let gs = new Fn()
    gs instanceof Fn // true
    let gcs = new String('Mercedes Gs')
    gcs instanceof String // true
    let str = 'string'
    str instanceof String // false

    其实自己也可以手写一个instanceof方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function newInstanceof(val, type) {
    // 这里先用typeof来判断基础数据类型,如果是,直接返回false
    if (typeof val !== 'object' || val === null) return false;
    // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(val);
    while (true) { //循环往下寻找,直到找到相同的原型对象
    if (proto === null) return false;
    if (proto === type.prototype) return true;//找到相同原型对象,返回true
    proto = Object.getPrototypeof(proto);
    }
    }
    // 验证newInstanceof方法是否有效
    console.log(newInstanceof(new Number(123), Number)); // true
    console.log(newInstanceof(123, Number)); // false

    注意

    • instanceof 可以准确地判断复杂引用数据类型,但不能正确判断基础数据类型

    以上两种方法单独使用都无法满足所有场景的判断,只有两者混写。下面看下第三种方法

    第三种判断方法:Object.prototype.toString

    toString() 是 Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call 来调用,才能返回正确的类型信息。还是先看代码吧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Object.prototype.toString({})       // "[object Object]"
    Object.prototype.toString.call({}) // 同上结果,加上call也ok
    Object.prototype.toString.call(1) // "[object Number]"
    Object.prototype.toString.call('1') // "[object String]"
    Object.prototype.toString.call(true) // "[object Boolean]"
    Object.prototype.toString.call(function(){}) // "[object Function]"
    Object.prototype.toString.call(null) //"[object Null]"
    Object.prototype.toString.call(undefined) //"[object Undefined]"
    Object.prototype.toString.call(/123/g) //"[object RegExp]"
    Object.prototype.toString.call(new Date()) //"[object Date]"
    Object.prototype.toString.call([]) //"[object Array]"
    Object.prototype.toString.call(document) //"[object HTMLDocument]"
    Object.prototype.toString.call(window) //"[object Window]"

    注意

    • 该方法可以很好的判断引用数据类型,包括浏览器窗口window和document
    • 该方法最后返回统一字符串格式为 “[object Xxx]” ,而这里字符串里面的 “Xxx” ,第一个首字母要大写(注意:使用 typeof 返回的是小写)

    我们可以封装一个工具函数用来检测数据类型

    1
    2
    3
    4
    5
    6
    7
    8
    function ifType(val){
    let type = typeof val;
    if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
    }
    // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
    return Object.prototype.toString.call(val).replace(/^\[object (\S+)\]$/, '$1');
    }

    第四种判断方法:constructor

    constructor在其对应对象的原型下面,是自动生成的。当我们写一个构造函数的时候,程序会自动添加:构造函数名.prototype.constructor = 构造函数名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var str = 'hello';
    alert(str.constructor == String);//true
    var bool = true;
    alert(bool.constructor == Boolean);//true
    var num = 123;
    alert(num.constructor == Number);//true
    var nul = null;
    alert(nul.constructor == Object);//报错
    var und = undefined;
    alert(und.constructor == Object);//报错
    var oDate = new Date();
    alert(oDate.constructor == Date);//true
    var json = {};
    alert(json.constructor == Object);//true
    var arr = [];
    alert(arr.constructor == Array);//true
    var reg = /a/;
    alert(reg.constructor == RegExp);//true
    var fun = function () { };
    alert(fun.constructor == Function);//true
    var error = new Error();
    alert(error.constructor == Error);//true

    注意

    • null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断
    • 函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object
    • 在重写对象原型时一般都需要重新给constructor赋值,以保证对象实例的类型不被篡改

    总结

    • typeof 可以判断基本数据类型null除外,引用数据类型除了function,其余也不能准确判断

    • instanceof 可以准确地判断复杂引用数据类型,但不能正确判断基础数据类型

    • Object.prototype.toString 很好的判断引用数据类型,包括浏览器窗口window和document

    • constructor 是不稳定的,开发者一旦重写prototype原有的constructor引用会丢失,需要重新给constructor赋值。个人不推荐使用

  • Vue中怎么使用Typescript

    之前一直听说Vue+Typescript体验有多好,这次来记录一下步骤以及过程

    这边是结合vue-property-decorator使用ts。

    使用ts的vue项目,和js项目结构是类似的,多了几个文件

    • tsconfig.json: typescript配置文件,主要用于指定待编译的文件和定义编译选项
    • shims-tsx.d.ts: 允许.tsx 结尾的文件,可在 Vue 项目中编写 jsx 代码
    • shims-vue.d.ts: 主要用于 TypeScript 识别.vue 文件,Ts 默认并不支持导入 vue 文件

    配合ts使用的库

    • vue-class-component: 类的装饰器
    • vue-property-decorator: 基于 vue 组织里 vue-class-component 所做的拓展import { Vue, Component, Inject, Provide, Prop, Model, Watch, Emit, Mixins } from 'vue-property-decorator'
    • vuex-module-decorators: 用 typescript 写 vuex 很好用的一个库import { Module, VuexModule, Mutation, Action, MutationAction, getModule } from 'vuex-module-decorators'

    组件声明

    组件声明乍一看有点复杂,以前就是export default {name:'Home',data:...}

    现在必须拆分成以下这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { Component, Vue } from "vue-property-decorator";
    import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src

    @Component({
    name:'Home'
    components: {
    HelloWorld
    }
    })
    export default class Home extends Vue {}

    data对象

    data在ts里是直接展开属性的:

    1
    2
    3
    4
    // 可以在上面使用{{name}}
    export default class Home extends Vue {
    private name = "哈哈哈";
    }

    props声明

    使用@props

    1
    2
    3
    4
    export default class HelloWorld extends Vue {
    // <hello-world msg="颜酱">
    @Prop({ default: "" }) private msg!: string;
    }
    • !: 表示一定存在,?: 表示可能不存在。这两种在语法上叫赋值断言
    • @Prop(options: (PropOptions | Constructor[] | Constructor) = {})
      • PropOptions,可以使用以下选项:type,default,required,validator
      • Constructor[],指定 prop 的可选类型
      • Constructor,例如 String,Number,Boolean 等,指定 prop 的类型

    methods

    直接展开写就行

    1
    2
    3
    4
    5
    6
    7
    export default class HelloWorld extends Vue {
    // methods <div @click="clickName">
    public clickName(): void {
    console.log(this.name);
    console.log(this.msg);
    }
    }

    watch

    watch也是分两块:

    1
    2
    3
    4
    5
    6
    7
    export default class HelloWorld extends Vue {
    // watch,函数和参数分为两部分,函数名必须是on[key]Change
    @Watch("name", { immediate: true, deep: true })
    private onNameChange(newValue: string, oldValue: string) {
    console.log("watch", newValue, oldValue);
    }
    }
    • @Watch(path: string, options: WatchOptions = {})
    • options 包含两个属性 immediate?:boolean 侦听开始之后是否立即调用该回调函数 / deep?:boolean 被侦听的对象的属性被改变时,是否调用该回调函数
    • @Watch(‘arr’, { immediate: true, deep: true }) onArrChanged(newValue: number[], oldValue: number[]) {}

    computed

    computed直接展开,就是普通方法加个get

    1
    2
    3
    4
    5
    6
    export default class HelloWorld extends Vue {
    // computed 就是普通方法前面加个get,表示此属性是计算来的 <div>{{allname}}</div>
    public get allname() {
    return "computed " + this.name;
    }
    }

    生命周期函数

    生命周期函数就是普通函数的感觉

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export default class HelloWorld extends Vue {
    // created生命周期函数
    public created(): void {
    console.log("created");
    }
    // mounted生命周期函数
    public mounted(): void {
    console.log("mounted");
    }
    }

    emit

    emit稍微绕了点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    export default class HelloWorld extends Vue {

    /* 以下相当于js时候的
    clickMsg(){
    this.name='xx';
    this.$emit('change-msg','ddd')
    }
    当前组件 <div @click="clickMsg">
    父组件 <hello-world :msg="msg" @change-msg="handleChangeMsg">
    handleChangeMsg(value){
    this.msg = value
    }

    */
    @Emit("change-msg")
    clickMsg() {
    // 组件本身的逻辑
    this.name = "点击msg 之后 可以修改名字";
    // emit的第二个参数,也就是给父组件的值
    return "ddd";
    }
    }
    • @Emit(event?: string)
    • @Emit 装饰器接收一个可选参数,该参数是emit的第一个参数,充当事件名,没有提供这个参数的话,Emit 会将回调函数名的 camelCase 转为 kebab-case,并将其作为事件名
    • @Emit 会将回调函数的返回值作为第二个参数,如果返回值是一个 Promise 对象,$emit 会在 Promise 对象被标记为 resolved 之后触发
    • @Emit 的回调函数的参数,会放在其返回值之后,一起被$emit 当做参数使用
  • HTTP相关网络知识点汇总

    HTTP到底是什么

    HTTP 是一种超文本传输协议(Hypertext Transfer Protocol),超文本传输协议该怎么理解是本文要讲的第一个问题,下面对 超文本、传输、协议 这三个名词做一下解释。

    超文本

    文本比较好理解就是字符串嘛,在计算机刚刚发明的时代网络通信传输的就只是字符串,后面随着技术的发展我们不满足仅限字符串的传输通信,还涉及到 图片、音视频、排版(CSS)、交互行为(JS)等资源的传输。之前传输的文本逐渐丰富起来,这个时候文本的语义就扩大了,我们把语义扩大后的文本称为超文本(Hypertext)。

    传输

    把上面提到过的超文本解析成二进制的数据包,通过传输载体比如网线等从一台设备传输到另一台设备的过程叫做传输。

    协议

    计算机之间相互通信需要遵守一定的规则,规则中定义了请求端如何传递参数,响应端如何解析数据,这个就是网络协议。

    现在我们知道了 http(超文本传输协议)就是在多台网络设备之间传输 文字、图片、音视频 等超文本内容的具体规范和约定。 网络协议除了http之外,常见的还有 电子邮件传输协议 SMTP、文件上传协议 FTP、域名解析协议 DNS 。

    一个HTTP请求是怎么完成通信的

    常见的场景是客户端在本地发起请求,服务端接收请求响应数据,那这个过程中具体需要哪些机制配合协作呢,HTTP是一个网络通讯协议,除了HTTP之外你肯定还听过TCP/IP协议,TCP/IP是HTTP通信不可或缺的一部分。

    HTTP 和 TCP/IP 的关系

    为什么说TCP/IP是HTTP通信不可或缺的一部分,因为HTTP协议本身并不具备数据传输的能力,HTTP主要负责客户端和服务端之间请求应答的标准定义(比如报文),而数据传输依赖TCP/IP协议,所以理解HTTP要先从TCP/IP开始。

    TCP/IP

    TCP/IP(Transmission Control Protocol/Internet Protocol 传输控制协议/网际协议)主要作用是在不同网络之间实现信息数据的传输,我们虽然称之为TCP/IP协议,但它实际上是一个协议族,包含的不仅仅是 TCP协议 和 IP协议,还有 UDP、ICMP、ARP 等,TCP、IP协议是这里面最核心的两个协议所以称谓用它们指代。

    TCP/IP的核心思想是分层管理,分为四层分别是 应用层、传输层、网络层和链路层。分层设计的好处是每一层都只负责自己的任务,不需要考虑其他层的任务,各层交互的时候只需要相互调用接口即可,当然也有劣势每次进行网络通信的时候都需要由下至上一层一层的传递信息,这对性能是有一定影响的。

    • 应用层:加密、解密、数据格式化、域名解析,面向业务。HTTP、FTP、DNS
    • 传输层:为两台计算机之间提供相互传输数据的能力。TCP、UDP
    • 网络层:进行网络连接的建立和终止,以及IP地址的寻找。IP、ICMP、IPV6
    • 链路层:用于处理网络连接的硬件部分。

    HTTP

    HTTP是TCP/IP应用层的协议,HTTP主要负责客户端和服务端之间请求应答的标准定义(比如报文)。

    FTP

    FTP是TCP/IP应用层的协议,FTP是文件传输协议,主要功能用于实现用户间文件分发共享。

    DNS

    我们通过IP可以定位带一台设备,当我们打开百度的IP地址http://202.108.22.5/就打开了百度的首页,但是这个IP地址无疑是反人类的很难记,这就有了域名,所以我们打开http://www.baidu.com/也能打开百度的首页。

    域名到IP的解析就是 DNS(Domain Name System)做的事情了,它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。

    DNS协议是用来将域名转换为IP地址的,也可以将IP地址转换为相应的域名地址。目前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。

    TCP

    TCP(传输控制协议)是TCP/IP传输层的协议,TCP提供可靠的字节流传输能力,“字节流”是指,为了方便传输,将大块数据分割成以报文段为单位的数据包进行传输管理。而“可靠”是指,TCP协议为保证数据可靠的传输采用了三次握手策略。TCP协议把数据包送出去后,不会对传送后的情况置之不理,它一定会向对方确认是否成功送达,如有异常会启动重发机制。HTTP就是使用TCP提供的传输能力进行通信。

    UDP

    UDP(用户数据报协议)是TCP/IP传输层的协议,UDP也提供了数据传输能力,UDP协议定义了端口,当数据包到达主机以后,就可以根据端口号找到对应的应用程序了,比较简单,实现容易,但它没有确认机制,数据包一旦发出,无法知道对方是否收到,因此可靠性较差。UDP和TCP很相似,但是更简单,同时可靠性低于TCP,UDP的优点是快速、资源消耗少。

    换句话说UDP只管发,不管对方收没收到,像直播、视频聊天这种就可以用UDP,因为这种需求对延时性要求很高,如果用TCP来做视频聊天,网络不好丢包严重时,那TCP就会不断重发,这样就会导致看到的画面可能还是对方一分钟之前的发送的,如果用UDP来做,网络不好时丢包就丢包了,丢包它也不会重发,而是一直发送最新的数据,所以就能保住我看到的画面都是实时的画面。

    UDP还有一个应用场景是DNS解析,DNS解析也使用UDP协议,因为UDP协议只要一个请求、一个应答就好了,而使用TCP协议要三次握手四次挥手更加浪费网络资源,基于数据层面考虑,DNS数据包并不是那种大数据包,所以使用UDP不需要考虑分包,如果丢包那么就是全部丢包,如果收到了数据,那就是收到了全部数据,就算是丢包了重新请求一次就好了。

    IP

    IP是TCP/IP网络层的协议,IP协议规定使用IP地址来定位互联网上每一台唯一的设备,再利用 ARP 协议获取中转设备的 MAC 地址,以此解决寻址相关问题。

    IPV6

    IPV4的地址长度为4个8位字节,IPV4地址迟早会有枯竭的一天,为了解决这个问题,开始了IPV6的研发,IPv6的地址长度则是原来的4倍,一般写成8个16位字节,IPV6的地址是取之不尽,用之不竭的。

    TCP与UDP

    当客户端希望与服务器通过TCP建立连接时,首先会发送一个通信请求,这个请求必须被送到一个明确的地址,并收到回复,在双方完成“握手”过程后,TCP连接建立,直到其中一端关闭再次“握手”断开连接,这就是“三次握手和四次挥手”,而UDP则没有握手和挥手的机制,这也是TCP更加可靠的原因。

    TCP三次握手

    握手过程中使用了TCP的标志 SYN 和 ACK,发送端首先发送一个带有 SYN 标志的数据包给对方,接收端收到后,回传一个带有 SYN/ACK 标志的数据包以示传达确认信息,最后发送端再回传一个带 ACK 标志的确认包,代表握手结束。若在握手过程某个阶段中断,TCP 协议会再次以相同的顺秀发送相同的数据包。

    • 第一次握手:客户端发送SYN报文到服务器,并进入SYN已发送状态,服务器接收到SYN报文。(服务器确认了客户端发送正常,自己接收正常)
    • 第二次握手:服务器发送SYN+ACK报文到客户端,并进入ACK已发送状态,客户端收到SYN+ACK报文。(客户端确认了服务器接收正常、发送正常,自己接收正常发送正常)
    • 第三次握手:客户端向服务器发送确认报文ACK,服务端接收到确认报文ACK,连接成功建立。(服务端确认了客户端接收正常、发送正常,自己接收正常发送正常)

    TCP四次挥手

    • 第一次挥手:当客户端的数据都传输完成后,客户端向服务端发出包含FIN标志位的连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),需要注意的是客户端发出FIN后就不能发数据了,但是还可以正常收数据。(客户端告诉服务端我的数据发完了)
    • 第二次挥手:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。(服务端告诉客户端你的断开请求我收到了)
    • 第三次挥手:服务端将最后的数据发送完毕后向客户端发出连接释放报文,报文包含FIN和ACK标志位,用来关闭服务器端到客户端的数据传送,也就是告诉客户端,我的数据也发送完了,此时服务端处于断开等待状态。(服务端告诉客户端我的数据也发完了)
    • 第四次挥手:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位,客户端发出确认报文后不是立马释放TCP连接而是要经过2MSL(最长报文段寿命的2倍时长),服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。

    为什么握手是三次不是两次

    三次握手的作用是让服务端和客户端双方明确彼此的发送能力和接收能力都是正常的,不会造成数据丢失保证信息传递的可靠性。而解决这个问题,三次通信是理论上的最小值。 若只有两次握手,第二次握手时服务器给客户端的确认报文如果丢失(客户端接收数据能力异常),此时客户端一直处在等待状态,它并不清楚问题出在哪里是自己第一次握手的报文没有送达服务端还是服务端的响应报文丢失了,导致客户端无法给服务端发送数据。如果是三次握手,即便发生数据丢失也不会有问题,比如第三次握手客户端发的确认报文丢失,服务端在一段时间内没有收到确认报文就会重新进行第二次握手,客户端收到重发的报文后会再次给服务端发送确认报文。

    为什么挥手是四次不是三次(为什么握手是三次挥手是四次)

    因为只有在客户端和服务端都没有数据发送的时候才能断开连接,客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发给客户端是不知道的。所以服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端服务端已经收到你的FIN报文了,但服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端)。

    为什么客户端发出第四次挥手的确认报文后不会立刻释放TCP连接

    这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ACK报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以客户端需要等这么长时间来确认服务端确实已经收到了。

    已经建立了连接如果客户端突然出现故障了怎么办

    TCP有一个计时器机制,服务端每次接收到客户端的请求都会复位这个计时器,当一定时间没有收到客户端的信息服务端就会发送一个探测报文,如果依然没有响应之后每隔75s重新发送一次,如果连着10个探测报文没有响应服务器就认为客户端出现了故障,会主动关闭连接。

    当我们在浏览器输入url后发生了什么

    我们以http://www.baidu.com/index.html为例,来分析一下浏览器地址栏输入URL之后发生了什么,这里只分析和网络请求相关的内容,大量页面渲染的东西先不涉及。

    • DNS服务器进行域名解析(先查询缓存以及本地hosts)找到www.baidu.com所射映的IP地址,然后在客户端发起一个到服务器的TCP连接(三次握手)。
    • 客户端通过TCP连接向服务器发送HTTP请求报文,该报文中包含请求资源路径/index.html,以及其他参数。
    • 服务器接收请求报文,进行请求解析工作,完成数据库查询、磁盘资源读取等,把这些内容封装到HTTP响应报文中并向客户端发送。
    • HTTP客户端接收到响应报文后TCP连接关闭(四次挥手)。
    • 浏览器渲染内容。

    HTTP的特点

    • 支持客户/服务器模式

      HTTP工作于客户端服务端的架构之上,浏览器作为客户端通过url向web服务器发送请求,web服务器根据接收到的请求向客户端发送响应信息。

    • 简单快速

      客户端向服务器请求时,只需传送请求方法和路径。请求方法常用的有 GET、HEAD、POST,每种方法客户端与服务器通信的特征不同。由于HTTP协议的简单,使得HTTP服务器的程序规模小,因而通信速度很快,并且HTTP协议使用明文传输开发者不需要借助第三方工具就可以研测。 HTTP协议主要的组成部分是header + body,头部信息就是简单的语义化多行文本,在这基础上 请求头、状态码 等又支持开发者自定义,做到了足够灵活,只要服务端和客户端就请求头字段达成语义一致,新功能就可以被轻松加入进来。

    • 灵活

      HTTP允许传输任意类型的数据对象,具体的传输类型由Content-Type值确定,可以是 图片(image/png image/gif)、JSON(application/json)、HTML(text/html )等。因为 HTTP 对语言以及平台不做限制,所以有跨语言跨平台的优越性,又因为其本身的简单灵活几乎所有的语言都有HTTP相关的研测方案。

    • 无连接

      无连接的含义是限制每次连接只处理一个请求,服务器处理完客户端的请求,并收到客户端的应答后即断开连接,采用这种方式可以节约资源。

      早期这么做的原因是 HTTP 协议产生于互联网,因为服务器需要处理同时面向全世界数十万、百万客户端的网页访问,但每个客户端(即浏览器)与服务器之间交换数据的间歇性较大,并且网页浏览的联想性、发散性导致两次传送的数据关联性很低,大部分通道实际上处于空闲状态、导致占用资源。因此 HTTP 的设计者有意利用这种特点将协议设计为请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端。

      随着时间的推移,网页变得越来越复杂,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次 TCP 连接就显得很低效,后来 Keep-Alive(HTTP1.1) 被提出用来解决效率低的问题。Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了重新建立连接。

      我们始终都要认为HTTP请求在结束后连接就会关闭,这是HTTP的特性,因为上层实现在结束后是否关闭连接都不会改变这个特性,这和WebSocket长连接有本质的区别。

    • 无状态

      HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,它不会对请求和响应的状态做持久化储存,每个请求都是独立的,如果后续请求需要前面的信息,则它必须重传,无法支持需要连续多个步骤的事务操作,这样可能导致每次连接传送的数据量增大。另一方面因为服务器不需要管理状态所以它的应答是较快的。

      这种特性有优点也有缺点,优点在于解放了服务器,每一次请求“点到为止”不会造成不必要连接占用,缺点在于每次请求会传输大量重复的内容信息。

      客户端与服务器进行动态交互的Web应用程序(webapp)出现之后,HTTP无状态的特性严重阻碍了这些webapp的实现,毕竟交互是需要承前启后的,于是两种用于保持 HTTP 连接状态的技术就应运而生了,一个是 Cookie,而另一个则是 Session。

      Cookie可以保持登录信息到用户下次与服务器的会话,换句话说,下次访问同一网站时,用户会发现不必输入用户名和密码就已经登录了(当然,不排除用户手工删除Cookie)。而还有一些Cookie在用户退出会话的时候就被删除了,这样可以有效保护个人隐私。

      Session可以理解是服务端的Cookie,当客户端访问服务器时,服务器根据需求设置 Session,将会话信息保存在服务器上,同时将 Session 的 SessionId 传递给客户端浏览器,浏览器将这个 SessionId 保存在内存中,以后浏览器每次请求都会额外加上这个参数值,服务器根据这个 SessionId,就能取得客户端的数据信息。浏览器关闭后这个 SessionId 就会被清掉,它不会存在于用户的 Cookie 临时文件。如果客户端浏览器意外关闭,服务器保存的 Session 数据不是立即释放,此时数据还存在,只要我们知道那个 SessionId,就可以继续通过请求获得此 Session 的信息,因为此时后台的 Session 还存在,当然我们可以设置一个 Session 超时时间,一旦超过规定时间没有客户端请求时,服务器就会清除对应 SessionId 的 Session 信息。

    • 明文

      HTTP协议还有一个特点是明文传输,明文是指在传输阶段报文不使用二进制数据,而是使用可直接阅读的文本形式,相对 TCP 这样的二进制协议优点是可以不使用外部工具直接抓包(浏览器的network)调试,缺点是不安全,可以被监听和篡改。

    报文

    什么是报文

    报文就是HTTP协议交互中传递的信息,就是一堆参数和声明,报文本身是多行结构的字符串文本,客户端发送给服务端的叫请求报文,服务端下发给客户端的叫响应报文。报文由四部分组成分别是 起始行、头部字段、空行、主体,在请求报文中起始行又叫请求行,在响应报文中起始行又叫状态行。

    请求报文

    请求报文由四部分构成分别是 请求行、头部字段、空行、主体,其中 请求行、头部字段 合起来就是我们常说的请求头(Req Header),http协议规定每次请求必须发送请求头,请求主体(body)则可有可无,而空行就是用来分割请求头和请求体的。

    • 请求行 请求行说明了本次请求要做什么,在请求行中定义了 请求方法、请求地址、HTTP版本。

    • 头部字段 头部字段以 key-value 的形式存在,最后以换行结束。头部字段是可扩展的,可以自定义新的字段名称,名称大小写均可,通常情况下只大写首字母。如果定义了重复的字段名称只会保留一个,值会被后面的覆盖。HTTP协议并没有对头部字段的整体长度做限制,但是在服务端实践中长度超过某一个额定值4K或8K处于安全考虑会主动抛出一个错误。
      头部字段的标头分为四种分别是 通用标头、请求标头、响应标头 和 实体标头,这个先放在后面讲。

    • 空行,分割请求头和请求体,告诉服务端下面的内容是请求体。

    • 主体

    响应报文

    响应报文由四部分组成分别是 状态行、头部字段、空行、主体,其中 状态行、头部字段 合起来就是响应头(Res Header)。

    • 状态行

      状态行说明本次请求的结果是什么,在状态行中定义了 HTTP版本、状态码、以及“短语消息”,短语消息为状态码提供了文本形式的解释,状态码和短语消息是一一对应的,比如状态码200就对应着OK。

    • 头部字段

      参考请求报文的头部字段

    • 空行

      分割响应头和响应体,告诉客户端下面的内容是响应体。

    • 主体

    头部字段

    头部字段分为以下五类,分别是 通用头部、请求头部、响应头部、实体头部、扩展头部

    • 通用头部: 在请求头和响应头中都适用,比如 Content-Type 字段。

      头部字段key 可选值 说明
      Connection keep-alive、close 是否持久性连接,决定当前事务(一次三次握手和四次挥手)完成后,是否会关闭网络连接。
      Date 表示报文创建时间
      Transfer-Encoding chunked 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
      Cache-Control 控制缓存行为
      Via 代理服务器相关信息
    • 请求头部: 只能出现在请求报文中,如 Origin 字段。

      头部字段key 可选值 说明
      Host 接收请求的服务端的域名(或者IP)和端口
      User-Agent 客户端信息,web常见的是浏览器内核以及版本
      Accept application/json 等 告诉服务端期望接收的数据类型
      Accept-Language zh-CN 等 告诉服务端期望接收的语言类型
      Accept-Encoding gzip 告诉服务端期望接收的编码方式
      Accept-Charset 告诉服务端期望接收的字符集
      Authorization 客户端提供给服务器的验证数据,客户端token通常会写到Authorization字段里。
      Cookie 携带Cookie
      Referer 客户端发起请求的地址
    • 响应头部: 只能出现在响应报文中,如 Server 字段。

      头部字段key 可选值 说明
      Set-Cookie 服务器主动在客户端设置一个标识令牌,会自动写入Cookie。
      Access-Control-Allow-Origin 指定域名 或 * 告诉客户端允许那些来源的域进行资源访问
      Keep-Alive timeout=8, max=3 timeout:保持持续连接状态的最短时间,max:单个连接的最大请求数。
      Server 服务端信息
    • 实体头部: 用来描述报文主体的头部字段,比如 Content-Length 字段,要注意的是请求和响应报文中都有可能包含实体部分,所以这两种类型的报文都有可能出现实体头部。

      头部字段key 可选值 说明
      Content-Type application/json 发送的主体对象(body)类型
      Content-Encoding gzip 发送的编码
      Content-Language zh-CN 发送的语言
      Content-Length 100{Number} 发送实体的长度,以字节为单位。
    • 扩展头部: 并非规定的标准头部字段,是开发者自行定义的头部字段。

    Content-Type 和 Accept 的区别

    application/json 表示json数据格式,同时是 Content-Type 和 Accept 字段的值,他俩的区别在于 Content-Type 是实体头部,会出现在请求头和响应头表示当前传输的数据体是json格式;Accept 是请求头部,表示本次请求客户端期望服务端返回json格式数据。

    请求url

    http://www.baidu.com:80/assets/public/index.html?a=1&b+2#/path/main 为例来分析一下请求url的组成:

    http://: 告知浏览器当前使用的版本,大部分情况是HTTP协议,或者更为安全的HTTPS协议。

    www.baidu.com: 域名用来定位到某一台机器,向这台机器发起请求,当然域名可以替换成IP地址,但是直接使用IP在生产环境并不常见。

    :80: 端口也是必须的,IP用来定位到机器,而端口是用来确定向该机器上那个server发起请求。HTTP默认端口为80,HTTPS为443,80和443可以省略不写其他端口必须要在请求地址上体现出来。

    /assets/public/index.html: 资源路径以端口后面的第一个/开始,到?号结束,中间的/代表了层级关系。

    ?a=1&b+2: 从 ? 到 # 之间的是参数,GET请求的参数会直接带在URL上,POST请求参数则在请求体中。

    #/path/main: #开始为前端的路由,路由改变页面不会刷新,以此实现前端的单页面应用,路由是不会传到服务端的。

    状态码

    响应报文的状态行声明了本次请求的状态码,不同状态码表示的含义如下。

    状态码 说明
    1xx 表示请求已经接收,正在继续处理。
    200 成功响应
    204 请求处理成功,但是没有资源可以返回
    206 对资源某一部分进行响应,由Content-Range 指定范围的实体内容。
    301 永久性重定向,请求的资源已经重新分配 URI,以后应该使用资源现有的 URI
    302 临时性重定向。请求的资源已被分配了新的 URI,希望用户(本次)能使用新的 URI 访问。
    303 由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。
    304 客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况。
    307 临时重定向。该状态码与 302 Found 有着相同的含义。
    400 请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。
    401 请求未经授权
    403 该状态码表明对请求资源的访问被服务器拒绝了。
    404 该状态码表明服务器上无法找到请求的资源。
    500 该状态码表明服务器端在执行请求时发生了错误。
    503 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

    总的来说 1xx 是进行中,2xx 是成功,3xx 是重定向,4xx 是客户端错误,5xx 是服务端错误。

    请求方法

    常用请求方法

    • GET,从服务器上获取资源,无主体。
    • POST,向服务器发送数据(常见的场景是表单提交)有主体。
    • DELETE,从服务器上删除资源,无主体。
    • PUT,更新资源,有主体。
    • HEAD,获得响应首部,HEAD 方法和 GET 方法一样,只是不返回报文主体部分。用于确认URI有效性及资源更新时间等。
    • OPTIONS,前置请求,询问支持的方法。
    • TRACE,追踪路径,TRACE 方法是让 Web 服务器端将之前的请求通信环回给客户端的方法。
    • CONNECT,用隧道协议连接代理,CONNECT方法要求在与代理服务器通信时建立隧道,实现用隧道协议进行 TCP 通信。主要用于 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。

    GET 和 POST 的区别

    上面的请求方法常用的只有GET和POST,其他方法基本用不到,在工作中更新和删除并不会特意用DELETE和PUT方法,而是统一用POST来解决。POST和GET在使用上有一些区别我们先直接给出来。

    • 常规在数据获取(查)时用GET方式,在数据提交(增删改)时使用POST方式。
    • 相对GET而言POST安全性更好,因为参数在地址栏直接不可见。(HTTP本身是明文传输POST抓包参数也可查看)
    • GET在URL中的参数有长度限制2kb(实测了一下好像没限制),而POST没有此限制可用来传输文件。
    • GET参数通过URL传递,POST放在RequestBody中。(在web中GET请求本身没有ReqBody)
    • POST方法通过RequestBody可以传递json数据更加语义化。(对有层级的数据用json描述体验更好)
    • GET请求性能更好速度更快
    • GET请求是幂等的

    当直接在地址栏(非ajax)访问GET地址时还有下面这些特点:

    • GET在浏览器回退时是无害的(被缓存),而POST会再次提交请求。
    • GET产生的URL地址可以被浏览器收藏,而POST不可以。
    • GET请求会被浏览器主动缓存,而POST不会,除非手动设置。
    • GET请求只能进行URL编码,而POST支持多种编码方式。
    • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

    这些特点其实只在web领域适用,如果说的更贴切一些就是只在浏览器适用,HTTP不只是在浏览器端使用,抛开web上面的几条区别就需要重新理解了。GET和POST是HTTP定义的Method,HTTP传输依赖TCP,在TCP这一层并没有GET和POST相关的标准,所以GET和POST本质上只是语义和规范上的区别。

    GET在url中传参且长度小于2kb POST通过ReqBody传参

    GET在url中传参且长度小于2kb(实测了一下好像没限制)在浏览器中是没问题的,因为浏览器厂商为了符合规范和语义对GET请求做了限制只允许通过url传参,至于2kb的限制是因为浏览器为了避免url过长影响传输效率对地址栏做了限制。上文有提到过url上除了#号之后的路由其他部分都可以传到服务端,当然也包括?号后面的query参数,这和请求方法无关,数据既然可以抵达服务端那只要做了解析就可以读取,所以POST请求通过url也可以传参。我们在讨论GET和POST时不应该只局限在浏览器(HTTP是跨平台的),抛开浏览器GET请求其实也可以用ReqBody来传参,下面截图通过node+axios做了演示。结论就是GET请求虽然可以通过ReqBody传参,但是规范上并不推荐这么做,各个浏览器厂商、各种服务器、第三方框架针对该规范可能会做不同的实现,不保证可用性与稳定性。但在技术上GET和POST都是基于TCP的传输实现,没有本质区别,完全可以实现GET通过ReqBody传参,POST通过url传参。

    GET获取数据POST提交数据

    既然GET和POST用URL和ReqBody都可以传参,那么GET获取数据POST提交数据的设定也不是必须的,如果你愿意完全可以反过来用,差别只在语义上。

    相对GET而言POST安全性更好

    这里的安全只是因为POST请求参数在URL上不可见,但说到安全往往和攻防挂钩,HTTP本身是明文传输POST请求随便抓个包或者在network也可以看到详细的参数。

    GET请求性能更好速度更快

    GET在请求时只发送一个数据包,会将header和data一起发送到服务端,而POST会发送两个数据包先发送header,服务器返回100,然后再发送data,服务器返回200。返回状态码100这个过程在浏览器network是不可见的,通过 (new XMLHttpRequest()).onreadystatechange = e => console.log(ajax.status) 可以看到100的过程。GET只发一个数据包以及对url长度的限制,所以性能好与POST。(GET只发一个数据包和浏览器实现也有关系部分浏览器也会和POST一样发两个包)

    GET请求是幂等的

    幂等是指多次请求同一个接口返回数据应该保持一致(不改变服务器状态),可以重复发送请求,POST增加数据时重复发送会带来不可预料的后果。

    如何优化HTTP请求

    web性能优化最主要的就是要减少HTTP请求及每次响应中内容的长度:

    • 域名解析:尽可能减少域名解析次数—–减少跨站、外部资源的引用
    • 创建连接:减少连接创建次数—–使用Keep-Alive避免重复连接
    • 发送请求:尽力减少请求次数—–合理设置Expires时间、资源合并
    • 等待响应:提高服务器端运行速度—–提高数据运算及查询速度
    • 接收响应:尽可能减少响应数据长度—–启用压缩

    CDN

    CDN的全称是Content Delivery Network,即内容分发网络,它应用了 HTTP 协议里的缓存和代理技术,代替源站响应客户端的请求。CDN 是构建在现有网络基础之上的网络,它依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

    HTTTPS

    HTTP 是明文传输,很容易被攻击者窃取重要信息,鉴于此,HTTPS 应运而生。HTTPS 的全称为 (Hyper Text Transfer Protocol over SecureSocket Layer),HTTPS 和 HTTP 有很大的不同在于 HTTPS 是以安全为目标的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在 HTTP 的基础上增加了 SSL 层,也就是说 HTTPS = HTTP + SSL。

  • 那些你总是记不住但又总是要用的css

    1. Input样式

    设置input的placeholder的字体样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    input::-webkit-input-placeholder {    /* Chrome/Opera/Safari */
    color: red;
    }
    input::-moz-placeholder { /* Firefox 19+ */
    color: red;
    }
    input:-ms-input-placeholder { /* IE 10+ */
    color: red;
    }
    input:-moz-placeholder { /* Firefox 18- */
    color: red;
    }

    设置input聚焦时的样式

    1
    2
    3
    input:focus {   
    background-color: red;
    }

    取消input的边框

    1
    2
    border: none;
    outline: none;

    2. 隐藏滚动条或更改滚动条样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /*css主要部分的样式*//*定义滚动条宽高及背景,宽高分别对应横竖滚动条的尺寸*/
    ::-webkit-scrollbar {
    width: 10px; /*对垂直流动条有效*/
    height: 10px; /*对水平流动条有效*/
    }

    /*定义滚动条的轨道颜色、内阴影及圆角*/
    ::-webkit-scrollbar-track{
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: rosybrown;
    border-radius: 3px;
    }

    /*定义滑块颜色、内阴影及圆角*/
    ::-webkit-scrollbar-thumb{
    border-radius: 7px;
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: #E8E8E8;
    }

    /*定义两端按钮的样式*/
    ::-webkit-scrollbar-button {
    background-color:cyan;
    }

    /*定义右下角汇合处的样式*/
    ::-webkit-scrollbar-corner {
    background:khaki;
    }

    3. 文字超出隐藏并显示省略号

    单行(一定要有宽度)

    1
    2
    3
    4
    width:200rpx;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;

    多行

    1
    2
    3
    4
    5
    word-break: break-all;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;

    4. 控制div内的元素自动换行

    1
    2
    word-wrap: break-word;
    word-break:break-all;

    5. 纯css画三角形

    1
    2
    3
    4
    5
    6
    7
    #demo {
    width: 0;
    height: 0;
    border-width: 20px;
    border-style: solid;
    border-color: transparent transparent red transparent;
    }

    6. 绝对定位元素居中(水平和垂直方向)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #demo {
    width: 200px;
    height: 200px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
    background-color: green;
    }

    7. 表格边框合并

    1
    2
    3
    4
    table,tr,td{border: 1px solid #333;}
    table{
    border-collapse: collapse;
    }
  • 移动端适配方案之postcss-px-to-viewport

    之前做移动端适配时,基本上是采用rem方案,现在发现了一个新的方案,就是用viewport单位,现在viewport单位越来越受到众多浏览器的支持

    postcss-px-to-viewport,将px单位自动转换成viewport单位,用起来超级简单,postcss-px-to-viewport 文档

    安装

    npm install postcss-px-to-viewport *--save-dev*

    引入vue项目,再postcss.config.js引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    module.exports = {
    plugins: {
    autoprefixer: {},
    'postcss-px-to-viewport': {
    viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
    viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置
    unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数
    viewportUnit: "vw", //指定需要转换成的视窗单位,建议使用vw
    selectorBlackList: ['.ignore'],// 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
    minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
    mediaQuery: false // 允许在媒体查询中转换`px`
    }
    }
    }

    参数解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    {
    unitToConvert: 'px'
    viewportWidth: 320,
    unitPrecision: 5,
    propList: ['*'],
    viewportUnit: 'vw',
    fontViewportUnit: 'vw',
    selectorBlackList: [],
    minPixelValue: 1,
    mediaQuery: false,
    replace: true,
    exclude: [],
    landscape: false,
    landscapeUnit: 'vw',
    landscapeWidth: 568
    }
    • unitToConvert(String) 要转换的单位,默认是’px’
    • viewportWidth(Number) viewport的宽度,默认是320,可根据设计稿来,750的设计稿就写750
    • unitPrecision(Number) 指定px转换为视窗单位值的小数位数,默认是5
    • propList(Array) 指定可以转换的css属性,默认是[‘*’],代表全部属性进行转换
      1. 精确匹配
      2. * 代表全部属性
      3. 在字符串前面或者后面用*,如 [‘position‘] 会匹配background-position-y
      4. 用!则该属性排除. 如: [‘*’, ‘!letter-spacing’]
      5. Combine the “not” prefix with the other prefixes. Example: [‘‘, ‘!font‘]
    • viewportUnit(String)指定需要转换成的视窗单位,默认vw
    • fontViewportUnit(String)指定字体需要转换成的视窗单位,默认vw
    • selectorBlackList(Array) 指定不转换为视窗单位的类,保留px,值为string或正则regexp,建议定义一至两个通用的类名
      1. 值为string类型, 检查字符是否包含[‘body’] 匹配 .body-class
      2. 值为regexp类型,正则匹配[/^body$/]匹配body而不是 .body
    • minPixelValue(Number)默认值1,小于或等于1px不转换为视窗单位
    • mediaQuery(Boolean)是否在媒体查询时也转换px,默认false
    • replace(Boolean) replaces rules containing vw instead of adding fallbacks.
    • exclude(Array or Regexp) 设置忽略文件,如node_modules
      1. 如果是regexp, 忽略全部匹配文件.
      2. 如果是数组array, 忽略指定文件.

    可能遇到的问题

    @keyframes 和media查询里的px默认是不转化的,设置mediaQuery: true则媒体查询里也会转换px

    @keyframes可以暂时手动填写vw单位的转化结果

  • 常用的正则表达式

    一、校验数字的表达式

    1 数字:^[0-9]*$

    2 n位的数字:^\d{n}$

    3 至少n位的数字:^\d{n,}$

    4 m-n位的数字:^\d{m,n}$

    5 零和非零开头的数字:^(0|[1-9][0-9]*)$

    6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$

    7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$

    8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$

    9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$

    10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$

    11 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]$

    12 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$

    13 非负整数:^\d+$ 或 ^[1-9]\d*|0$

    14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$

    15 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d*[1-9]\d*|0?.0+|0$

    16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d*[1-9]\d*))|0?.0+|0$

    17 正浮点数:^[1-9]\d.\d|0.\d*[1-9]\d*$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$

    18 负浮点数:^-([1-9]\d.\d|0.\d*[1-9]\d*)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$

    19 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d*[1-9]\d*|0?.0+|0)$

    二、校验字符的表达式

    1 汉字:^[\u4e00-\u9fa5]{0,}$

    2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$

    3 长度为3-20的所有字符:^.{3,20}$

    4 由26个英文字母组成的字符串:^[A-Za-z]+$

    5 由26个大写英文字母组成的字符串:^[A-Z]+$

    6 由26个小写英文字母组成的字符串:^[a-z]+$

    7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$

    8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$

    9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$

    10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$

    11 可以输入含有^%&’,;=?$"等字符:[^%&’,;=?$\x22]+

    12 禁止输入含有的字符:[^\x22]+

    三、特殊需求表达式

    1 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$

    2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?

    3 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$

    4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

    5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$

    6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

    7 身份证号(15位、18位数字):^\d{15}|\d{18}$

    8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$

    9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

    10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$

    11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$

    12 日期格式:^\d{4}-\d{1,2}-\d{1,2}

    13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$

    14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

    15 钱的输入格式:

    16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]*$

    17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$

    18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$

    19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$

    20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$

    21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$

    22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$

    23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$

    24 备注:这就是最终结果了,别忘了”+”可以用”*”替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里

    25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$

    26 中文字符的正则表达式:[\u4e00-\u9fa5]

    27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))28 空白行的正则表达式:\n\s*\r (可以用来删除空白行)

    29 HTML标记的正则表达式:<(\S*?)[^>]>.?|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)30 首尾空白字符的正则表达式:^\s|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)

    31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)

    32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)

    33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)34 IP地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))

    “^\d+$” //非负整数(正整数 + 0)

    “^[0-9][1-9][0-9]$” //正整数

    “^((-\d+)|(0+))$” //非正整数(负整数 + 0)

    “^-[0-9][1-9][0-9]$” //负整数

    “^-?\d+$” //整数

    “^\d+(.\d+)?$” //非负浮点数(正浮点数 + 0)

    “^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$” //正浮点数

    “^((-\d+(.\d+)?)|(0+(.0+)?))$” //非正浮点数(负浮点数 + 0)

    “^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$” //负浮点数

    “^(-?\d+)(.\d+)?$” //浮点数

    “^[A-Za-z]+$” //由26个英文字母组成的字符串

    “^[A-Z]+$” //由26个英文字母的大写组成的字符串

    “^[a-z]+$” //由26个英文字母的小写组成的字符串

    “^[A-Za-z0-9]+$” //由数字和26个英文字母组成的字符串

    “^\w+$” //由数字、26个英文字母或者下划线组成的字符串

    “^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$” //email地址

    “^[a-zA-z]+://(\w+(-\w+))(.(\w+(-\w+)))(?\S)?$” //url

    整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。

    “^\d+$” //非负整数(正整数 + 0)

    “^[0-9][1-9][0-9]$” //正整数

    “^((-\d+)|(0+))$” //非正整数(负整数 + 0)

    “^-[0-9][1-9][0-9]$” //负整数

    “^-?\d+$” //整数

    “^\d+(.\d+)?$” //非负浮点数(正浮点数 + 0)

    “^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$” //正浮点数

    “^((-\d+(.\d+)?)|(0+(.0+)?))$” //非正浮点数(负浮点数 + 0)

    “^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$” //负浮点数

    “^(-?\d+)(.\d+)?$” //浮点数

    “^[A-Za-z]+$” //由26个英文字母组成的字符串

    “^[A-Z]+$” //由26个英文字母的大写组成的字符串

    “^[a-z]+$” //由26个英文字母的小写组成的字符串

    “^[A-Za-z0-9]+$” //由数字和26个英文字母组成的字符串

    “^\w+$” //由数字、26个英文字母或者下划线组成的字符串

    “^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$” //email地址

    “^[a-zA-z]+://(\w+(-\w+))(.(\w+(-\w+)))(?\S)?$” //url

    整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。

    “^\d+$” //非负整数(正整数 + 0)

    “^[0-9][1-9][0-9]$” //正整数

    “^((-\d+)|(0+))$” //非正整数(负整数 + 0)

    “^-[0-9][1-9][0-9]$” //负整数

    “^-?\d+$” //整数

    “^\d+(.\d+)?$” //非负浮点数(正浮点数 + 0)

    “^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$” //正浮点数

    “^((-\d+(.\d+)?)|(0+(.0+)?))$” //非正浮点数(负浮点数 + 0)

    “^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$” //负浮点数

    “^(-?\d+)(.\d+)?$” //浮点数

    “^[A-Za-z]+$” //由26个英文字母组成的字符串

    “^[A-Z]+$” //由26个英文字母的大写组成的字符串

    “^[a-z]+$” //由26个英文字母的小写组成的字符串

    “^[A-Za-z0-9]+$” //由数字和26个英文字母组成的字符串

    “^\w+$” //由数字、26个英文字母或者下划线组成的字符串

    “^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$” //email地址

    “^[a-zA-z]+://(\w+(-\w+))(.(\w+(-\w+)))(?\S)?$” //url

    整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。整数或者小数:^[0-9]+.{0,1}[0-9]{0,2}$

    只能输入数字:”^[0-9]*$”。

    只能输入n位的数字:”^\d{n}$”。

    只能输入至少n位的数字:”^\d{n,}$”。

    只能输入m~n位的数字:。”^\d{m,n}$”

    只能输入零和非零开头的数字:”^(0|[1-9][0-9]*)$”。

    只能输入有两位小数的正实数:”^[0-9]+(.[0-9]{2})?$”。

    只能输入有1~3位小数的正实数:”^[0-9]+(.[0-9]{1,3})?$”。

    只能输入非零的正整数:”^+?[1-9][0-9]*$”。

    只能输入非零的负整数:”^-[1-9][]0-9”*$。

    只能输入长度为3的字符:”^.{3}$”。

    只能输入由26个英文字母组成的字符串:”^[A-Za-z]+$”。

    只能输入由26个大写英文字母组成的字符串:”^[A-Z]+$”。

    只能输入由26个小写英文字母组成的字符串:”^[a-z]+$”。

    只能输入由数字和26个英文字母组成的字符串:”^[A-Za-z0-9]+$”。

    只能输入由数字、26个英文字母或者下划线组成的字符串:”^\w+$”。

    验证用户密码:”^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。

    验证是否含有^%&’’,;=?$"等字符:”[^%&’’,;=?$\x22]+”。

    只能输入汉字:”^[\u4e00-\u9fa5]{0,}$”

    验证Email地址:”^\w+([-+.]\w+)*@\w+([-.]\w+).\w+([-.]\w+)$”。

    验证InternetURL:”^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$”。

    验证电话号码:”^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$”正确格式为:”XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX”。

    验证身份证号(15位或18位数字):”^\d{15}|\d{18}$”。

    验证一年的12个月:”^(0?[1-9]|1[0-2])$”正确格式为:”01”~”09”和”1”~”12”。

    验证一个月的31天:”^((0?[1-9])|((1|2)[0-9])|30|31)$”正确格式为;”01”~”09”和”1”~”31”。

    可输入形如2008、2008-9、2008-09、2008-9-9、2008-09-09. ^(\d{4}|(\d{4}-\d{1,2})|(\d{4}-\d{1,2}-\d{1,2}))$

    邮箱验证正则表达式 \w+([-+.’]\w+)*@\w+([-.]\w+).\w+([-.]\w+)

    System.Text.RegularExpressions.Match m = System.Text.RegularExpressions.Regex.Match(s, @”您的IP:(?[0-9.]*)”);

    四.网络验证应用技巧

    \1. 验证 E-mail格式

    public bool IsEmail(string str_Email)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_Email,@”^([\w-.]+)@((

    [−9]1,3.[−9]1,3.[−9]1,3.)|(([\w−]+.)+))([a−zA−Z]2,4|[−9]1,3)(

    ?)$”);

    }

    \2. 验证 IP 地址

    public bool IPCheck(string IP)

    {

    string num = “(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)”;

    return Regex.IsMatch(IP,(“^” + num + “\.” + num + “\.” + num + “\.” + num + “$”));

    }

    \3. 验证 URL

    public bool IsUrl(string str_url)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_url, @”http(s)?://([\w-]+.)+[\w-]+(/[\w- ./?%&=]*)?”);

    }

    五. 常用数字验证技巧

    \1. 验证电话号码

    public bool IsTelephone(string str_telephone)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_telephone, @”^(\d{3,4}-)?\d{6,8}$”);

    }

    \2. 输入密码条件(字符与数据同时出现)

    public bool IsPassword(string str_password)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_password, @”[A-Za-z]+[0-9]”);

    }

    \3. 邮政编号

    public bool IsPostalcode(string str_postalcode)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_postalcode, @”^\d{6}$”);

    }

    \4. 手机号码

    public bool IsHandset(string str_handset)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_handset, @”^[1]+[3,5]+\d{9}$”);

    }

    \5. 身份证号

    public bool IsIDcard(string str_idcard)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_idcard, @”(^\d{18}$)|(^\d{15}$)”);

    }

    \6. 两位小数

    public bool IsDecimal(string str_decimal)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_decimal, @”^[0-9]+(.[0-9]{2})?$”);

    }

    \7. 一年的12个月

    public bool IsMonth(string str_Month)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_Month, @”^(0?[[1-9]|1[0-2])$”);

    }

    \8. 一个月的31天

    public bool IsDay(string str_day)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_day, @”^((0?[1-9])|((1|2)[0-9])|30|31)$”);

    }

    \9. 数字输入

    public bool IsNumber(string str_number)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_number, @”^[0-9]*$”);

    }

    \10. 密码长度 (6-18位)

    public bool IsPasswLength(string str_Length)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_Length, @”^\d{6,18}$”);

    }

    \11. 非零的正整数

    public bool IsIntNumber(string str_intNumber)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_intNumber, @”^+?[1-9][0-9]*$”);

    }

    六. 常用字符验证技巧

    \1. 大写字母

    public bool IsUpChar(string str_UpChar)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_UpChar, @”^[A-Z]+$”);

    }

    \2. 小写字母

    public bool IsLowChar(string str_UpChar)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_UpChar, @”^[a-z]+$”);

    }

    \3. 检查字符串重复出现的词

    private void btnWord_Click(object sender, EventArgs e)

    {

    System.Text.RegularExpressions.MatchCollection matches = System.Text.RegularExpressions.Regex.Matches(label1.Text, @”\b(?\w+)\s+(\k)\b”, System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase);

    if (matches.Count != 0)

    {

    foreach (System.Text.RegularExpressions.Match match in matches)

    {

    string word = match.Groups[“word”].Value;

    MessageBox.Show(word.ToString(),”英文单词”);

    }

    }

    else { MessageBox.Show(“没有重复的单词”); }

    }

    \4. 替换字符串

    private void button1_Click(object sender, EventArgs e)

    {

    string strResult = System.Text.RegularExpressions.Regex.Replace(textBox1.Text, @”[A-Za-z]*?”, textBox2.Text);

    MessageBox.Show(“替换前字符:” + “\n” + textBox1.Text + “\n” + “替换的字符:” + “\n” + textBox2.Text + “\n” + “替换后的字符:” + “\n” + strResult,”替换”); }

    \5. 拆分字符串

    private void button1_Click(object sender, EventArgs e)

    {

    //实例: 甲025-8343243乙0755-2228382丙029-32983298389289328932893289丁

    foreach (string s in System.Text.RegularExpressions.Regex.Split(textBox1.Text,@”\d{3,4}-\d*”))

    {

    textBox2.Text+=s; //依次输出 “甲乙丙丁”

    }

    }

    \6. 验证输入字母

    public bool IsLetter(string str_Letter)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_Letter, @”^[A-Za-z]+$”);

    }

    \7. 验证输入汉字

    public bool IsChinese(string str_chinese)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_chinese, @”^[\u4e00-\u9fa5],{0,}$”);

    }

    \8. 验证输入字符串 (至少8个字符)

    public bool IsLength(string str_Length)

    {

    return System.Text.RegularExpressions.Regex.IsMatch(str_Length, @”^.{8,}$”);

    }

  • 切换组件页面时进入进出动画

    App.vue 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <template>
    <div id="app">
    <Header></Header>
    // 用transition 把切换组件页面的容器包含
    <transition name="slide-fade">
    <router-view></router-view>
    </transition>

    </div>
    </template>



    <script>
    import Header from './components/header'
    export default {
    name: 'app',
    components: {Header},

    }

    </script>
    // 动画
    <style scoped>
    .slide-fade{
    position: absolute;left:0;right: 0;
    }
    .slide-fade-enter-active {
    transition: all 1.2s ease;
    }
    .slide-fade-leave-active {

    transition: all .1s cubic-bezier(2.0, 0.5, 0.8, 1.0);
    }
    .slide-fade-enter, .slide-fade-leave-to
    {
    left:0;right: 0;
    transform: translateX(50px);
    opacity: 0;
    }
    </style>