分类: 写写代码

  • Java 多行字符串字面量——Text Blocks

    JDK 15 引入了多行字符串字面量(Text Blocks)特性,不再需要采用字符串拼接的方式来在 Java 代码中定义多行字符串,同时也不需要对字符串内部的双引号做转义,让代码有更好的可读性和可维护性。

    使用

    定义一个 Text Block 很简单,采用三个双引号 """ (开始分隔符)来开头,后面可跟空白符,然后换行开始输入内容,可以换行或输入直接输入双引号,最后用三个双引号""" (结束分隔符)结束。

    Text Block 的内容从开始分隔符后第一个换行符的下一个字符串开始,到结束分隔符前一个字符结束。

    var s = """
            <html>
                <body>
                    <p>Hello, world!</p>
                </body>
            </html>
            """;
    

    最后将得到,其中用.来代表空格,可以看到保持了多行,并且保持了缩进(注意,最后有一个空行)。

    <html>
    ....<body>
    ........<p>Hello,.world!</p>
    ....</body>
    </html>
    
    

    (更多…)

  • EventSource

    EventSource 是浏览器端配合服务端事件(Server-Sent Events)使用的技术。和 WebSocket 相比,服务端事件是单向推送,而 WebSocket 是双向通信。

    浏览器端

    // 第二个参数是可选的,withCredentials 目前用于跨域 cookie,默认 false
    const es = new EventSource(url, { withCredentials: true });
    es.addEventListener('error', err => console.error(err));
    // 监听 message 事件
    es.addEventListener('message', evt => console.log(evt.data));
    // 监听 type/custom 事件
    es.addEventListener('type/custom', evt => console.log(evt.data));
    

    EventSource 要求的是持续不断的输出,如果请求正常地结束终止了,那么客户端会再次自动发起连接,除非显式地调用 close() 方法来结束连接。

    服务端

    服务端的实现是返回标记为 Content-Type: text/event-stream 的持续内容,其内容的基本格式是:

    event: evt-type
    data: any message
    

    其中,event 对应 EventSource 监听的事件类型,但该内容是可选的,如果没指定 event,那么则被 message 事件监听(可简化为 es.onmessage)。

    紧邻的 data 字段会把内容合并起来,连接内容是一个空行。

    直接以 : 开头的内容则是注释,不被解析使用。

    (更多…)

  • Proxy in JavaScript

    可以代理一个对象,对读取、赋值等操作做拦截,从而可额外地做一些事情,对于没有拦截的操作,则按原对象操作调用。

    let p = new Proxy(target, handler);
    

    通过 handler 来提供以下方法(this 指向 代理后的对象,该对象的 [[handler]] 指向 handler):

    • get(target, prop, receiver) 拦截属性读取操作
    • set(target, prop, val, receiver) 拦截设置属性值操作
    • has(target, prop) 可以看作是对 in 的操作的拦截
    • apply(target, thisArg, args) 拦截函数的调用,包括普通调用和 applycall 等。
    • construct(target, args, newTarget) 拦截 new 操作符
    • defineproperty(target, prop, descriptor) 拦截 Object.defineProperty 的操作
    • deleteProperty(target, prop) 拦截 delete 操作
    • getOwnPropertyDescriptor(target, prop) 拦截 Object.getOwnpropertyDescriptor
    • getPrototypeOf(target) 拦截原型对象的读取,包括 Object.getPrototypeOf()__proto__ 以及 instanceof
    • isExtensible(target) 拦截 Object.isExtensible()
    • ownKeys(target) 拦截 Object.getOwnPropertyNames() 以及 Object.keys()
    • preventExtensions(target) 拦截 Object.preventExtensions(obj)
    • setPrototypeOf(target, proto) 拦截 Object.setPrototypeOf

    receiver,Proxy 或继承 Proxy 的对象,最初被调用的对象,通常就是 proxy 自身。但如果对象本身无该属性,原型链上有 Proxy 对象,那么这时对象本身就作为 receiver 参数传进来了。1

    不仅使用于 Object,也使用于 Reflect,拦截具体对应的操作可能有所区别

    Proxy.revocable(target, handler) 创建一个 proxy 并返回撤销该 proxy 的方法。

    const { proxy, revoke } = Proxy.revocable(target, handler);
    // .. do something with proxy
    // 撤销代理
    revoke();
    

    参考

  • 基于ANSI转义序列来构建命令行工具

    命令行工具在输出时,如果是简单的进度更新,可以使用 \r\b 来达成刷新行的效果,但如果要更复杂些的如字体颜色、背景颜色、光标位置移动等功能,那就需要使用 ANSI 转移序列了。

    已有不少成熟的命令行工具库,如 ReadlineJLinePython Prompt Toolkit,基于这些库创造了如 mycliipython 等好用的工具。

    ANSI 转义序列有比较悠久的历史,不同平台支持的功能不完全一致,这里学习到的是比较简单常用的,包括字体颜色、背景色和其它装饰的富文本和光标操作。

    \u001b 即 ESC 的 ASCII 码,\u001b[0m 是清除之前的设定。

    适用于 *nix 的系统。

    富文本

    前景色

    8 色

    \u001b[?m,其中 ? ∈ [30, 37]

    • 黑(black):\u001b[30m
    • 红(red):\u001b[31m
    • 绿(green):\u001b[32m
    • 黄(yellow):\u001b[33m
    • 蓝(blue):\u001b[34m
    • 品红(magenta):\u001b[35m
    • 蓝绿(cyan):\u001b[36m
    • 白(white):\u001b[37m
    • 重置(reset) :\u001b[0m

    16 色

    在 8 色的基础上对字体加粗,颜色加亮,得到另外 8 种,加起来就是 16 色。

    \u001b[?;1m,其中 ? ∈ [30, 37]

    • 亮黑(black):\u001b[30;1m
    • 亮红(red):\u001b[31;1m
    • 亮绿(green):\u001b[32;1m
    • 亮黄(yellow):\u001b[33;1m
    • 亮蓝(blue):\u001b[34;1m
    • 亮品红(magenta):\u001b[35;1m
    • 亮蓝绿(cyan):\u001b[36;1m
    • 亮白(white):\u001b[37;1m
    const out = process.stdout;
    
    function colors8(pre, post, startCode = 30) {
      const codePointA = 'A'.codePointAt(0);
    
      let i = 0;
      while (i < 8) {
        const colorCode = startCode + i;
        const char = String.fromCodePoint(codePointA + i);
        out.write(`{pre}{colorCode}{post}{char} `);
        i++;
      }
      console.log('\u001b[0m');
    }
    
    function fgColors8() {
      colors8('\u001b[', 'm');
    }
    
    function fgColors8Bright() {
      colors8('\u001b[', ';1m');
    }
    
    fgColors8();
    fgColors8Bright();
    

    (更多…)

  • 浏览器下载 PDF 文件

    用浏览器访问一个指向 PDF 文件的链接,具体表现出来的行为会有所区别,最主要的区别就是在浏览器中打开(记作预览)或触发下载(记作下载)。

    这些行为与请求响应结果中的 Content-Type 以及 Content-Disposition 这两个 header 有关。

    Content-Typeapplication/pdfapplication/octet-stream 以及 application/custom-unknown 和无 Content-Disposition、取 inline 以及 attachment 来做组合实验,得到结果如下:

    header Chrome Firefox Safari
    Content-Type: application/pdf 预览 预览 预览
    Content-Type: application/pdf
    Content-Disposition: inline
    预览 预览 预览
    Content-Type: application/pdf
    Content-Disposition: attachment
    下载 下载 下载
    Content-Type: application/octet-stream 下载 下载 下载
    Content-Type: application/octet-stream
    Content-Disposition: inline
    下载 下载 下载
    Content-Type: application/octet-stream
    Content-Disposition: attachment
    下载 下载 下载
    Content-Type: application/custom-unknown 下载 下载 下载
    Content-Type: application/custom-unknown
    Content-Disposition: inline
    下载 下载 下载
    Content-Type: application/custom-unknown
    Content-Disposition: attachment
    下载 下载 下载

    Content-Typeapplication/octet-stream 表示浏览器应当把这个文件作为二进制数据来对待。但实际上浏览器可能会自己根据 Content-Disposition 甚至文件内容来做些文件格式探测,从而使用浏览器内置的功能或插件功能来处理。上面用的 application/custom-unknown 就是特意设计一个不是通用的 type 来避免已有场景。

    下载时,如果没通过 Content-Disposition 指定文件名(如 Content-Disposition:inline;filename=attach.pdf),那么下载后的文件的名称通过 url 来获取,如 /download.php 得到的是 download.php

    Firefox 在触发下载时,如果 Content-Dispositioninline,那么会静默下载,而不会像 attachment 或无该 header 时那样弹出浏览器的保存弹框。

    Safari 下载后,如果是 pdf 文件(通过 Content-TypeContent-Disposition 或其它方式识别出来),那么会自动打开该文件。

    此处使用 pdf 来作为示例,对于其它类型的文件,浏览器和系统的规则推测应类似。

    • 操作系统:macOS Mojave 10.14.2
    • Chrome: 72.0.3626.121(正式版本) (64 位)
    • Firefox: 65.0.1 (64 位)
    • Safari: 12.0.2 (14606.3.4)

    参考

  • properties 文件值的两端空白

    properties 文件中,取值(键)时会做 trim 来去掉头尾的空白,如果是在需要在两端有空白,可以用 \u0020 来替代。

    参考

  • 包含块

    一个元素的占用面积大小以及布局定位通常受到包含块(Containing Block)的影响,比如 topleft 取百分比时相对的长度是什么长度,widthheight 取百分比时相对的长度是什么长度?
    包含块一般情况下是当前元素的最近的祖先块元素1内容区域,但具体还需要分情况来确定。

    布局区域

    浏览器渲染文档时,对于每一个元素,都会有一个布局盒子,该盒子又被划分为四个区域。

    • 内容区域——Content Area
    • 内边距区域——Padding Area
    • 边框区域——Border Area
    • 外边距区域——Margin Area

    box-sizing 设为 border-box,会影响 width 和 height 的计算,但不影响布局区域的划分。

    (更多…)

  • 视窗百分比长度

    视窗百分比长度是指相对于视窗1的尺寸,包含了 vwvhvminvmax

    • vw:视窗宽度的 1%
    • vh:视窗高度的 1%
    • vminmin(vw, vh)
    • vmaxmax(vh, vh)

    其中,视窗一般是指浏览器文档渲染窗口,但对于 iframe 里的文档而言,它们的视窗是 iframe 元素的 内容区域(content area) 的大小。

    参考资料

  • NBSP

    &nbsp; ,全称是 non-breaking space,除了表示空白,还含有告诉渲染引擎不要在此换行的意义。

    可用于空间足够时和别的文本在同一行内展示,空间不够时,整串字符串(如 0800 3000 9974 这样的通过空白符来让电话号码等更易读的情况)不会在在空白符处换行,而是整串换行(需要把空白符替换为 &nbsp;)。当然,也可以通过设置这个串为不折行来解决:white-space: nowrap;

    此为示例代码

    参考

  • React Context

    Context 的目的是对于全局的需要多处使用的数据,不希望通过 prop 一层层传递下去,而是可以直接使用。

    childContextTypes & getChildContext

    该方案在 16.3 后不再是推荐方案。

    React 最初提供的 context 方案是提供 context 数据的组件通过静态的 childContextType 来定义 context 中各字段的数据类型,通过 getChildContext 成员方法来提供实际的 context 数据,其中,返回的数据可以搭配 prop 和 state 来提供响应式的数据。而消费 context 数据的组件通过静态的 contextTypes 来指定需要消费的 context 字段的类型集合,然后即可通过 this.context[fieldName] 来使用。

    注意,消费 context 的组件只需要定义需要的字段的类型即可,不需要是全部。同时,也不需要是提供 context 的组件的直接子组件。

    import PropTypes from 'prop-types';
    
    class Button extends React.Component {
      static contextTypes = {
        color: PropTypes.string
      };
    
      render() {
        return (
          <button style={{background: this.context.color}}>
            {this.props.children}
          </button>
        );
      }
    }
    
    class Message extends React.Component {
      render() {
        return (
          <div>
            {this.props.text} <Button>Delete</Button>
          </div>
        );
      }
    }
    
    class MessageList extends React.Component {
      static childContextTypes = {
        color: PropTypes.string
      };
    
      getChildContext() {
        return {color: "purple"};
      }
    
      render() {
        const children = this.props.messages.map((message) =>
          <Message text={message.text} />
        );
        return <div>{children}</div>;
      }
    }
    

    (更多…)