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
40
红31
41
绿 32
42
黄 33
43
蓝 34
44
品红 35
45
青 36
46
白 37
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 字段的替代方案。区别在于它支持条件导出。
主入口导出
一般建议同时定义 main
和 exports
。
注意:定义了 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 兼容方案
postcss-viewport-units
浏览器宽高相关的 API 以及区别
Window:
window.outerWidth
、 window.outerHeight
:
浏览器整个的宽和高,会包含书签栏、导航栏、控制台等,是浏览器窗口的宽高
window.innerWidth
、 window.innerHeight
:
是窗口的布局宽高( layout viewport ),包含了滚动条的宽度。(显示区的宽高,即使有元素超过了布局,产生滚动条了,也不会影响)
Element:
el.clientWidth
、el.clientHeight
:
元素的 content width + padding width(不包括滚动条)
比如 width: 100px, padding: 20px, 那么就是 140px,如果有 20px 的滚动条,那就是 120px
el.offsetWidth
、el.offsetHeight
:
元素的 content width + padding width + border width(包括滚动条)
el.offsetLeft
、el.offsetTop
:
元素的左侧与上侧的偏移量,相对于元素(以 offsetWidth 和 offsetHeight 组成的容器)左上角和视口比较
el.scrollWidth
、el.scrollHeight
:
当没有滚动条时,等同于 clientWidth,有了之后会加上整个超出的宽高。
el.scrollLeft
、el.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
在 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。