Skip to main content

2022

八月

本地测试 cli

cli 工程:执行 yarn link

项目工程:执行 yarn link xxx-cli ,然后通过 yarn xxx-cli --version 命令测试。

注1:执行 yarn [xxx-cli] 是跟你 cli 工程定义的 bin 来的。

注2:执行 yarn [xxx-cli] 可能会报错 Permission denied。执行 chmod +x ./node_modules/.bin/xxx-cli 来获得权限

node 打印样式

打印模版可以看 \x1b[${style}m${text}\x1b[0m

\x1b[ 表示样式控制开始

m 表示样式控制结束

0 重制

1 粗体

30-37 前景色

40-47 背景色

30 4031 41 绿 32 4233 4334 44 品红 35 4536 4637 47

六月

避免滚动条占位置

可以使用 scroll: overlay,使用后将不会占位

如何避免 open 重复打开浏览器标签页

就目前来看,最佳方案就是不用 open,而是直接用 applescript 来执行,当然只适用于 MacOS 环境。

参考 create-react-app 方案:

五月

表单 disabled 与 readonly 的区别

相同点

  • 不可编辑

不同点

disabled:

  • form 提交不会被发送
  • 不能被 focus

readonly:

  • form 提交被发送
  • 可以被 focus
  • select、option、button 等时没有 readonly 属性的

四月

package.json 字段解析之 exports

exports 于 node v12+ 开始支持,是 main 字段的替代方案。区别在于它支持条件导出

主入口导出

一般建议同时定义 mainexports

注意:定义了 exports 后会有严格的使用限制。包的所有子路径都被封装,不再提供给导入器,如 require('pkg/foo.js') 会抛出错误。

{
"main": "./dist/index.js",
"exports": "./dist/index.js"
}

子路径导出

{
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js",
"./foo": "./dist/bar/foo.js",
}
}

如上的配置,允许用户导入子路径 require('pkg/foo') 相当于 ./node_modules/pkg/dist/bar/foo.js

如果有多个文件,可以使用模式匹配

 {
"exports": {
"./features/*": "./dist/features/*.js"
},
}

require('pkg/features/xx') 相当于 ./node_modules/pkg/dist/features/xx.js, xx 可以是其他文件名

条件导出

{
"type": "module",
"main": "./index.cjs",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
}
}

高清化方案整理

一些概念

  • 设备像素:物理像素,不能改变。
  • 设备分辨率:设备真实宽高的像素点宽高。
  • 独立像素:操作系统定义的像素(操作系统会将独立像素转为设备像素)
  • CSS 像素:1 css 像素在缩放比例为 100% 时,等于 1 1 个独立像素,为 200% 时,等于 2 2 个独立像素
  • 布局视口(layout viewport):是 html 的父容器
  • 视觉视口(visual viewport):是 屏幕所见区域的视口
  • 理想视口(ideal viewport):是理想视口,通过 <meta name="viewport" content="width=device-width"> 来设置

0.5px 方案

伪元素 + 宽高 200% + 缩小比例 0.5 + 定位从 0,0 缩小 + 绝对定位,父元素相对定位

图片高清方案

媒体查询,不同 dpr 不同图片

iPhone X 以上机型兼容

iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离。

给 viewport 加上 viewport-fit=cover 才能使用 env 。

padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

vw 兼容方案

Viewport Units Buggyfill

postcss-viewport-units

浏览器宽高相关的 API 以及区别

Window:

window.outerWidthwindow.outerHeight:

浏览器整个的宽和高,会包含书签栏、导航栏、控制台等,是浏览器窗口的宽高

window.innerWidthwindow.innerHeight:

是窗口的布局宽高( layout viewport ),包含了滚动条的宽度。(显示区的宽高,即使有元素超过了布局,产生滚动条了,也不会影响)

Element:

el.clientWidthel.clientHeight:

元素的 content width + padding width(不包括滚动条)

比如 width: 100px, padding: 20px, 那么就是 140px,如果有 20px 的滚动条,那就是 120px

el.offsetWidthel.offsetHeight:

元素的 content width + padding width + border width(包括滚动条)

el.offsetLeftel.offsetTop:

元素的左侧与上侧的偏移量,相对于元素(以 offsetWidth 和 offsetHeight 组成的容器)左上角和视口比较

el.scrollWidthel.scrollHeight:

当没有滚动条时,等同于 clientWidth,有了之后会加上整个超出的宽高。

el.scrollLeftel.scrollTop:

元素的滚动偏移

三月

charAt 与 index 的区别

相同点:

当字符串是一个 non-BMP(Unicode字符平面映射),都是识别出对应位置的编码,而实际上 \uD87E\uDC04 是一个字。如何使得 charAt 支持 non-BMP 字符可以参考 MDN 的方案

const str = 'A\uD87E\uDC04Z'

case: str.length // -> 4
case: str[1] === str.charAt(1) // true 都是 \uD87E

不同点:

参考 StackOverFlow: charAt 执行过程

charAt 会处理入参,将参数转为数字,向下舍入,超出字符串范围范围空字符串

"Hello"[313]    //undefined
"Hello".charAt(313) //"", 313 is out of bounds

"Hello"[3.14] //undefined
"Hello".charAt(3.14) //'l', rounds 3.14 down to 3

"Hello"[true] //undefined
"Hello".charAt(true) //'e', converts true to the integer 1

"Hello"["World"] //undefined
"Hello".charAt("World") //'H', "World" evaluates to NaN, which gets converted to 0

"Hello"[Infinity] //undefined
"Hello".charAt(Infinity) //"", Infinity is out of bounds

__dirname in ES module

No __filename or __dirname

在 es 模块中,是没有 __dirname 的,我们可以使用下面这种方式 hack

import { dirname } from 'path'
import { fileURLToPath } from 'url'

const __dirname = dirname(fileURLToPath(import.meta.url))

compose 组合

函数组合,来自于 Redux,可以通过 compose 函数组合 middleware

// compose(f, g, h) 相当于 (...args) => f(g(h(...args)))
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}

if (funcs.length === 1) {
return funcs[0]
}

return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

二月

标签模版

最近看了下 styled-components,发现他很奇怪的使用方式

const Button = styles.a`
width: 11rem;
padding: 0.5rem 0;
`

以前一直以为是编译时的,觉得这种写法也太难搞了,最近又看了下,意识到这是模版字符串的一种功能,叫标签模版。(以前学 ES 的时候咋没发现 😂)

又突然想到,graphql 也是用了标签模板的:

const typeDefs = gql`
type Query {
hello: String
}
`

用例参考 阮一峰的 ESCMScript 6 入门 —— 标签模版

const tag = strArr => {
console.log(strArr) // -> ['hello']
}
tag`hello`

如果使用动态拼接,就会不太一样,动态拼接的会使得字符串分割,并一个一个的都放在 resetParams 中:

const tag = (strArr, ...resetParams) => {
console.log(strArr) // ['start:', ' end then:', '']
console.log(resetParams) // [10, 11]
}

const a = 10
tag`start:${a} end then:${a + 1} `

有哪些用处呢:

可以写一个 jsx 函数

const Page = ({ children, title, onClick }) => jsx`
<div>
<h1>${title}</h1>
<button onClick={${onClick}>${children}</button>
</div>
`

甚至可以在 JS 中写 java。(那是不是甚至可以在 JS 中写 c++ 🤣)

java`
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
`
HelloWorldApp.main()

(0, foo)()

逗号操作符:从左往右求值,并返回最后一个值

(0, foo) 相当于 const bar = foo

似乎跟直接用 foo 调用没什么区别,但是在下面的代码中,就可以看出来不一样。

const obj = {
a: 1,
foo: function () {
return this.a
}
}
window.a = 2
obj.foo() // -> 1
(0, obj.foo)() // -> 2,此时的 this 指向的是 window

因为 (0, obj.foo)() 相当于 const bar = obj.foo; bar(),改变了 foo 的运行环境,所以 this 指向 window 了。

闭包的另一种思考

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)—— 闭包 - MDN

最近在看 《JavaScript 设计模式与开发实践》中提到一点也很有意思:

对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。

PS:不过我觉得这个描述有点奇怪,另外一点,在我看来(也不准确),对象写法的方法中,使用了 this.count ,也可以认为是其周围状态,所以也可以认为这是一种闭包技术吗?

闭包写法

通过 chrome 调试可以发现,会产生一个叫 Closure(foo) 的 Scope,内部是 { count: 0 }

function foo () {
let count = 0
return {
increase: () => {
count++
return count
}
}
}
const counter = foo()
counter.increase() // -> 1
counter.increase() // -> 2

对象写法

通过 chrome 调试可以发现,会产生一个叫 Closure(./src/index.js) 的 Scope,内部是 { count: 0, increase: increase() }

const counter = {
count: 0,
increase: function () {
this.count++
return this.count
}
}
counter.increase() // -> 1
counter.increase() // -> 2

一月

TS 的异常捕获

因为在 js 中,任何异常都有可能被抛出

throw "What a weird error";
throw 404;
throw new Error("What a weird error");

所以当你 catch(e: TypeError) 类似这样的时候 ts 会报 TS1196 Catch clause variable type annotation must be ‘any’ or ‘unknown’ if specified.

考虑下面的方式写:

try {
myroutine(); // There's a couple of errors thrown here
} catch (e) {
if (e instanceof TypeError) {
// A TypeError
} else if (e instanceof RangeError) {
// Handle the RangeError
} else if (e instanceof EvalError) {
// you guessed it: EvalError
} else if (typeof e === "string") {
// The error is a string
} else if (axios.isAxiosError(e)) {
// axios does an error check for us!
} else {
// everything else
logMyErrors(e);
}
}

Promise 也是一样的,reject 允许你传入任何类型。

IOS 滚动穿透问题

很早之前就遇到过这个问题,明明整个页面都 overflow:hidden 了,但是当 ios 虚拟键盘唤起时,整个页面确又可以滑动了。

可以通过阻止 touchemove 事件来避免。

const handler = e => e.preventDefault()

<input
onFocus={() => {
document.body.addEventListener('touchmove', handler, { passive: false })
}}
onBlur={() => {
document.body.removeEventListener('touchmove', handler, { passive: false })
}}
/>

e.preventDefault() 阻止默认事件。

passive 为 true 时, listener 将永远不会调用 preventDefault()(即为 true 时,是无法阻止默认事件的手动调用了 preventDefault 也不会有效果,控制台会警告)。

根据规范 passive 属性默认为 false(允许阻止)。但是在处理某些触摸事件时,可能会阻止浏览器的主线程,导致滚动处理的性能降低,所以有些浏览器将文档级节点 Window Document Document.body 的 touchStart touchMove 事件的 passive 默认值改为 true。