Monday, February 14, 2022

SegmentFault 最新的文章

SegmentFault 最新的文章


展开操作符:一家人就这么被拆散了

Posted: 13 Feb 2022 06:48 PM PST

大家好,我卡颂。

想必大家在业务中应该经常使用展开操作符(Spread syntax),比如展开数组:

function sum(x, y, z) {   return x + y + z; }  const numbers = [1, 2, 3];  // 6 console.log(sum(...numbers));

克隆对象:

const clone = { ...obj };

但你有没有见过如下操作:

本文就来聊聊上述情况的成因。

欢迎加入人类高质量前端框架群,带飞

展开操作符对字符串的作用

iterableES2015增加的类型,展开操作符可以作用于iterable,将其展开为0到多个参数。ArrayString这些常见的类型都属于iterable

iterable可以使用for..of语法遍历,比如:

for (let i of 'abc') {   console.log(i) } // a // b // c

Array属于iterable很好理解,但是为什么String也属于iterable呢?

这是因为在ES2015之前,String基本实现了Array用于遍历的所有要素,比如:

  • 拥有.length属性
  • 可以通过下标访问索引位置的字符

我们不讨论这种实现是否正确,只是想说:用于遍历时,StringArray体验类似。

所以到了ES2015,当Array作为iterable支持for...of遍历时,从体验上来说,String也需要支持同样的语法。

展开操作符展开一家三口的结果:

for...of遍历,可以看到对应的Unicode字符:

那么一家三口作为一个emoji,有什么特殊的呢?

Emoji Sequence

这种由多个emoji组合成的emoji被称为Emoji Sequence,他利用了文字可以组合形成这一特性。

比如,泰语就是可以组合形成的文字

泰语里的萨瓦迪卡(你好),写为สวัสดีครับ。你会发现,在字符头上有个帽子

此外,泰语中的一些字符还有鞋子,比如ผู

又或者又有帽子又有鞋子,比如:ผู้

在泰语输入法中,用户依次输入基本字符帽子鞋子,组合成需要的字符后再输入结束字符,就拼凑成一个完整字符。

回到我们的一家三口,你会发现,展开后的第1、3项为零宽字符(Zero Width Joiner,简称zwj):

从上述for...of遍历的结果可知,这个零宽字符为\u200D。他通常用于排版。但Emoji Sequence把他拿去作为emoji之间的胶水。

换言之,你可以用\u200D将多个emoji组合成一个Emoji Sequence

比如,爸爸妈妈小男孩可以组合成一家三口:

当然,小女孩也可以:

小姑娘学士帽组合成戴学士帽的小姑娘

总结

当了解组合方法后,你可以用现有emoji组合成全新的Emoji Sequence

两个爸爸带孩子的家庭,两个妈妈带孩子的家庭,这都是可以尝试的嘛~

Cross-origin 跨域请求

Posted: 13 Feb 2022 07:20 AM PST

Cookie

  • 1993 迎来了 img 标签,这是 web 第一次迎来了资源加载

    <img src="foo.jpg" /> <img src="http://somewhere.com/foo.jpg" />
  • 1994

    Set-Cookie: foo=bar
  • 1995

    <img src="http://somewhere.com/foo.jpg" /> <script src="http://somewhere.com/foo.js" ></script> <iframe src="http://somewhere.com/foo.html" />

    起初这样的设计会导致严重的问题:无论是网站,只要包含上面的标签都会携带 cookie 访问。

更不用说这样的请求了。

<form action="http://somewhere.com./submit"></form>

因此 http 通过 Origin 这个头来限制访问

  • 1999

    // XMLHttpRequest 的前身 new ActiveXObject('Microsoft.XMLHTTP');
  • 2008

    var req = new XMLHttpRequest() req.addEventListener('load', loadListener); req.open('GET', 'https://example.com/data.txt'); req.send();

现在

const res = await fetch('http://example.com/data.txt', {     credentials: 'include', });

现在我们已经使用这样方式请求,但是同样也要受限于服务器是否响应 (下面就是服务器为什么有的会响应)

跨域资源共享(CORS)

我们通过 access-control-allow-origin 来决定那些那资源可以被跨域访问,例如设置 access-control-allow-origin: * 允许所有资源可以被访问 。并且通过设置 credential 来决定是否携带 cookie

access-control-allow-credentials: true 决定是否允许你访问 cookie。但是同时你必须明确通过 access-control-allow-origin 指出跨域访问的网址。

这些头信息已经在我们通过浏览器发出的请求中设置好了,我们不需要特别关心。例如我们通过 <form> 发送时请求时, Content-Type 已经设置成例如 x-www-form-urlencoded

一个小知识点:你可以发送设置成 Content-type: text/plainPOST 请求。例如通过 <form> 来发送 email,只要保证了像 x-www-form-urlencoded 的空行间隔。

Referrer

从一个网站跳到另外一个网站时,会有这个头信息

Origin

Referrer 不可靠,所以有了 Origin 这个头信息。Origin 会出现在跨域的请求中。

通常来说,我们限制了 access-control-allow-origin 网站就已经足够安全。

除了CORSGET 请求外,其他的GET 请求都不带 Origin。可以通过判断 Origin 得知这个是不是跨域的请求。

跨域中危险的 cookie

当我们不带 cookie 发出请求时,可能会引发很多问题。首先我们要知道什么情况 access-control-allow-origin: * 时是安全的 ,例如家中分布的IoT 设备,本地的请求。除此之外都可能引发CSRF 攻击。

限制 cookie

例如通过图片追踪。当你访问 other.com 时,<img /> 会允许设置cookie 从而进行跟踪。

<!-- site: other.com --> <img src="http://statics.com/user-avatar-a.png" />

设置 SameSite: Strict | Lax | NoneLax 不允许 img iframe AJAX POST 表单携带 cookie (当前所在地站点是:other.com ) 。不过 Lax 允许<a>链接,预加载请求,GET 表单这3种情况的携带 cookie

Set-cookie: sessionid=1234567; SameSite=Lax; HttpOnly; Max-Age=3153600
SameSite: Strict 这会导致所有的跨域均不带 cookie ,例如在非github 的网站上跳转至 github 将不会携带 cookie ,此时 github 将显示未登录。
httponly:不能通过 javasript 获取 cookie

跨域资源策略(CORP)

Cross-Origin-Resource-Policy: same-site 标记为 same-site 的资源只能从相同站点加载。

Cross-Origin-Resource-Policy 响应头会指示浏览器阻止对指定资源的无源跨域/跨站点请求。

在下面这个请求中加入 CORP 的头信息,图片将加载失败。

<!-- site: other.com --> <img src="http://statics.com/user-avatar-a.png" />
Cross-Origin-Resource-Policy: same-site | same-origin |cross-origin

CORB

下面这个场景是无法通过同一个 cookie 处理,虽然你可以将 cookie 放在 html 的请求中。

<img src="http://example.com/secret.html" />

这个请求仍然会将 html 文件的请求放到进程中,来表示当前请求处理完成。这可能会引起某些问题。Meltdown and Spectre

Cross-Origin-Read-Blocking 阻止跨域的请求动作,同时跨域的返回也会被阻止。例如上面的请求,将不会进入浏览器的处理进程中。

请求在进入进程前,会注意mine type 如果是 nosniff (nosniff 将阻止浏览器针对 content-type的一些优化)。Chrome 浏览器针对 html 文件的请求并且不是跨域将会阻止。

CORB 是不头信息

跨域嵌入策略 (COEP)

Cross-Origin-Embedder-Policy: require-corp 文档只能从同一来源加载资源,或者显式被标记为可从另一来源加载的资源。

为了从其他来源加载资源,需要支持跨域资源共享(CORS)或跨域资源策略(CORP)

Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Resource-Policy: cross-origin

字符作画,我用字符画个冰墩墩

Posted: 13 Feb 2022 05:00 PM PST

文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客
本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star。

哈喽,大家好啊,我是阿朗。

已经 2022 年了,最近北京冬奥会的吉祥物冰墩墩很火,据说一墩难求,各种视频新闻应接不暇。程序员要有程序员的方式,今天我来用 Java 画一个由字符组成的冰墩墩送给大家,这篇文章记录字符图案的生成思路以及过程。

下面是一个由字符W@#&8*0. 等字符组成的冰墩墩图案。

字符冰墩墩

1. 字符图案思路

我们都知道数字图片是一个二维图像,它使用一个有限的二维数组保存每个像素点颜色信息,这些像素点的颜色信息通常使用 RGB 模式进行记录。所以从本质上看,我们常见的图片就是一个保存了像素信息的二维数组。

像素图片 - 来自维基百科

基于以上的图片原理,我们可以发现,如果想要把一个图片转换成字符图案,只需要把每个像素点的颜色信息转换成某个字符就可以了,所以可以理出把图片转换成字符图案的步骤如下。

  1. 缩放图片到指定大小,为了保证输出的字符数量不会太多。
  2. 遍历图片的像素点,获取每个像素点的颜色信息。
  3. 根据像素点的颜色信息,转换成灰度(亮度)信息。
  4. 把亮度信息转换成相应的字符。
  5. 输出字符图案,也就是打印二维字符数组。

2. 图片的缩放

如上所述,我们既然想要把每个像素点的颜色信息转换成某个字符,如果像素点过多的话,虽然会增加字符图片的还原度,但是看起来会非常麻烦,因为那么多字符你的屏幕可能显示不完。

因此,我们要先对图片进行缩放,缩放到一定大小后再进行字符化。这里为了方便,直接使用 Java 自带的图片处理方式进行图片缩放,下面的代码示例都是指定宽度进行缩放,高度等比例计算后进行缩放。

Java 中调整图片大小主要有两种方式:

  1. 使用 java.awt.Graphics2D 调整图片大小。
  2. 使用 Image.getScaledInstance 调整图片大小。

2.1. java.awt.Graphics2D

Graphics2D 是 Java 平台提供的可以渲染二维形状、文本、图像的基础类,下面是使用 Graphics2D 进行图片大小调整的简单示例。

/**  * 图片缩放  *  * @param srcImagePath  图片路径  * @param targetWidth   目标宽度  * @return  * @throws IOException  */ public static BufferedImage resizeImage(String srcImagePath, int targetWidth) throws IOException {     Image srcImage = ImageIO.read(new File(srcImagePath));     int targetHeight = getTargetHeight(targetWidth, srcImage);     BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);     Graphics2D graphics2D = resizedImage.createGraphics();     graphics2D.drawImage(srcImage, 0, 0, targetWidth, targetHeight, null);     graphics2D.dispose();     return resizedImage; }  /**  * 根据指定宽度,计算等比例高度  *  * @param targetWidth   目标宽度  * @param srcImage      图片信息  * @return  */ private static int getTargetHeight(int targetWidth, Image srcImage) {     int targetHeight = srcImage.getHeight(null);     if (targetWidth < srcImage.getWidth(null)) {         targetHeight = Math.round((float)targetHeight / ((float)srcImage.getWidth(null) / (float)targetWidth));     }     return targetHeight; }

代码中的 BufferedImage.TYPE_INT_RGB 表示所使用的颜色模型,所有的颜色模型可以在 Java doc - Image 文档中看到。

调整大小后的图片可以通过以下方式保存。

BufferedImage image = resizeImage("/Users/darcy/Downloads/bingdundun.jpeg", 200); File file = new File("/Users/darcy/Downloads/bingdundun_resize.jpg"); ImageIO.write(image, "jpg", file);

下面把原图为 416 x 500 的冰墩墩图片缩放到 200 x 240 的效果。

Java 图片缩放

2.2. Image.getScaledInstance

这是 Java 原生功能调整图片大小的另一种方式,使用这种方式调整图片大小简单方便,生成的图片质量也不错,代码比较简洁,但是这种方式的效率并不高

/**  * 图片缩放  *  * @param srcImagePath  图片路径  * @param targetWidth   目标宽度  * @return  * @throws IOException  */ public static BufferedImage resizeImage2(String srcImagePath, int targetWidth) throws IOException {     Image srcImage = ImageIO.read(new File(srcImagePath));     int targetHeight = getTargetHeight(targetWidth, srcImage);     Image image = srcImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_DEFAULT);     BufferedImage bufferedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);     bufferedImage.getGraphics().drawImage(image, 0, 0, null);     return bufferedImage; } // getTargetHeight 同 java.awt.Graphics2D 中示例代码 

代码中的 Image.SCALE_DEFAULT 表示图片缩放使用的算法,在Java doc - Image 文档中可以查看所有可以使用的算法。

3. RGB 灰度计算

我们知道图片是由像素点组成的,每个像素点存储了颜色信息,通常是 RGB 信息,所以我们想要把每个像素点转换成字符,也就是把像素点中的 RGB 信息的灰度表达出来,不同的灰度给出不同的字符进行表示。

比如我们把灰度分为 10 个等级,每个等级从高到低选择一个字符进行标识。

'W', '@', '#', '8', '&', '*', 'o', ':', '.', ' '

那么如何进行灰度计算呢?目前常见的计算方法有平均值法、加权均值法、伽马校正法等。这里直接使用与伽马校正线性相似的数学公式进行计算,这也是 MATLABPillowOpenCV 使用的方法。

4. 输出字符图片

前期准备已经完成了,我们已经把图片进行了缩放,同时也知道了如何把图片中的每个像素点上的 RGB 信息转换成灰度值,那么我们只需要遍历缩放后的图片的 RGB 信息,进行灰度转换,然后选择对应的字符进行打印即可。

public static void main(String[] args) throws Exception {     BufferedImage image = resizeImage("/Users/darcy/Downloads/bingdundun.jpeg", 150);     printImage(image); }  /**  * 字符图片打印  *  * @param image  * @throws IOException  */ public static void printImage(BufferedImage image) throws IOException {     final char[] PIXEL_CHAR_ARRAY = {'W', '@', '#', '8', '&', '*', 'o', ':', '.', ' '};     int width = image.getWidth();     int height = image.getHeight();     for (int i = 0; i < height; i++) {         for (int j = 0; j < width; j++) {             int rgb = image.getRGB(j, i);             Color color = new Color(rgb);             int red = color.getRed();             int green = color.getGreen();             int blue = color.getBlue();             // 一个用于计算RGB像素点灰度的公式             Double grayscale = 0.2126 * red + 0.7152 * green + 0.0722 * blue;             double index = grayscale / (Math.ceil(255 / PIXEL_CHAR_ARRAY.length) + 0.5);             System.out.print(PIXEL_CHAR_ARRAY[(int)(Math.floor(index))]);         }         System.out.println();     } }  // resizeImage 同第二部分代码 

这里我选择一张冰墩墩的图片,可以看到输出后的效果。

5. 其他字符图片

下面是一些其他图片转字符图的效果展示。

2022 年,虎虎生威字符画。

老虎字符画

进击的巨人人物 - 三笠字符画。

三笠字符画

一如既往,文章中的代码存放在:github.com/niumoo/lab-notes

参考

https://www.kdnuggets.com/201...

https://en.wikipedia.org/wiki...

订阅

可以微信搜一搜程序猿阿朗或访问未读代码博客阅读。
本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star。

No comments:

Post a Comment