GithubHelp home page GithubHelp logo

justjavac / the-front-end-knowledge-you-may-not-know Goto Github PK

View Code? Open in Web Editor NEW
2.3K 308.0 120.0 2.78 MB

:innocent: 你可能不知道的前端知识点

Home Page: https://git.io/fjrsr

javascript html html5 css css3 chrome debug performance frontend

the-front-end-knowledge-you-may-not-know's Introduction

你可能不知道的前端知识点

为什么建这个 repo?

使用方式

GitHub 提供了一些方便的功能:

  • fork:慎用。当我写了新文章后,你 fork 的仓库并不会自动同步。所以除非是你想参与这个项目,比如提交一篇文章、修改错别字、等,否则你没有必要 fork 这个项目。

  • star:这个功能可以理解为收藏或点赞。如果你想收藏这个项目,以便将来某一天来学习,我劝你别这么做,相信我,Read It Later === Read It Never。另一方面,我的 star 已经够多了,所以除非是你确实觉得项目对你有帮助,否则你不需要点 star 按钮。

  • watch:推荐使用。点击 watch 旁边的下拉三角可以得到更多的 watch 选项,最有用的 2 个是:Releases only 和 Watching:

    • 选择 Watching,当本项目有任何动态时,你都会收到通知;
    • 选择 Releases only,只有发布新的 Release 版本时你才会收到通知。

    当有新文章时,我会不定期发布 Release 版本。

  • sponsor:GitHub 新上线的赞赏功能。如果我的项目帮助你找到了心仪的工作,或者让你升职加薪,可以请我喝杯咖啡

  • subscribe:这个功能是 issues 的,在每个 issue 的右面都有这个按钮。点击后可以关注此 issue 的动态。

推荐流程:

  1. 点击 watch,选择 Releases only
  2. 当发布了新的 Release 版本后,你会收到提醒。每个版本会包含几篇文章的链接。
  3. 当阅读完文章后,在每篇文章的最下面都附有讨论这篇文章的 issue,如果你对这篇文章感兴趣,进入相应的 issue,点击右侧的 subscribe。
  4. 祝阅读愉快。

注意:如果你选择 Releases only,则会错过一些新开的 issues。

目录

更多...

建议

关于碎片化阅读其实我是持反对意见的,碎片化阅读只能作为自己已有知识的补充,但是真正想学好前端,还是应该多看书,从头构建自己的完整知识体系,然后把碎片化阅读作为自己知识体系中知识点的补充。

License

知识共享许可协议
作品justjavac创作,采用知识共享署名-非商业性使用-相同方式共享 3.0 **大陆许可协议进行许可。凡是转载的文章,翻译的文章,或者由其他作者投稿的文章,版权归原作者所有。

the-front-end-knowledge-you-may-not-know's People

Contributors

ifunnybox avatar justjavac avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

the-front-end-knowledge-you-may-not-know's Issues

unhandledrejection 处理没有显式捕获的 Promise 异常

我们经常会写如下代码:

function main() {
    asyncFunc()
    .then(···)
    .then(() => console.log('Done!'));
}

上面的代码有一处问题,就是异步 asyncFunc() 函数可能会 reject,这时我们并没有捕获 Promise 的 reject 分支。

我们可以使用 window.addEventListener('unhandledrejection', event => ···); 来处理所有 Promise 的异常情况。

根据 MDN 的兼容性表格看,好像目前只有 Chrome 支持这个事件。在 can i use 网站也搜不到关于 unhandledrejection 的相关信息。

除了这个事件之外,还有一个 rejectionhandled 事件:表示 rejection 当时并没有立即处理,在之后的一段时间内处理了,此时触发 rejectionhandled 事件。

关于 Promise 的 Rejection 处理,Google 的 Chrome 团队有个演示页面:Promise Rejection Events Sample

即使异常被处理了,DevTools 依然会有错误信息:

image

如果需要禁用此错误信息,需要在 unhandledrejection 事件处理函数中调用:

event.preventDefault();

相关链接

!目录

  • scrollIntoViewIfNeeded、scrollIntoView 将当前元素滚动到可视区域 #3
  • DocumentFragment 文档片段,不会引起 DOM 树的重新渲染 #5
  • addEventListener 提升滚动性能 Passive event #6
  • unhandledrejection 处理没有捕获的 Promise 异常 #7
  • requestIdleCallback 空闲回调 #9
  • IntersectionObserver #10
  • MutationObserver 监听 DOM 变化 #11

window.performance 性能对象

用于评估性能的api,有些鸡肋
eg: performance.timing.domContentLoadedEventEnd - performance.timing.domContentLoadedEventStart

不打印出 console 信息出现在文件哪一行

每次用 console 函数调试页面的时候,都会显示出 在文件中哪一行调用的。
image

但某次去看 fackbook 的代码 却没有显示......经过不懈努力,终于还是找到了 他们是怎么玩转的~

image

setTimeout(console.log.bind(console, 'Hello world'), 2000)

对象解构可以应用在数组上

1、获取数组的长度:

const {0:a, 2:b, length:l} = ['foo', 'bar', 'baz']
a === 'foo'
b === 'baz'
l === 3

2、还可以使用此技巧获取数据最后一个元素:

const { length: l, [l-1]: last, ...rest } = [1, 2, 3]
l === 3
last === 3

只是 rest 变成了对象 {0: 1, 1: 2}

3、将数组转换为对象:

> const { ...obj } = [1,2,3]
> obj
{0: 1, 1: 2, 2: 3}

try-catch 退出 forEach 循环

try {
    [1, 2, 3].forEach(v => {
        if (v === 2) {
            throw new Error('my err')
        }
    })
} catch (e) {
    if (e.message === 'my err') {
        console.log('breaked') 
    } else {
        throw e
    }
}

pointer-events: none; 穿透当前元素,由“下面”的元素接收事件

简介

pointer-events: none;表示鼠标事件“穿透”该元素并且指定该元素“下面”的任何东西。

例:红色块上面有一个蓝色块(z-index),如果蓝色块设置了 pointer-events: none; 。点击蓝色块将无效,红色块能捕获到鼠标事件,虽然蓝色块“罩住”了红色块。

兼容性

https://caniuse.com/#search=pointer-events

qq20180502-101821 2x

实用程度

★★

相关链接

Syntax Error of the Title

Syntax Error: change the title 'the-front-end-knowledge-you-may-dont-know' to be 'the-front-end-knowledge-you-may-not-know' instead!

清空 input[type="file"] 的值

let resetFileDom = dom => {
    if (({}).toString.call(dom) !== '[object HTMLInputElement]') {
        throw new Error('必须传入DOM节点')
        return
    }
    dom.value = ''
    dom.type = 'text'
    dom.type = 'file'
}

IntersectionObserver

简介

  • 你想跟踪 DOM 树里的一个元素,当它进入可视区域时得到通知。
  • 你想实现延迟加载图片功能
  • 你需要知道用户是否真的在看一个广告 banner。

你可以通过绑定 scroll 事件或者用一个周期性的定时器,然后在回调函数中调用元素的 getBoundingClientRect() 获取元素位置实现这个功能。但是,这种实现方式性能极差,因为每次调用 getBoundingClientRect() 都会强制浏览器 重新计算整个页面的布局 ,可能给你的网站造成相当大的闪烁。

IntersectionObserver 就是为此而生的,它可以检测一个元素是否可见,IntersectionObserver 能让你知道一个被观测的元素什么时候进入或离开浏览器的视口。

兼容性

  • Chrome 51+(发布于 2016-05-25)
  • Android 5+ (Chrome 56 发布于 2017-02-06)
  • Edge 15 (2017-04-11)
  • iOS 不支持

Polyfill

WICG 提供了一个 polyfill

Chrome
Firefox
Safari
6+
Edge
Internet Explorer
7+
Opera
Android
4.4+

实用程度

★★★★

相关链接

获取图片原始尺寸

let getImgRawSize = (img, cb) => {
    img = img || document.querySelector(img)
    if (!img instanceof HTMLImageElement) {
        return console.log(`error getting ${img} dom`)
    }

    if (img.naturalWidth) {
        return cb({width: img.naturalWidth, height: img.naturalHeight})
    }
    if (img.complete) {
        return cb({width: img.width, height: img.height})
    }
    let _image = new Image
    _image.src = img.src
    _image.onload = _ => {
        cb({width: _image.width, height: _image.height})
    }
}

CSS 新属性 contain: 允许开发者限定浏览器 style、layout、paint的工作范围

简介

先上一张图看性能:

Containment

当我们对一个页面进行布局时,性能瓶颈通常是 style、layout、paint。

一般情况下,浏览器会把 整个 DOM 作为 CSS 布局上下文,因此:当我们改变部分 DOM 的样式时,也会影响到其他部分,并且没有什么方式来告诉浏览器哪些内容应该在范围内哪些应该在范围外。

假设你有如下页面结构:

<section class="view">
  Home
</section>

<section class="view">
  About
</section>

<section class="view">
  Contact
</section>

现在在 view 里面新增一些元素,会影响页面的 style、layout、paint:

<section class="view">
  Home
</section>

<section class="view">
  About
  <div class="newly-added-element">Check me out!</div>
</section>

<section class="view">
  Contact
</section>

在上面的示例中,所有的**节点(whole DOM)**均被影响,这就意味着,不论元素的 style、layout、paint 是否改变,都会重新计算他们的样式和布局。

现代主流的浏览器都会做一些智能的判断,最终决定哪些需要改变,哪些不需要改变。

不过,页面布局是很复杂的,浏览器不一定能判断到所有的情况。好消息是,一个新的 CSS 属性 contain 把判断的事情交给了开发者。

兼容性

Chrome
52+
Firefox
Safari
Edge
Internet Explorer
Opera
40+
Android
6+

实用程度

★★★★

相关链接

scrollIntoViewIfNeeded 与 scrollIntoView

MDN 文档:

Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内。而 Element.scrollIntoViewIfNeeded()Element.scrollIntoView() 的变体,如果该元素已经在浏览器窗口的可见区域内,则不会发生滚动

  • 当元素已经在可视区域时,调用 Element.scrollIntoView(),无论设置什么参数,均发生滚动。
  • 当元素已经在可视区域时,调用 Element.scrollIntoViewIfNeeded(),无论设置什么参数,均发生滚动。

!须知

这个 repo 的目的是前端知识点的讨论、归纳、整理、使用。话题会放到 issues 区,任何人都可以围观讨论。

最终内容整理成文章、PPT、PDF,甚至是电子书。

此 repo 不是留言板,也不是灌水区,不要发布无意义的 issues。

对某个主题感兴趣,可以在相关的 issue 右边点击 Subscribe 按钮。

image

利用 URLSearchParams 对象获取URL之中的查询字符串,即问号之后的部分

简介

URLSearchParams API 用于处理 URL 之中的查询字符串,即问号之后的部分。没有部署不支持这个 API 的浏览器,可以用 url-search-params 这个垫片库。

之前自己一直用 qs 这个库来处理,建议最好不要自己写,自己写有些坑你会考虑不到的==。

用法

URLSearchParams 有以下方法,用来操作某个参数。

has():返回一个布尔值,表示是否具有某个参数
get():返回指定参数的第一个值
getAll():返回一个数组,成员是指定参数的所有值
set():设置指定参数
delete():删除指定参数
append():在查询字符串之中,追加一个键值对
toString():返回整个查询字符串

const paramsString = 'name=jawil&age=24';
const searchParams = new URLSearchParams(paramsString);
console.log(searchParams.get('name')); // jawil

URLSearchParams 还有三个方法,用来遍历所有参数。

keys():遍历所有参数名
values():遍历所有参数值
entries():遍历所有参数的键值对

上面三个方法返回的都是 Iterator 对象。

const searchParams = new URLSearchParams('name=jawil&age=24');

for (let key of searchParams.keys()) {
  console.log(key);
}
// name
// age

for (let value of searchParams.values()) {
  console.log(value);
}
// jawil
// 24

for (let pair of searchParams.entries()) {
  console.log(pair[0]+ ', '+ pair[1]);
}
// name, jawil
// age, 24

Chrome 浏览器之中,URLSearchParams 实例本身就是 Iterator 对象,与 entries 方法返回值相同。所以,可以写成下面的样子。

for (let p of searchParams) {
  console.log(p);
}

下面是一个替换当前 URL 的例子。

// URL: https://example.com?version=1.0
const params = new URLSearchParams(location.search.slice(1));
params.set('version', 2.0);

window.history.replaceState({}, '', `${location.pathname}?${params}`);
// URL: https://example.com?version=2.0

URLSearchParams 实例可以当作 POST 数据发送,所有数据都会 URL 编码。

const params = new URLSearchParams();
params.append('api_key', '1234567890');

fetch('https://example.com/api', {
  method: 'POST',
  body: params
}).then(...)

DOMa 元素节点的 searchParams 属性,就是一个 URLSearchParams 实例。

const a = document.createElement('a');
a.href = 'https://example.com?filter=api';
a.searchParams.get('filter') // "api"

URLSearchParams 还可以与 URL 接口结合使用。

const url = new URL(location);
let foo = url.searchParams.get('foo') || 'somedefault';

兼容性

https://caniuse.com/#search=URLSearchParams

实用程度

★★★★★

怎样阻止谷歌浏览器的密码提示?

您好,我想请教您一个问题,当一个网页输入用户名和密码的时候,提交表单之后,谷歌浏览器会弹出“是否保存密码”的对话框,当下次再登入的时候,就会自动提示上次输入的密码,前端如何取消密码提示呢?

qq 20180609100925

ResizeObserver 监听元素的尺寸变化

监听元素的尺寸变化

var ro = new ResizeObserver( entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;
    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

参考链接

利用toString检测浏览器devTool是否为开启状态

f = function(){}
f.toString = function () {
    // do something when devTool is opened
}
console.log('%c', f);

关于检测 devTool 是否为打开状态当前已经有好多方案了,比如轮询检测 window 的内外宽高差、对 html 元素注册 id 的 getter 等,但是这些方法要不具有侵入性,要不无法满足 devTool 是悬浮状态的情况,上面的方法可能更好一下,亲测 chrome 和 ff 没问题。

requestIdleCallback 当浏览器处于闲置状态时,调度工作的新的性能相关的 API

原文: Using requestIdleCallback
译文: 使用requestIdleCallback

略有改动

这是 Google 2015年8月的文章


如今,大多数的站点和 app 都需要执行很多的 JavaScript 脚本。你的 JavaScript 通常需要尽可能快地执行,而且,你又不希望通过获取用户行为的方式来达成目的。如果当用户滚动页面的时候,你的 JavaScript 开始上报数据,或者当用户点击按钮的时候,你往 DOM 中添加元素,你的 web 应用其实就已经变得迟钝,导致很差的用户体验。

现在有个好消息,一个新的 API 能够帮助你: requestIdleCallback 。跟 requestAnimationFrame 一样,requestAnimationFrame 允许我们正确地安排动画,同时最大限度地去提升到 60fps。而requestIdleCallback 则会在某一帧结束后的空闲时间或者用户处于不活跃状态时,处理我们的工作。这表明在不获取用户行为条件下,你能执行相关的工作。目前这个新的 API 在 Chrome Canary(M46+) 下可用(需要打开 chrome://flags/#enable-experimental-web-platform-features 去开启该功能),这样你从今天开始先尝试玩玩。但要记着,这个 API 是一个实验性的功能,该规范仍在不断变化,所以任何东西都可能随时改变。

为什么我要使用 requestIdleCallback?

靠自己人工的安排不必要的工作是很困难的。比如,要弄清楚一帧剩余的时间,这显然是不可能的,因为当 requestAnimationFrame 的回调完成后,还要进行样式的计算,布局,渲染以及浏览器内部的工作等等。上面的话貌似还不能说明什么。为了确保用户不以某种方式进行交互,你需要为各种交互行为添加监听事件(scrolltouchclick),即使你并不需要这些功能,只有这样才能绝对确保用户没有进行交互。另一方面,浏览器能够确切地知道在一帧的结束时有多少的可用时间,如果用户正在交互,通过使用requestIdleCallback 这个 API,允许我们尽可能高效地利用任何的空闲时间。

接下来让我们看看它的更多细节,且让我们知道如果使用它。

检查 requestIdleCallback

目前 requestIdleCallback 这个 API 仍处于初期,所以在使用它之前,你应该检查它是否可用。

if ('requestIdleCallback' in window) {
  // Use requestIdleCallback to schedule work.
} else {
  // Do what you’d do today.
}

现在,我们假设已经支持该 API

使用 requestIdleCallback

调用 requestIdleCallback 跟调用 requestAnimationFrame 十分相似,它需要把回调函数作为第一个参数:

requestIdleCallback(myNonEssentialWork);

myNonEssentialWork 被调用,会返回一个 deadline 对象,这个对象包含一个方法,该方法会返回一个数字表示你的工作还能执行多长时间:

function myNonEssentialWork (deadline) {
  while (deadline.timeRemaining() > 0)
    doWorkIfNeeded();
}

调用 timeRemaining 这个方法能获得最后的剩余时间,当 timeRemaining() 返回 0,如果你仍有其他任务需要执行,你便可以执行另外的 requestIdleCallback

function myNonEssentialWork (deadline) {
  while (deadline.timeRemaining() > 0 && tasks.length > 0)
    doWorkIfNeeded();

  if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

确保你的方法已被调用

当事件很多的时候,你会怎么做?你可能会担心你的回调函数永远不被执行。很好,尽管requestIdleCallbackrequestAnimationFrame 很像,但它们也有不同,在于 requestIdleCallback 有一个可选的第二个参数:含有 timeout 属性的对象。如果设置了 timeout 这个值,回调函数还没被调用的话,则浏览器必须在设置的这个毫秒数时,去强制调用对应的回调函数。

// Wait at most two seconds before processing events.
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });

如果你的回调函数是因为设置的这个 timeout 而触发的,你会注意到:

  • timeRemaining() 会返回 0
  • deadline 对象的 didTimeout 属性值是 true

如果你发现 didTimeouttrue,你的代码可能会是这样子的:

function myNonEssentialWork (deadline) {

  // Use any remaining time, or, if timed out, just run through the tasks.
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0)
    doWorkIfNeeded();

  if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

因为设置 timeout 对你用户导致的潜在破坏(这个操作会使你的 app 变得迟钝且低质量),请小心地设置这个参数。所以,在这,就让浏览器自己去决定什么时候触发回调吧。

使用 requestIdleCallback 去上报数据

让我们试试用 requestIdleCallback 去上报数据。在这种情况下,我们可能希望去跟踪一个事件,如“点击导航栏菜单”。然而,因为通常他们是通过动画展现在屏幕上的,我们希望避免立即发送事件到 Google Analytics,因此我们将创建一个事件的数组来延迟上报,且在未来的某个时间点会发送出去。

var eventsToSend = [];

function onNavOpenClick () {

  // Animate the menu.
  menu.classList.add('open');

  // Store the event for later.
  eventsToSend.push(
    {
      category: 'button',
      action: 'click',
      label: 'nav',
      value: 'open'
    });

  schedulePendingEvents();
}

现在我们使用 requestIdleCallback 来处理那些被挂起的事件。

function schedulePendingEvents() {

  // Only schedule the rIC if one has not already been set.
  if (isRequestIdleCallbackScheduled)
    return;

  isRequestIdleCallbackScheduled = true;

  if ('requestIdleCallback' in window) {
    // Wait at most two seconds before processing events.
    requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
  } else {
    processPendingAnalyticsEvents();
  }
}

上面代码中,你可以看到我设置了2秒的超时,但取决于你的应用。因为对于上报的这些分析数据,设置一个 timeout 来确保数据在一个合理的时间范围内被上报,而不是延迟到某个未知的时间点。这样做才是合理且有意义的。

最后我们来写下 requestIdleCallback 执行的回调方法:

function processPendingAnalyticsEvents (deadline) {

  // Reset the boolean so future rICs can be set.
  isRequestIdleCallbackScheduled = false;

  // If there is no deadline, just run as long as necessary.
  // This will be the case if requestIdleCallback doesn’t exist.
  if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

  // Go for as long as there is time remaining and work to do.
  while (deadline.timeRemaining() > 0 && eventsToSend.length > 0) {
    var evt = eventsToSend.pop();

    ga('send', 'event',
        evt.category,
        evt.action,
        evt.label,
        evt.value);
  }

  // Check if there are more events still to send.
  if (eventsToSend.length > 0)
    schedulePendingEvents();
}

这个例子中,我假设如果不支持 requestIdleCallback,则立即上报数据。然而,对于一个在生产环境的应用,最好是用 timeout 延迟上报来确保不跟任何相互冲突。

使用 requestIdleCallback 改变 dom

requestIdleCallback 可以帮助提高性能的另一个场景是,当你需要做一些非必要的 dom 改动,比如懒加载,滚动页面时候不断在尾部添加元素。让我们看看 requestIdleCallback 事实上是如何插入一帧里的。

对于浏览器,在给定的一帧内因为太忙而没有去执行任何回调这是有可能的,所以你不应该期望在一帧的末尾有空闲的时间去做任何事。这一点就使得 requestIdleCallbacksetImmediate 不太像,setImmediate 是在每一帧里都会执行。

如果在某一帧的末尾,回调函数被触发,它将被安排在当前帧被 commit 之后,这表示相应的样式已经改动,同时更最重要的,布局已经重新计算。如果我们在这个回调中进行样式的改动,涉及到的布局计算则会被判无效。如果在下一帧中有任何的读取布局相关的操作,例如 getBoundingClientRectclientWidth等等,浏览器会不得不执行一次强制同步布局(Forced Synchronous Layout),这将是一个潜在的性能瓶颈。

另一个不要在回调中触发 Dom 改动的原因是,Dom 改动是不可预期的,正因为如此,我们可以很容易地超过浏览器给出的时间限期。

最佳的实践就是只在 requestAnimationFrame 的回调中去进行 dom 的改动,因为浏览器会优化同类型的改动。这表明我们的代码要在 requestIdleCallback 时使用文档片段,这样就能在下一个
requestAnimationFrame 回调中把所有改动的 dom 追加上去。如果你正在使用 Virtual DOM 这个库,你可以使用 requestIdleCallback 进行 Dom 变动,但真正的 Dom 改动还是在下一个 requestAnimationFrame 的回调中,而不是 requestIdleCallback 的回调中。

所以谨记上面说的,下面来看下代码吧:

function processPendingElements (deadline) {

  // If there is no deadline, just run as long as necessary.
  if (typeof deadline === 'undefined')
    deadline = { timeRemaining: function () { return Number.MAX_VALUE } };

  if (!documentFragment)
    documentFragment = document.createDocumentFragment();

  // Go for as long as there is time remaining and work to do.
  while (deadline.timeRemaining() > 0 && elementsToAdd.length > 0) {

    // Create the element.
    var elToAdd = elementsToAdd.pop();
    var el = document.createElement(elToAdd.tag);
    el.textContent = elToAdd.content;

    // Add it to the fragment.
    documentFragment.appendChild(el);

    // Don't append to the document immediately, wait for the next
    // requestAnimationFrame callback.
    scheduleVisualUpdateIfNeeded();
  }

  // Check if there are more events still to send.
  if (elementsToAdd.length > 0)
    scheduleElementCreation();
}

在上面,我创建了一个元素,而且使用添加上了 textContent 这个属性。但这时候还不应该把元素追加到文档流中去。创建完元素添加到文档片段后,scheduleVisualUpdateIfNeeded 则被调用,它会创建一个 requestAnimationFrame 的回调,这时候,我们就应该把文档片段追加到 body 中去了:

function scheduleVisualUpdateIfNeeded() {

  if (isVisualUpdateScheduled)
    return;

  isVisualUpdateScheduled = true;

  requestAnimationFrame(appendDocumentFragment);
}

function appendDocumentFragment() {
  // Append the fragment and reset.
  document.body.appendChild(documentFragment);
  documentFragment = null;
}

一切顺利的话,我们则会看到追加 dom 到文档中时,并没有什么性能的损耗。真 tm 的棒!

用fetch在控制台测试接口

模拟post方法进行测试,不一定总是要用postMan呢

fetch(apiUrl, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({q: 1})
}).then(async res => console.log(await res.json()))

使用 DocumentFragment 提升性能

DocumentFragment 接口表示一个没有父级文件的最小文档对象。

因为 DocumentFragment 不是真实 DOM 树的其中一部分,它的变化不会引起 DOM 树的重新渲染操作(reflow) ,因此不会导致性能问题。

当我们需要修改多个节点时,可以创建一个 DocumentFragment,在此 Node 节点进行添加(append)或被插入(inserted)操作。因为所有的节点会被一次性插入到文档中,而这个操作仅发生一个重渲染的操作,而不是每个节点分别被插入到文档中,因为后者会发生多次重渲染的操作。

documentFragment 被所有主流浏览器支持。

MDN 文档说**似乎IE 6/7/8也支持documentFragment!**

image

随后又写道:

怀疑 caniuse 上给出的数据是错误的,因为起码在 IE8 上这个 document.createDocumentFragment 是可以被调用的。

我刚才查了 can i use 的数据,好像 IE9 以下并不支持。但是 IE9 能够支持已经足够了。


相关文档:


性能测试

appendChild vs. DocumentFragment vs. innerHTML

另一个测试,使用 DocumentFragment 反而会更慢。

image

为什么使用了 DocumentFragment 不但没有变快,反而变慢了呢?

我们看测试代码就知道原因了,为 <select> 添加 option,使用 fragment 的目的是为了防止页面的 reflow 操作,但是给 <select> 添加 option 根本就不会引起页面的 reflow。

Preflighted requests 可以算一个不?

本人小菜一枚。这个知识点其实蛮基础的,估计在大大们看来肯定很简单。

最近接了一个老项目,配置好跑起来。network面板一看,一大坨空请求,因为要调优,肯定是要去掉的。但诡异的是竟然找不到在哪发的,最后展开method列看看都是options请求。然后查查资料发现还有这玩意,还是蛮惊奇的。附链接

小技巧:已知年月,求该月共多少天?

在写日历组件时,曾遇到 已知年月,求该月共多少天? 这样的需求。

最开始思路会是:

  • 先判断该年份是否是闰年,来处理 2 月份情况,闰年 2 月共 29 天,非闰年 2 月共 28 天
  • 再判断其他月份,如 1 月共 31 天,4 月共 30 天

代码就不一一列出了,思路代码啥的没啥问题。

这里其实有种更简便的方法,借助 Date API 处理日期溢出时,会自动往后推延响应时间的规则,直接上代码:

// month 值需对应实际月份减一,如实际 2 月,month 为 1,实际 3 月,month 为 2
function getMonthCountDay (year, month) {
  return 32 - new Date(year, month, 32).getDate()
}

验证下:

// 求闰年的 2 月份总天数
getMonthCountDay(2000, 1) // 29
// 求 1 月份总天数
getMonthCountDay(2000, 0) // 31
getMonthCountDay(2001, 0) // 31
// 求 4 月份总天数
getMonthCountDay(2000, 3) // 30
getMonthCountDay(2001, 3) // 30

我也来推荐一个吧 Intl实例的运用

  1. 字符串对比:

exp: 排序'我','爱', 以往我们会用localeCompare进行,但是也可以用

new Intl.Collator().compare('我','爱');

PS: 推荐在字符串超长时,或者长字符串数组时使用

  1. 数字格式化:
    exp: 将1000转化为1,000,这个特性大大有用啊 再也不用自己手写了
new Intl.NumberFormat().format(1000); // '1,000'

PS: 当然也可以使用:

既然楼下有提到,就都写出来吧:
console.log(99757..toLocaleString('en-US')); //99,757
//正则
const test = num => String(num).replace(/(^|\s)\d+/g, s => s.replace(/(?=(?!\b)(\d{3})+$)/g, ','))

  1. 时间格式化:
    exp: 将时间格式化为2017-12-3
new Intl.DateTimeFormat().format(new Date()); //'2017-12-3';

PS: 也可以使用

console.log(new Date().toLocaleDateString()); //兼容性好点

PS:当然每个都有更加高级的配置,可以自定制,这个太多了,记不住,用的时候再去查就好了,现在赖得去查,各位看官也可以自己去瞅瞅ecmascript文件;

JS取整

大家都知道 parseInt 可以取整,那么下面这两个也是可以的哦

~~3.123 === 3;// true
~~4.3423 === 4;//true
0|3.123;//3
0|4.3423 ;//4

请看 JJC 在 TFC 大会上的抽奖代码:

~~(Math.random() * 50 + 0)

抛砖引玉

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.