# 2020年11月技术日常
# 2020/11/30 周一
# git 修改文件内容后误删了该文件,再使用 git 还原该文件,修改的文件内容丢失怎么办?
最近修改某个文件后,还没将改动提交到 git,中途不小心删了该文件。然后想着恢复文件,就使用 git 恢复该文件。这个时候发现未提交的修改内容都丢失了,文件内容是 git 上最近的内容。
这种情况,理论上 git 是无法还原的。因为没有提交,git不会记录对应的内容。我们不能总是想着使用 git 怎么恢复,需要换一下思路。修改的文件删除后,默认会存在废纸篓(回收站),去里面找即可。
# 使用 hash 滚动页面时,被顶部 fix 区域遮挡怎么解决
一般页面 URL 的 hash 值变化后,如果当前页面中存在 id 为该 hash 值的元素,页面会滚动到该区域。下面的例子中,点击跳转到h1,页面会滚动到 h1 元素,元素对齐到顶部。这里有一个问题,如果顶部导航栏是 fixed 固定的,那使用这种方法滚动时,h1 标题会被导航栏遮挡。
<h1 id="h1">一级标题</h1>
<a href="#h2">跳转到h2</a>
// 假设这里有很长的内容,超过一页
<h2 id="h2">二级标题</h2>
<a href="#h1">跳转到h1</a>
之前在写 zuo-blog 时,我的解决方案是:滚动到顶部后,再往下滚动 70 px,这种方法有一个缺点,就是只能是 JS 跳转,如果直接通过 a 标签访问,还是会被遮挡
document.getElementById(id).scrollIntoView(true)
document.documentElement.scrollBy(0, -70)
后来使用 VuePress 时,发现跳转的时候没有遮挡,于是审查元素,看了下样式。发现使用一个 css 就可以解决这个问题。 给需要定位到该 id 的元素加一个超过导航栏高度的 padding-top,再加一个保持元素位置的 margin-top,这样使用 hash 滚动到该 id 元素时,就不会有遮挡,会与顶部保持 padding 的距离
h1,h2,h3,h4,h5,h6 {
margin-top: -3.1rem;
padding-top: 4.6rem;
margin-bottom: 0;
}
我写了一个测试 demo,可以看对应的效果,完整 demo 参见 顶部 fix 遮挡 hash demo | Github (opens new window)
<style>
body {
margin: 0;
}
.top {
position: fixed;
height: 60px;
width: 100%;;
border-bottom: 1px solid #ccc;
background-color: #fff;
}
.main {
padding-top: 80px;
}
/* fix 顶部遮挡的问题 */
/* h1,h2 {
padding-top: 100px;
margin-top: -90px;
} */
</style>
<body>
<div class="top">顶部区域</div>
<div class="main">
我是主内容区域
<h1 id="h1">一级标题</h1>
<a href="#h2">跳转到h2</a>
<div>
<ul>
<li>列表a</li>
<!-- 省略多行... -->
<li>列表a</li>
</ul>
<h2 id="h2">二级标题</h2>
<a href="#h1">跳转到h1</a>
<ul>
<li>列表a</li>
<!-- 省略多行... -->
<li>列表a</li>
</ul>
</div>
</div>
</body>
# 2020/11/22 周二
# 字符串原始类型不是对象,为什么可以使用点语法运行方法
理论上,原始值本身不是对象,逻辑上是不能有属性、方法的。下面的例子中,第 2 行可以正常执行。主要是 JS 内部在后台进行了一些处理
let s1 = "some text"
let s2 = s1.substring(2) // "me text"
在执行到第 2 行时
- 临时创建一个 String 类型的实例
let t = new String("some text")
- 调用实例上的特定方法
s2 = t.substring(2)
- 销毁实例
t = null
这种行为可以让原始值拥有对象的行为。临时创建的 原始值包装类型(String)实例的声明周期只在执行的那一行。执行完后,就销毁了。下面的例子即可说明这个问题。
let s1 = "some text"
s1.color = "red"
console.log(s1.color) // undefined
更多详情参考:《JavaScript高级程序设计》第四版 p113 原始值包装类型。
# 前端低代码框架 amis,通过 JSON 配置生成页面
什么是低代码开发平台? (LCDP,Low-Code Development Platform),是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。
对于比较简单的、通用的页面可以自动生成代码。amis 就是这样的一个框架。可以根据 JSON 配置,自动生成页面,而且还支持在线可视化编辑。对于简单、通用、不需要定制化开发的情况,可以直接使用。
参考:
# DOMContentLoaded 与白屏, performance timing
白屏时间 = 地址栏输入网址后回车 - 浏览器出现第一个元素
首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成
一般页面 白屏结束 的时间节点在 head 结束,body 开始执行时。可以通过 window.performance.timing
这个对象来看具体时间。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>白屏</title>
<script>
// 不兼容 performance.timing 的浏览器
window.pageStartTime = Date.now()
</script>
<!-- 页面 CSS 资源 -->
<link rel="stylesheet" href="xx.css">
<link rel="stylesheet" href="zz.css">
<script>
// 白屏结束时间
window.firstPaint = Date.now()
// 白屏时间
console.log(firstPaint - performance.timing.navigationStart)
</script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
白屏时间 = firstPaint - performance.timing.navigationStart || pageStartTime
因此:在 head 元素里面如果放了非 async 或 defer 的 JS,会增加白屏时间。
DOMContentLoaded 是页面元素(dom)完成加载并解析,而不用等 css样式、图片和子 frame 完全加载完成时触发。对应 jQuery 的 $(document).ready()
Load 事件是当整个页面加载完成时(包括所有相关资源,例如css样式表和图片)触发,对应 jQuery 里面的 $(document).load()
参考:
# JS 事件循环, Promise.then 与 setTimeout 执行顺序问题
来看一个 demo 的执行顺序
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i === 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
执行顺序是:2 3 5 4 1,我们知道 setTimeout 与 Promise.then 的回调都是异步的。setTimeout 的执行函数是先 push 到任务队列的,而 Promise.then 的回调是后面 push 的。为什么 Promise.then 的回调先执行呢?这里涉及到 JS 事件循环中 宏任务 与 微任务。
- 宏任务:同步任务、I/O(比如文件读写等)、setTimeout、setInterval 等
- 微任务:Promise.then/catch/finally、generator、async/await 等
某个宏任务执行 ok 后,会先看微任务事件队列里是否有任务,有就执行,然后才是宏任务队列。
参考:js 事件循环消息队列和微任务宏任务 (opens new window)
# Promise.all/race 实现中函数参数简写
一般函数当参数时,有一种情况可以直接简写,例子如下
func(data => {
console.log(data)
})
可以简写为
func(console.log)
下面来看看在 Promise.all 以及 Promise.race 中的应用
class MyPromise {
static all(array) {
return new MyPromise((resolve, reject) => {
let successCount = 0
let resultArr = []
for (let i = 0; i < array.length; i++) {
let promise = MyPromise.resolve(array[i])
promise._then((data) => {
successCount++
resultArr.push(data)
successCount === array.length && resolve(resultArr)
}, (err) => {
reject(err)
})
// 函数参数 (err) => { reject(err) } 等价于 reject
}
})
}
static race(array) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
let promise = MyPromise.resolve(array[i])
promise._then(resolve, reject)
// 函数参数 (err) => { reject(err) } 等价于 reject
}
})
}
}
# 2020/11/16 周一
# Final Cut Pro 嗡嗡声噪音消除
在时间线区域,选中视频片段或声音片段 => 点击右上角的检查器区域的音频检查器图标,如下图
在音频增强中,均衡默认为平缓,下拉选择 嗡嗡声减弱 即可。这样噪音会好很多,声音越大越明显。声音调小后就感觉不到了。
# Final Cut Pro 视频加马赛克
在时间线中,选中要加马赛克的视频片段,点击右下角的效果图标,注意是转场按钮左边的图标。选择全部 => 搜索 删减。双击删减效果即可应用到选中的视频片段。
效果如下图,可以根据需要调整马赛克大小、位置。
# 2020/11/08 周日
# getElementsByTagName 为什么不可以 forEach ?和 querySelectorAll 有什么区别
在 JS DOM 编程艺术的例子中,看到 getElementsByTagName
后使用了 Array.from(xx)
将其结果转为标准数组后再使用 forEach
。
下面写个例子来验证为什么?
<nav>
<ul>
<li><a href="index.html">Home</a></li>
<li><a href="about.html">About</a></li>
</ul>
</nav>
<script>
let nav = document.getElementsByTagName('nav')[0]
let linkArr = nav.getElementsByTagName('a')
console.log(linkArr) // HTMLCollection [] => Object
console.log(linkArr[0].__proto__) // HTMLAnchorElement
// HTMLAnchorElement => HTMLElement => Element => Node => EventTarget => Object
// <nav> HTMLElement
// <li> HTMLLIElement
console.log(linkArr[0].nodeType, linkArr[0].nodeName) // 1 "A"
console.log(linkArr.__proto__) // HTMLCollection []
// Uncaught TypeError: linkArr.forEach is not a function
linkArr.forEach(item => console.log(item))
</script>
getElementsByTagName返回结果类型
从上面的例子中,我们可以看到 getElementsByTagName
函数返回的数据类型是 HTMLCollection
,它是直接使用 Object 创建的对象,并没有实现 forEach 方法,但它内部实现了遍历、for...of(Symbol.iterator)方法,因此可以使用 for、for...of 来遍历
我们换个思路,使用 Array.prototype.forEach.call
来调用试试
// forEach 基本用法
[1,2,3].forEach((item, index, array) => console.log(item, index, array))
// 使用 prototype.forEach 直接运行
let cb = (item, index, array) => console.log(item, index, array)
Array.prototype.forEach.call(linkArr, cb)
可以正常执行,执行结果如下图。因此 Array 下的一些方法,我们可以使用 prototype 方式使用
querySelectorAll返回结果类型
同样是获取元素列表,querySelectorAll
返回的结果类型是 NodeList
// 和 querySelectorAll 对比
let linkArr2 = document.querySelectorAll('li a')
console.log(linkArr2) // NodeList [] => Object
console.log(linkArr2[0].__proto__) // HTMLAnchorElement
console.log(linkArr2[0].nodeType, linkArr2[0].nodeName) // 1
console.log(linkArr2.__proto__) // NodeList []
linkArr2.forEach(item => console.log(item))
如下图,NodeList 类型实现了 forEach, for...of 等方法,所以可以直接使用 forEach 遍历
综上:getElementsByTagName 返回的结果为 HTMLCollection
类型,而 querySelectorAll 返回的结果为 NodeList
。HTMLCollection 并没有实现 forEach,无法使用点语法执行forEach,而 NodeList 是可以的。
# JS数据类型有多少种,有哪些细节需要注意的?
ES3 有 5 种基本数据类型,1种复杂数据类型。ES6+ 后面新增了两种基本数据类型:Symbol, bigint
注意:
- typeof 函数值为
function
, typeof null 值为object
,本质上其实有 9 种数据类型。 - new String('12') 是字符串对象,不是 string,new Number(1) 是数字对象,不是 number,new Boolean(false) 是对象,不是 boolean
- 新增的 Symbol 以及 bigint 都是不能 new 的,没有 constructor 构造函数方法
var a = null, // null
b = undefined, // undefined
c = false, // bollean
d = 1, // mumber
e = "123", // string
f = {}, // object
g = Symbol("3"), // symbol
h = BigInt(4); // 4n bigint
[a, b, c, d, e, f, g, h].forEach(item => console.log(`typeof `, item, `: ${typeof item}`))
// typeof null : object
// typeof undefined : undefined
// typeof false : boolean
// typeof 1 : number
// typeof 123 : string
// typeof {} : object
// typeof Symbol(3) : symbol
// typeof 4n : bigint