本周我们 55 人学了什么

7,093 阅读8分钟

程序员这行如果想一直做下去,那么持续学习是必不可少的。

大家找工作通常会喜欢技术氛围好点的团队,因为这样能够帮助自己更好的成长,但是并不是每个团队都拥有这样的氛围。于是萌发一个念头,想建立一个地方,让一些人能在这块地方记录自己学习到的内容。这些内容通常会是一个小点,可能并不足以写成一篇文章。但是这个知识点可能很多人也不知道,那么通过这种记录的方式让别人同样也学习到这个知识点就是一个很棒的事情了。

如果你也想参与这个记录的事情,欢迎贡献你的一份力量,地址在这里

本周总共有 55 人贡献了他们所学到的知识,以下是一些整合后的内容,更详细的内容推荐前往仓库阅读。

JS

解决键盘弹出后挡表单的问题

window.addEventListener('resize', function () {
if (
  document.activeElement.tagName === 'INPUT' ||
  document.activeElement.tagName === 'TEXTAREA' ||
  document.activeElement.getAttribute('contenteditable') == 'true'
) {
  window.setTimeout(function () {
    if ('scrollIntoView' in document.activeElement) {
      document.activeElement.scrollIntoView();
    } else {
      // @ts-ignore
      document.activeElement.scrollIntoViewIfNeeded();
    }
  }, 0);
}
})

图片加载相关

首先是实现图片懒加载

<ul>
	<li><img src="./img/default.png" data="./img/1.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/2.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/3.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/4.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/5.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/6.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/7.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/8.png" alt=""></li>
</ul>
let imgs =  document.querySelectorAll('img')
// 窗口可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
// img 距离窗口可视区顶部的距离 imgs[i].getBoundingClientRect().top
function lazyLoadImg () {
    for (let i = 0; i < imgs.length; i ++) {
        if((imgs[i].getBoundingClientRect().top + imgs[i].height)>=0&&imgs[i].getBoundingClientRect().top < clientHeight ){
            imgs[i].src = imgs[i].getAttribute('data')
        }
    }      
}
window.addEventListener('scroll', lazyLoadImg);

但是这种方式会引起图片下载过程中闪白一下,可以通过 JS 预先加载图片解决。

同时上述的懒加载解决方案已经很老了,可以使用最新的 API Intersection_Observer 来做这件事,会更简单而且可控一些。

无loop生成指定长度的数组

const List1 = len => ''.padEnd(len, ',').split('.')

const List2 = len => [...new Array(len).keys()]

异步的 Promise的 then 方法的回调是何时被添加到microtasks queue中的?

今天刷博客的时候看到一个题:

const pro = new Promise((resolve, reject) => {
    const pro1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
        }, 0);
    });
    resolve(4);
    pro1.then((args) => {
        console.log(args);
    });
});
pro.then((args) => {
    console.log(args);
});

很多人都知道这道题的输出结果是4,3;但是我对题主的这个问题产生了很大的疑问,因为个人并没有着手实现过符合promise A/A+规划的promise,所以每次做这种题都是凭着平时的使用经验,实际上内心虚得很,然后自己查阅了 spec:ECMAScript 2018 Language Specification 根据 spec,如果调用 then 时 promise 是 pending 状态,回调会进入 promise 的 [[PromiseFulfill/RejectReactions]] 列表里;否则会进入 PromiseJobs。

PromiseJob 以及 Job Queue 是 ES 中的说法,而 macroTask 和 microTask 是浏览器中的概念,包括 setTimeout 也是宿主环境提供的。因此输出 4 3 是 ECMAScript 和 浏览器两种规范共同约束的结果。

PromiseJob 对应浏览器中的一个 microTask。对于调用 then 时 promise 处于 pending 状态,回调函数进入到对应的 reactions 队列中。当该 promise 被 fulfill 或 reject 时,则 flush 对应的 reactions 队列 ,其中的每个 reaction 对应一个 PromiseJob 被按序 enqueue 到 Job Queue如果调用 then 时 promise 处于其他两个状态,JS 引擎就直接 enqueue 一个对应的 PromiseJob 到 Job Queue示例中的代码。

在浏览器中如下顺序:

0. current cycle of evevt loop start
1. Install Timer,Timer get enqueued
2. Resovle pro, because there is no fulfillReaction binding to pro, do nothing
3. call then() at pro1, because pro1 is pending, add fulfillReaction to pro1
4. call then() at pro, because pro is reolved,immediately enqueue a PromiseJob
5. current macroTask is finished
6. run all PromiseJobs(microTasks) in order, 
7. console.log(4)
8. current cycle of event loop is finishedanother cycle starts
9. Timer Fires, and pro1 is resolved
10. at this time, pro1 hasfulfillReactions,enqueue every fulfillReaction as a PromiseJob in order
11. current macro Job is finished 
12. run all PromiseJobs in order
13. console.log(3)
14. current cycle of event loop is finished

移动端打开指定App或者下载App

navToDownApp() {
      let u = navigator.userAgent
      if (/MicroMessenger/gi.test(u)) {
        // 如果是微信客户端打开,引导用户在浏览器中打开
        alert('请在浏览器中打开')
      }
      if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) {
        // Android
        if (this.openApp('en://startapp')) {
          this.openApp('en://startapp') // 通过Scheme协议打开指定APP
        } else {
          //跳转Android下载地址
        }
      } else if (u.indexOf('iPhone') > -1) {
        if (this.openApp('ios--scheme')) {
          this.openApp('ios--scheme') // 通过Scheme协议打开指定APP
        } else {
          // 跳转IOS下载地址
        }
      }
    },
    openApp(src) {
      // 通过iframe的方式试图打开APP,如果能正常打开,会直接切换到APP,并自动阻止a标签的默认行为
      // 否则打开a标签的href链接
      let ifr = document.createElement('iframe')
      ifr.src = src
      ifr.style.display = 'none'
      document.body.appendChild(ifr)
      window.setTimeout(function() {
        // 打开App后移出这个iframe
        document.body.removeChild(ifr)
      }, 2000)
    }

利用 a 标签解析 URL

function parseURL(url) {
    var a =  document.createElement('a');
    a.href = url;
    return {
        host: a.hostname,
        port: a.port,
        query: a.search,
        params: (function(){
            var ret = {},
                seg = a.search.replace(/^\?/,'').split('&'),
                len = seg.length, i = 0, s;
            for (;i<len;i++) {
                if (!seg[i]) { continue; }
                s = seg[i].split('=');
                ret[s[0]] = s[1];
            }
            return ret;
        })(),
        hash: a.hash.replace('#','')
    };
}

数组去重

  var array = [1, 2, 1, 1, '1'];
  function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
      return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
  }

利用一个空的 Object 对象,我们把数组的值存成 Object 的 key 值,比如 Object[value1] = true,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的。

因为 1 和 '1' 是不同的,但是这种方法会判断为同一个值,这是因为对象的键值只能是字符串,所以我们可以使用 typeof item + item 拼成字符串作为 key 值来避免这个问题

JS 函数对象参数的陷阱

上周在实现某个弹层功能的时候,用到了rc-util里的 contains 方法函数, 结果 code-review 的时候同事对该代码提出了疑问:

rc-util 源码仓库

export default function contains(root, n) {
  let node = n;
  while (node) {
    if (node === root) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
}

上述代码是 antd 内部抽象的一个工具方法,用来判断某个dom是否为另一个dom的祖先节点。

同事疑问的是 let node = n; 这段代码是不是多余的?

首先一开始的理解是 函数参数 n 是一个对象,一个dom节点对象。
如果用 node 保存 n 的值,防止 node = node.parentNode 这段代码执行的时候,会改变传入的实参 n 对应的值。

毕竟以下的代码我们都很熟悉:

function contains(root, n) {
  if(n) {
    n.a = 3
  }
}

const A = {a:1};
const B = {a:2};
contains(A,B)
console.log(B)    // {a:3}

即当实参为对象时,函数内部是可以改变该对象的值从而影响函数之外的实参。

但是测试另外一段代码,发现和理解的不一样:

function contains(root, n) {
  if(n) {
    n = {a:3}
  }
}

const A = {a:1};
const B = {a:2}
contains(A,B)
console.log(B) // {a:2}

n.a = 3n = {a:3} 这两段代码是不一样的。

网上也有相关资料,其实可以简单的理解为: 当函数一开始执行时,n 是指向实参 B 的一个引用.

n.a = 3 是在引用上关联了一个属性,此时和 B 还是同一个引用,因此会改变实参B的值。

n = {a:3} 则使得 n 不再指向实参 B, 而是指向一个新对象{a:3},也就是 nB 彻底断绝了关系,因此不会改变实参 B 的值。

是不是可以给蚂蚁的团队提个issue建议删除该代码,不过有这句代码也不会有什么bug~

相关资料:JavaScript深入之参数按值传递

其他

kill 指定端口

以下命令可以 kill 掉 8080 端口,当然你也可以选择通过 npm 命令的方式指定需要 kill 的端口。

lsof -i tcp:8080 | grep LISTEN | awk '{print $2}'| awk -F"/" '{ print $1 }' | xargs kill -9

另外以上命令在 windows 上是不可用的。如果有多平台的需求的话,可以直接使用 Kill-port-process

Linux下通过命令行替换文本

# 将wxml文件的i标签替换为text
grep '<i ' -rl . --include='*.wxml' --exclude-dir=node_module --exclude-dir=dist | xargs sed -i -e 's/<i /<text /g'
grep '</i>' -rl . --include='*.wxml' --exclude-dir=node_module --exclude-dir=dist | xargs sed -i -e 's/<\/i>/<\/text>/g'

如何判断文件中的换行符是 LF(\n) 还是 CRLF(\r\n)

文章链接,通过这篇文章可以了解到换行符到底是什么。

另外这位大佬每天都将学习到的知识记录了下来,感兴趣的可以 阅读一下

毕业半年感悟

  • 自身的实力最重要,要有一样核心技能,其他方面也要有所涉猎。
  • 公司带给个人的影响是很大的,如果一个公司不愿意培养你,真的不值得去付出。
  • 沟通确实很重要,沟通不明确会导致接下来一系列的问题。
  • 说话是后天锻炼出来的,多和人交流,话到嘴边留三分。
  • 不用讨厌加班,人与人拉开差距就在下班后的几个小时,加班可以学习啊。雷军还说过你拿3000块钱换我一个月的青春,多不划算。

最后

这周的分享内容质量很高,我也从中汲取到了一些知识。

这是一个需要大家一起分享才能持续下去的事情,光靠我一人分享是做不下去的。欢迎大家参与到这件事情中来,地址在这里