13# 软件开发时的一些笔记 (WIP)

 

# 开发

1. [开发](#开发)
   1. [后端](#后端)
      1. [Java](#java)
   2. [前端](#前端)
      1. [JS / TS](#js--ts)
      2. [浏览器跨域](#浏览器跨域)
      3. [跨站脚本是什么](#跨站脚本是什么)
         1. [如何防范](#如何防范)
      4. [浏览器的重排重绘](#浏览器的重排重绘)

## 后端

### Java

1. `StringBuffer``StringBuilder` 的区别.

    - 两者底层都是 `char[], StringBuilder` 不是线程安全的类, `StringBuffer` 是线程安全的类, 其实现线程安全的方法是给每个方法加上了 `synchronized`.

2. `ThreadLocal` 是什么.

    - `ThreadLocal` 是无并发的线程安全容器, 其内部自己实现了一个 Map 数据结构, key 是线程的 id, value 是线程的值.

3. java 程序产生内存溢出的排查过程.
    - 设置 Xmx 小一点, 这样最终产生的 dump 文件方便分析, 使用 jhat 或者 jvisualvm 分析 dump 文件, 注重观察占用最多 bytes 的类是什么并分析这些类为什么没有被 jvm 回收从而导致 oom.

## 前端

### JS / TS

1. JS 的箭头函数是什么?
    - 箭头函数表达式的语法比函数表达式更简洁, 并且没有自己的 `this, arguments, super``new.target`. 箭头函数表达式更适用于那些本来需要匿名函数的地方, 并且它不能用作构造函数. 引入箭头函数有两个方面的作用: 更简短的函数并且不绑定`this`.

### 浏览器跨域

1. 什么是跨域

    - 跨域问题的来源是浏览器为了请求安全而引入的基于同源策略的安全特性. 当页面和请求的协议, 主机名或端口不同时, 浏览器判定两者不同源, 即为跨域请求. 需要注意的是跨域是浏览器的限制, 服务端并不受此影响.

2. 为什么会跨域

    - 当页面和请求的协议, 主机名或端口不同时, 浏览器判定两者不同源, 从而产生跨域. 需要注意的是跨域是浏览器的限制, 实际请求已经正常发出和响应了. 当协议, 主机名和端口都相同时, 浏览器才判定两者是同源关系, 否则即为跨域.

3. 为什么有跨域限制

    - 浏览器自带的安全策略.

4. 怎么解决跨域

    - CORS 是目前最为广泛的解决跨域问题的方案. 方案依赖服务端/后端在响应头中添加 `Access-Control-Allow-*` 头, 告知浏览器端通过此请求. CORS 只需要服务端/后端支持即可, 不涉及前端改动. CORS 将请求分为简单请求 (Simple Requests) 和需预检请求 (Preflighted requests), 不同场景有不同的行为:

        - 简单请求: 不会触发预检请求的称为简单请求.
            - 请求方法: `GET, HEAD, POST`.
            - 请求头: `Accept, Accept-Language, Content-Language, Content-Type`.
                - `Content-Type` 仅支持: `application/x-www-form-urlencoded, multipart/form-data, text/plain`.
        - 需预检请求: 当一个请求不满足以上简单请求的条件时, 浏览器会自动向服务端发送一个 `OPTIONS` 请求, 通过服务端返回的 `Access-Control-Allow-*` 判定请求是否被允许. CORS 引入了以下几个以 `Access-Control-Allow-*` 开头:

            - `Access-Control-Allow-Origin` 表示允许的来源
            - `Access-Control-Allow-Methods` 表示允许的请求方法
            - `Access-Control-Allow-Headers` 表示允许的请求头
            - `Access-Control-Allow-Credentials` 表示允许携带认证信息

        - 当请求符合响应头的这些条件时, 浏览器才会发送并响应正式的请求.

    - 反向代理解决跨域问题的方案依赖同源的服务端对请求做一个转发处理, 将请求从跨域请求转换成同源请求. 反向代理只需要服务端/后端支持, 几乎不涉及前端改动, 只用切换接口即可. 实现方式为在页面同域下配置一套反向代理服务, 页面请求同域的服务端, 服务端请求上游的实际的服务端, 之后将结果返回给前端.

5. `LocalStorage / SessionStorage` 跨域

    - `LocalStorage``SessionStorage` 同样受到同源策略的限制. 而跨域读写的方式也可以使用前文提到的 `postMessage`.

6. 跨域与监控

    - 前端项目在统计前端报错监控时会遇到上报的内容只有 Script Error 的问题. 这个问题也是由同源策略引起. 在 `<script>` 标签上添加 `crossorigin="anonymous"` 并且返回的 JS 文件响应头加上 `Access-Control-Allow-Origin: *` 即可捕捉到完整的错误堆栈.

7. 跨域与图片
    - 前端项目在图片处理时可能会遇到图片绘制到 Canvas 上之后却不能读取像素或导出 base64 的问题. 这个问题也是由同源策略引起. 解决方案同上.

### 跨站脚本是什么

就是执行了不在本域名内 (别家公司的) 脚本, 对用户数据造成了损害或者窃取.

#### 如何防范

做好跨域检查.

### 浏览器的重排重绘

浏览器渲染大致分为四个阶段, 其中在解析 HTML 后, 会依次进入 Layout 和 Paint 阶段. 样式或节点的更改, 以及对布局信息的访问等, 都有可能导致重排和重绘. 而重排和重绘的过程在主线程中进行, 这意味着不合理的重排重绘会导致渲染卡顿, 用户交互滞后等性能问题.

> Parse HTML:相关引擎分别解析文档和样式表以及脚本, 生成 DOM 和 CSSOM , 最终合成为 Render 树.
>
> Layout:浏览器通过 Render 树中的信息, 以递归的形式计算出每个节点的尺寸大小和在页面中的具体位置.
>
> Paint:浏览器将 Render 树中的节点转换成在屏幕上绘制实际像素的指令, 这个过程发生在多个图层上.
>
> Composite:浏览器将所有层按照一定顺序合并为一个图层并绘制在屏幕上.

1. 什么是重排重绘

    - DOM 或 CSSOM 被修改, 会导致浏览器重复执行图中的步骤. 重排和重绘, 本质上指的就是触发 Layout 和 Paint 的过程, 且重排必定导致重绘.

2. 如何提升页面渲染性能

    - 尽可能减少重绘重排 (跳过 `Layout / Paint` 步骤), 从而降低浏览器渲染耗费的时间, 将内容尽快渲染到屏幕上.

3. 如何减少页面重排重绘

    - 对 DOM 进行批量写入和读取 (通过虚拟 DOM 或者 `DocumentFragment` 实现).
    - 避免对样式频繁操作, 了解常用样式属性触发 `Layout / Paint / Composite` 的机制, 合理使用样式.
    - 合理利用特殊样式属性 (如 `transform: translateZ(0)` 或者 `will-change`), 将渲染层提升为合成层, 开启 GPU 加速, 提高页面性能.
    - 使用变量对布局信息 (如 `clientTop`)进行缓存, 避免因频繁读取布局信息而触发重排和重绘.

4. 哪些行为会引起重排 / 重绘.
    - 外观有变化时, 会导致重绘. 相关的样式属性如 `color opacity` 等.
    - 布局结构或节点内容变化时, 会导致重排. 相关的样式属性如 `height float position` 等.
        - 盒子尺寸和类型.
        - 定位方案 (正常流, 浮动和绝对定位).
        - 文档树中元素之间的关系.
        - 外部信息 (如视口大小等).
    - 获取布局信息时, 会导致重排.相关的方法属性如 `offsetTop getComputedStyle` 等.


评论

此博客中的热门博文

17# Apache Spark 的学习笔记 (WIP)

20# Apache Kafka 的学习笔记

1# 失败的赛博空间匿名技术的思考