# 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 行时

  1. 临时创建一个 String 类型的实例 let t = new String("some text")
  2. 调用实例上的特定方法 s2 = t.substring(2)
  3. 销毁实例 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_weng_1.png

在音频增强中,均衡默认为平缓,下拉选择 嗡嗡声减弱 即可。这样噪音会好很多,声音越大越明显。声音调小后就感觉不到了。

final_cut_weng_2.png

# Final Cut Pro 视频加马赛克

在时间线中,选中要加马赛克的视频片段,点击右下角的效果图标,注意是转场按钮左边的图标。选择全部 => 搜索 删减。双击删减效果即可应用到选中的视频片段。

final_cut_mosaic_1.png

效果如下图,可以根据需要调整马赛克大小、位置。

final_cut_mosaic_2.png

# 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 来遍历

getElementsByTagName_1.png

我们换个思路,使用 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 方式使用

getElementsByTagName_2.png

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 遍历

NodeList_1.png

综上:getElementsByTagName 返回的结果为 HTMLCollection 类型,而 querySelectorAll 返回的结果为 NodeList。HTMLCollection 并没有实现 forEach,无法使用点语法执行forEach,而 NodeList 是可以的。

# JS数据类型有多少种,有哪些细节需要注意的?

ES3 有 5 种基本数据类型,1种复杂数据类型。ES6+ 后面新增了两种基本数据类型:Symbol, bigint

注意:

  1. typeof 函数值为 function, typeof null 值为 object,本质上其实有 9 种数据类型。
  2. new String('12') 是字符串对象,不是 string,new Number(1) 是数字对象,不是 number,new Boolean(false) 是对象,不是 boolean
  3. 新增的 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
上次更新: 2021/1/6 20:05:35