Giter Site home page Giter Site logo

blog's Introduction

akibear

Profile views

Aki

blog's People

Contributors

akiq2016 avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

reflow-repain

CSS重绘与回流

浏览器渲染过程

我们先来讨论一下浏览器在接收到HTML、CSS和JavasSript后,怎样把页面呈现在屏幕上的?
不同的浏览器的渲染过程存在些许不同,但大体的机制是一样的,下图展示了浏览器下载完所有代码后的大致工作流程:
img

  • 首先,浏览器解析HTML源码并构建一个DOM树:在DOM树中,每个HTML标签都有相应的节点,并且在介于两个标签中间的文字块也对应一个text节点。DOM树的根节点是documentElement,也就是标签;
  • 然后,浏览器对CSS代码进行解析,一些当前浏览器不能识别的CSS hack写法(如-webkit前缀)将被忽略。CSS样式中包括浏览器默认样式(user agent stylesheet),用户自定义样式表(通过 / import引入的外部样式&行内样式)。最终样式会写入HTML标签的style属性中;
  • 接着,构建render树。render树跟DOM树有点像但不完全一样。render树能识别样式。假如你用display: none隐藏一个div,这个标签不会在render树中出现。这个规则适用于其他不可视元素,比如head标签等;另外,一个DOM元素在render树中可以有多个节点,比如代表p标签的一个文本节点中的每一行文字,又有一个渲染节点。render树中的节点叫做frame-结构体/box-盒子,这些节点都有CSS盒子属性:width, height, border, margin 等等
  • 最后,render树构建完毕,浏览器便开始将渲染节点绘制到屏幕上。

森林和树

<html>
<head>
  <title>Beautiful page</title>
</head>
<body>
    
  <p>
    Once upon a time there was 
    a looong paragraph...
  </p>
  
  <div style="display: none">
    Secret message
  </div>
  
  <div><img src="..." /></div>
 
</body>
</html>

这个HTML文档对应的DOM树:每个标签对应一个节点,以及每个标签之间的文本也为一个节点。(实际上,空白区域也会被映射为一个text节点,为了简单说明,在此忽略)。因此DOM树:

documentElement (html)
  head
    title
  body
    p
      [text node]
      
    div 
      [text node]
  
    div
      img

render树包含了DOM树的可视部分。因此他丢掉了一些东西,比如头部head标签和隐藏的div,同时他也为文本块增加了节点(又称作frames,boxs)。因此render树:

root (RenderView)
  body
    p
      line 1
      line 2
    div
      img

渲染树的root根节点是一个包括了所有其他节点的结构体(盒子)。你可以将它理解为浏览器窗口的内部区域,毕竟页面被限制在这个区域内。从技术上,WebKit把root节点称为RenderView(渲染视图),他与CSS初始包含块相对应,从坐标(0,0)到(window.innerWidth,window.innerHeight)。

接下来,我们将研究浏览器是如何通过循环遍历渲染树把页面展示到屏幕上的。

WHAT - Repaints and reflows

你的页面中至少存在一个初始页面布局,并且和一次绘制动作。当然这仅仅是第一次绘制,在此之后,在用户的交互行为中,页面结构以及CSS可能会有变化。任何影响到渲染树的行为都会触发以下一种或者多种动作:

  • render树的局部或全部需要重新验证,节点大小需要重新计算。这种行为成为reflow回流。请注意这里存在至少一次reflow行为:就是初始化页面布局时的那次。
  • 屏幕的部分区域需要进行更新:可能是因为节点的几何结构改变,或样式改变(如背景色变化)。这种屏幕更新动作叫repaint/redraw。

重绘和回流可能是昂贵的,它们可能会伤害用户体验,并使用户界面显得迟钝。

WHEN - 触发重绘/回流的机制

任何影响到构造渲染树的行为都会触发repaint或reflow,例如

1.DOM元素的增删改
2.通过display:none隐藏节点(重绘+回流),通过visibility:hidden隐藏(重绘,因为没有几何结构的改变)
3.节点的移动、动画
4.样式表的增删改
5.浏览器窗口变化(滚动或缩放)
······(待补充)

举个栗子:

// cache
var bstyle = document.body.style;

// reflow, repaint
bstyle.padding = "20px"; 
// another reflow and a repaint
bstyle.border = "10px solid red";
 
// repaint only
bstyle.color = "blue";
// repaint only
bstyle.backgroundColor = "#fad";

// reflow, repaint
bstyle.fontSize = "2em";

// reflow, repaint (new DOM element)
document.body.appendChild(document.createTextNode('dude!'));

可见,repaint是指元素的样式改变不影响文档流整体结构时,渲染树结构也就没有变化,因此仅仅是重新显示样式。重绘的代价是比较小的。注意,这并不是说样式改变不会导致回流,只是特定样式改变,才不会导致回流。第二个需要注意的点是,reflow一定需要repaint,但是repaint却不需要reflow。

/*  以下Css Property改变  */

1.background-color

2.color

3.visibility

······(待补充)

有些reflow行为要比其他的花销大一些。比如你对body中最后的一个直属子元素乱搞,你可能不会影响到什么其他的节点,但是如果你对body中最前面的一个节点添加动画,或者改变这个div的尺寸,这就会将后面跟着的所有元素都推下去了,这种行为是非常消耗性能的。

HOW - 减少重绘和回流: 开发优化策略

由于reflows和repaints带来的render树的改变会导致昂贵的性能消耗,而浏览器的目标就是减少这种副作用。浏览器的策略就是不执行/推迟执行。他会设置一个队列用来存放这些行为变动的需求,并且一次性执行他们。也就是说,存在多个需要reflow的动作会被合并为一个reflow动作。浏览器将这些动作加入到缓存队列中,当到达一定的时间间隔,或者累积了足够多个后执行它们。

但是,有时候某些的代码会破坏上述的浏览器优化机制,导致浏览器刷新缓存队列并且执行所有已缓存的操作行为。这种情况发生在获取下面这些样式信息的行为中:

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
getComputedStyle(), 或者IE下的currentStyle

以上的行为本质上是获取一个节点的样式信息,浏览器必须提供最新的值。为了达到此目的,浏览器需要将缓存队列中的所有行为全部执行完毕,并且被强制回流。所以,在一条逻辑中同时执行set和get样式操作时非常不好的,如下:

el.style.left = el.offsetLeft + 10 + "px";
// 最终只有一次重绘和回流被触发
var $body = $('body');
$body.css('padding', '1px'); // 触发重绘与回流
$body.css('color', 'red'); // 触发重绘
$body.css('margin', '2px'); // 触发重绘与回流

//两次回流
var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // 此处触发强制回流
$body.css('color', 'red');
$body.css('margin', '2px');

减少reflows/repaints引起的用户体验上的负面影响的策略是尽量少的引起reflows/repaints,以及尽量少的请求获得样式信息,由此,浏览器则可以利用其机制优化合并reflows行为。那么怎么做呢?

  • 不要一个一个的去改样式。最明智及可维护的是去改变class名,而不是样式。但是这种是指静态的样式修改。假如样式是动态变化的,可以选择修改cssText,而不是每次有变动就直接操作元素的每个style属性。
// bad
var left = 10,
    top  = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// better 
el.className += " theclassname";

// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  • "离线"处理多个DOM操作。“离线”的意思是将需要进行的DOM操作脱离DOM树,比如:
1.用documentFragment集中处理临时操作;
2.将需要更新的节点克隆,在克隆节点上进行更新操作,然后把原始节点替换为克隆节点;
3.先通过设置display:none将节点隐藏(此时出发一次回流和重绘),然后对隐藏的节点进行100个操作(这些操作都会单独触发回流和重绘),完毕后将节点的display恢复显示(此时再次触发一次回流和重绘)。通过这种方法,将可能存在的多次repaints/reflows缩减为2次。
  • 不要过度进行计算样式的操作。如果你需要用到一个样式值,请用局部变量储存,然后利用这个局部变量进行相关操作。例如:
// bad
for(big; loop; here) {
  el.style.left = el.offsetLeft + 10 + "px";
  el.style.top  = el.offsetTop  + 10 + "px";
}
 
// better
var left = el.offsetLeft,
    top  = el.offsetTop
    esty = el.style;
for(big; loop; here) {
    left += 10;
    top  += 10;
    esty.left = left + "px";
    esty.top  = top  + "px";
}

理解浏览器重绘以及回流的主要目的是为了优化性能。当你在打算改变样式时,首先考虑一下渲染树的机制,并且评估一下你的操作会引发多少刷新渲染树的行为。例如,浏览器认为 position 为 absolute 或 fixed 的元素更改只会影响其本身和子元素,而 static 的元素变化则会影响之后的所有元素,也就是说,一个绝对定位的节点是会脱离文档流,所以当对此节点应用动画时不会对其他节点产生很大影响,当绝对定位的节点置于其他节点上层时,其他节点只会触发重绘,而不会触发回流。

怎样使用devtools查看回流和重绘

分析绘制

渲染性能

无线性能优化:Composite

GPU Accelerated Compositing in Chrome

学习资料

Rendering: repaint, reflow/relayout, restyle (以上内容97%翻译自此原文)

[翻译]浏览器渲染Rendering那些事 (以上内容参考了此翻译文章,并修正了不准确的翻译或错误部分)

deep clone

function getType(data) {
  let type = Object.prototype.toString.call(data).slice(8, -1).toLocaleLowerCase();

  if (type === 'object') {
    if (data instanceof Set) {
      type = 'set';
    } else if (data instanceof Map) {
      type = 'map';
    } else if (data instanceof Function) {
      type = 'function';
    }
  }

  return type;
}

function cloneArr(res, data, hash) {
  data.forEach((item) => {
    res.push(cloneDeep(item, hash));
  });
  return res;
}

function clonePlainObject(res, data, hash) {
  for (let key in data) {
    res[key] = cloneDeep(data[key], hash);
  }
  return res;
}

function cloneSet(res, data, hash) {
  Array.from(data, (val) => {
    res.add(cloneDeep(val, hash));
  })
  return res;
}

function cloneMap(res, data, hash) {
  Array.from(data, ([key, value]) => {
    res.set(cloneDeep(key, hash), cloneDeep(value, hash))
  })
  return res;
}

/**
 * @param {any} data
 */
function cloneDeep(data, hash = new WeakMap()) {
  let res;

  // handle circular scene
  if (hash.has(data)) {
    return hash.get(data);
  }

  if (typeof data === 'object') {
    res = new data.constructor();
    hash.set(data, res);
  }

  switch (getType(data)) {
    case 'array': res = cloneArr(res, data, hash); break;
    case 'object': res = clonePlainObject(res, data, hash); break;
    case 'set': res = cloneSet(res, data, hash); break;
    case 'map': res = cloneMap(res, data, hash); break;
    default: res = data; break;
  }

  return res;
}

let a = {
  a: 1,
  b: 2,
  c: {
    cc: 1,
    dd: [1, 2, 3],
    ee: {
      gg: '213'
    },
    hh: {
      ii: {
        name: 'jjjj',
      }
    }
  },
  i: new Set([1,2,2,3, { a: 1 }]),
  j: new Map([
    ['key', 'value'],
    [['key'], 'value'],
  ])
};
a.h = a;
a.c.hh.kk = a.c.hh;

let b = cloneDeep(a);
console.log(a)
console.log('----')
console.log(b);
console.log(a.a === b.a === true)
console.log(a.b === b.b === true)
console.log(a.c === b.c === false)
console.log(a.c.cc === b.c.cc === true)
console.log(a.c.dd === b.c.dd === false)
console.log(a.c.ee === b.c.ee === false)
console.log(a.c.ee.gg === b.c.ee.gg === true)
console.log(a.i === b.i === false)
console.log(a.j === b.j === false)
console.log(a.h === a, b.h === b )

不遵循html的模板规范引发的一系列问题

在编写 wxml-loader 时,试图添加压缩的功能,主要是利用 html-minifier 来做的。但在编译时以下情况报错。这是开发者在 wxml 中写预留字符,却靠插值来写的情况。

<view>500元≤累积销售业绩{{'<'}}1000元</view>

因为开发者发现直接写成以下情况,小程序的 wxml 解析器会报错。小程序的 wxml 是基于 xml 解析的,不像 html 有强大的容错机制。写成上面一种情况,小程序就会把插值中的部分,交给 js 来解析,而非 wxml 解析器。

<view>500元≤累积销售业绩<1000</view>

但是,经过其他工具,比如 html-minifier 的时候,这个 < 依旧会被 parser 理解为标签开头,会报 Parse Error 的错误。

起初,想在不改变代码的前提下,调整一下 minifier 的配置,加了 ignoreCustomFragments = [/{{[\s\S]*?}}/] 的参数。用来忽略插值表达式片段。加上这个配置后,原本编译正常的引号相关内容,却出错了,导致 wxml 不可用。

<!-- input(注意单引号双引号): -->
<div class="{{a?'aa':'bb'}}">123</div><div class='{{a?"aa":"bb"}}'>123</div>

<!-- 预期output(原样输出): -->
<div class="{{a?'aa':'bb'}}">123</div><div class='{{a?"aa":"bb"}}'>123</div>

<!-- 实际output(后面的插值表达式变成了双引号): -->
<div class="{{a?'aa':'bb'}}">123</div><div class="{{a?"aa":"bb"}}">123</div>

查找源码,看似有关的代码是这一段:
https://github.com/kangax/html-minifier/blob/583e0861ee852a76acb1e8ec00b3de35a024927d/src/htmlminifier.js#L592

  if (!options.preventAttributesEscaping) {
    if (typeof options.quoteCharacter === 'undefined') {
      var apos = (attrValue.match(/'/g) || []).length;
      var quot = (attrValue.match(/"/g) || []).length;
      attrQuote = apos < quot ? '\'' : '"';
    }
    else {
      attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
    }
    if (attrQuote === '"') {
      attrValue = attrValue.replace(/"/g, '&#34;');
    }
    else {
      attrValue = attrValue.replace(/'/g, '&#39;');
    }
  }
  emittedAttrValue = attrQuote + attrValue + attrQuote;

我目前并没有填写 quoteCharacter 这个配置,而根据代码,他做了单双引号比多比少,来决定属性引号应该用什么的操作。这个操作表示不理解。但是基于代码的命名和逻辑,我猜测这个主要是处理属性转义的问题,然后我将 preventAttributesEscaping 配置设置成了 true 来规避这个问题,能正常编译了,并且暂时没有发现引发其他问题。

问题似乎解决了,我们再来猜测这段代码为什么这样写。查看了 html 相关的规范:https://html.spec.whatwg.org/multipage/syntax.html#attributes-2

对于单引号属性值语法、双引号属性值语法,有规定:

Single-quoted attribute value syntax
The attribute name, followed by zero or more ASCII whitespace, followed by a single U+003D EQUALS SIGN character, followed by zero or more ASCII whitespace, followed by a single U+0027 APOSTROPHE character ('), followed by the attribute value, which, in addition to the requirements given above for attribute values, must not contain any literal U+0027 APOSTROPHE characters ('), and finally followed by a second single U+0027 APOSTROPHE character (').

Double-quoted attribute value syntax
The attribute name, followed by zero or more ASCII whitespace, followed by a single U+003D EQUALS SIGN character, followed by zero or more ASCII whitespace, followed by a single U+0022 QUOTATION MARK character ("), followed by the attribute value, which, in addition to the requirements given above for attribute values, must not contain any literal U+0022 QUOTATION MARK characters ("), and finally followed by a second single U+0022 QUOTATION MARK character (").

基于单引号的语法规范,我们画个图来快速理解下:
image

  1. 属性名 name
  2. 后面可以跟着0或若干个空格
  3. 然后是等号
  4. 然后可以是0或若干个空格
  5. 然后是一个单引号
  6. 属性值 value,值中不可以有单引号
  7. 然后是一个单引号

总的来说,意思就是:使用单引号时,属性值中不能包含单引号;使用双引号时,属性值中不能包含双引号。

例子A

测试一下以下两种写法:

a.innerHTML = '<div testsome  = "a"aa">123</div>'
a.innerHTML = '<div testsome  = "a\"aa">123</div>'

实际html都会解析成:

image

也就是只能写相应的字符实体。

  • 双引号:字符实体为 &quot; 或者 &#34;,对应的字符是 " 。
  • 单引号:字符实体为 &apos; 或者 &#39;,对应的字符是 ' 。

例子B

测试一下以下写法:

a.innerHTML = '<div testsome  = "a&quot;aa">123</div>'

实际html会被展示成:

image

基于对规范的了解,再回看以下源码,就可以明白这一段的含义:在没有指定单双引号值的前提下,尝试检查属性值中是否含有双引号或单引号,以此来推测,当前属性值是用双引号还是单引号括着的。假如值内,有且主要是单引号,那外部肯定是用双引号,反之亦然,确定好属性引号后,再将属性值中含有的相关引号转换成字符实体,以免造成前面例子A中的不在预期内的解析。

  if (!options.preventAttributesEscaping) {
    if (typeof options.quoteCharacter === 'undefined') {
      var apos = (attrValue.match(/'/g) || []).length;
      var quot = (attrValue.match(/"/g) || []).length;
      attrQuote = apos < quot ? '\'' : '"';
    }
    else {
      attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
    }
    if (attrQuote === '"') {
      attrValue = attrValue.replace(/"/g, '&#34;');
    }
    else {
      attrValue = attrValue.replace(/'/g, '&#39;');
    }
  }
  emittedAttrValue = attrQuote + attrValue + attrQuote;

那么由于我们设置了 ignoreCustomFragments,将所有插值表达式忽略掉了,那么根据逻辑,当前的属性引号就会被认为应该取双引号,导致了我们前面的bug。而为了解决这个bug,我们又把禁止转义选项也设置为了真,那么基于以上的规范理解,其实这么做也是会存在例子A的隐患的。因此最正确的选择应该是改业务代码,特殊字符应该用字符实体来代替。

小程序

前面的解析都很有道理,但是都是基于 wxmlhtml 底层基本类似的前提下做的论断。但是实际上, wxml 的解析器经测试发现,并不支持实体字符,实体字符会被当成普通字符内容展示。因此以下写法并不能达到预期展示500元≤累积销售业绩<1000元

<view>500元≤累积销售业绩&lt;1000元</view>

也就是确实只能写成

<view>500元≤累积销售业绩{{'<'}}1000元</view>

然后,html-minifier对应配置好 ignoreCustomFragmentspreventAttributesEscaping 参数。

length

笔记

Array实例的length属性的数据属性:

{
  [[Writable]]: true,
  [[Enumerable]]: false,
  [[Configurable]]: false
}

length的属性值在[+0, Math.pow(2,32)-1]的范围内,并且在数值上始终大于每个数组中的可配置属性的名称(这些可配置属性的名称是数组索引,即为整数索引)。

对Array实例的length属性进行其他赋值会报如下错误

Uncaught RangeError: Invalid array length

principles-for-preparing-cv

STAR面试法

STAR面试法的依据是“过去的行为是未来行为的最好预言”,就是多问过去,少问将来,从过去的行为中判断是否是真实的,有效的,而不是应聘者的夸夸其谈。SITUATION(背景)、TASK(任务)、ACTION(行动)和RESULT(结果)

自荐简历一定要署名

如果自荐简历的话,一定要在邮件标题、简历附件文件名上写清楚,谁、应聘什么岗位、建议层级(初级、资深、专家)

务必要有 word 或 pdf 文件的简历

一页 HTML 简历源码能表现的东西实在有限,毕竟我们现在不招初级(P5及以下)前端了,所以 word 和 pdf 格式的载体足够了,多花精力放在简历内容上。

简历要表现出亮点,最好有作品

全栈通才固然好,每个方向都深入有点太难为人,但至少某一个方向要非常精钻非常深入。所以简历里要重点标出这个长处。实际上九成的简历里的“技能”部分几乎都是一样的,都差不多把前端用到的技术罗列一遍,毫无意义的占据简历中的黄金地段。

所以,既然全栈不易,不如把自己最擅长的那个语言或技术高亮加粗出来,面试的时候我就直接绕过其他,专从这个“高亮加粗”部分来做一些技术探底了,以此来引导面试官来问你擅长的部分,更有利于你扬长避短。

技术探底一般怎么做?看作品!如果擅长 Node,就看看 NPM 上提交的模块,install 下来演示下,讲讲核心代码。如果是一个组件,就直接看 Demo,讲设计封装思路。如果是一套脚手架工具,直接画架构图,讲原理和适用场景。如果是项目,那就 show 下你作为 PM 发的 Release 邮件!作品不仅仅是这些,也可以是技术专利、设计方案、成功的带人案例、成功的团建案例等,只要内容充实接地气,脉略(可以是代码、也可以是一件事)清晰就对了。

所以,简历里要适当带有这些储备好的作品,备着面试时狂秀一下。

突出重点,展示能力

具体说就是描述每个项目时,不仅要有背景、过程,还要有你为这个项目带来的结果。比如这样描述项目:我做了 XX 项目重构,用了 XX 技术方案,克服了 XX 的困难,最终让 XX 这类需求变更可以短平快的被消化,极速研发、快速上线、且数据采集也做到了标准化,研发成本大大降低,数据积累增长迅速,比如 XX ,最后在 XX 产品线中开始推广。

在面试时,这种描述很容易引出有针对性的话题来聊,也正因为简历里有这些铺垫,可以节省大量的面聊时间,更快做出判断。

相比之下,这种描述就不好:XX 项目前端研发,用了 AngluarJS,整个项目独立完成,包括整个 Boss 系统的受理收费、商品零售、小灵通短信群发、有线电视代收费、营业员/营业点结账等模块。这段描述只传达出一个信息,就是你用过 AngularJS。至于当初面临多少种技术选型,如何做取舍选择了 AngluarJS,这些系统模块之间如何基于 AngularJS 进行耦合,有没有涉及前端架构,AngularJS 用的有多深,项目成员分工怎样,如何并行研发?遇到过哪些你认为有价值的问题,并针对此提出应对方案,是代码解决还是组织解决?这个过程要靠面试的时候问答大半天才能搞清楚,问不出来的,基本也就跳过了。要是简历里表达出来,脑子里很嘹喨,面聊也不会很拖沓,能力评价自然会很高。

再补充一个,有好多人有写博客写总结的习惯,这个习惯非常好,如果某个总结能和简历里做的项目结合起来,就更好了。

全绿的 Github 提交记录

简历里附上 Github,最好提交记录那里泛绿的厉害,如果进到 Github 首页就想点开项目列表,找项目去读,如果看到有那种长时间维护的、Readme 写的清楚的、一本正经的写上“转载注明出处的”,基本上抓起电话就开聊了,结果基本上不会差。

其他关注和不关注

关注:每次换工作的离职原因,可量化的工作成果,能代表你最高水平的项目要写详细,标红加粗下划线,在简历中强调出来。

不关注:证书、四六级、了解的编程语言(如果不精通就不要写)、你不擅长什么、个人简介。

典型问题

  1. 哪个项目让你最满意、代表你的最高水平?如何做的?
  2. 让你印象最深刻的一个(技术)难点,害的你搞了很久,最后怎么解的,有什么心得?
  3. 你做的时间最久的一个项目(或产品),你看到这个项目有哪些问题,你能做什么?
  4. 你能给我们团队或者产品带来什么?

how-to-set-cursor-in-drag-related-events

I got a problem recently. when I write a custom drag item, I want it to show 'move' cursor while dragging. So first I think about giving style like:

draggable-item {
  cursor: move;
}

But it doesn't work, cause while we dragging, the default action of dragover event is to reset the current drag operation to 'none', which makes the dragging cursor show a disabled cursor icon. So I add the below code:

document.addEventLister('dragover', e => {
  e.preventDefault()
})

Well, it doesn't go like what I wish. After doing above stuff. It shows an arrow-like cursor. The specific type can be set by dropEffect, but all is the arrow-like cursor, which means do not have the values provided by cursor in CSS. Here is my current code:

class DraggableItem extends HTMLElement {
}

class DraggableWrapper extends HTMLElement {
  draggingItem: DraggableItem
  oldPositionIndex: number

  constructor() {
    super()

    this.addEventListener('dragstart', ({ target, dataTransfer }): void => {
      if (!(target instanceof DraggableItem)) return

      this.draggingItem = target
      this.oldPositionIndex = this.getItemIndexInList(target)

      target.style.cssText = 'opacity: 0.5'
      dataTransfer.effectAllowed = 'move'
    })

    this.addEventListener('dragenter', ({ target }): void => {
      if (!(target instanceof DraggableItem)) return

      const newDragIndex = this.getItemIndexInList(target)
      if (newDragIndex === this.oldPositionIndex) return

      if (newDragIndex < this.oldPositionIndex) {
        this.insertBefore(this.draggingItem, target)
      } else if (newDragIndex > this.oldPositionIndex) {
        target.nextElementSibling
          ? this.insertBefore(this.draggingItem, target.nextElementSibling)
          : this.appendChild(this.draggingItem)
      }
      this.oldPositionIndex = newDragIndex
    })

    this.addEventListener('dragend', (e): void => {
      this.draggingItem.style.cssText = ''
    }, false)
  }

  getItemIndexInList (item: DraggableItem): number {
    return Array.from(this.querySelectorAll('draggable-item'))
      .findIndex(v => v === item)
  }
}

customElements.define('draggable-item', DraggableItem)
customElements.define('draggable-wrapper', DraggableWrapper)

document.addEventListener("dragover", e => {
  e.preventDefault()
}, false)

go to sleep! to be continued..

throttle-debounce

Why Debounce

浏览器中某些计算和处理要比其他的昂贵的多。例如,DOM操作比起非DOM交互需要更多的内存和CPU时间。连续尝试进行过多的DOM相关操作可能会导致浏览器挂起,有时候甚至会崩溃。尤其在IE中使用onresize事件处理程序的时候容易发生,当调整浏览器大小的时候,该事件连续触发。在onresize事件处理程序内部如果尝试进行DOM操作,其高频率的更改可能会让浏览器崩溃。

How Debounce

// 去抖背后的基本**是,某些代码不可以在没有间断的情况连续重复执行。
// 第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。
// 当第二次调用该函数时,它会清除前一次的定时器并设置另一个。
// 如果前一个定时器已经执行过了,这个操作就没有任何意义。
// 然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。
// 目的是只有在执行函数的请求停止了一段时间之后才执行。

function debounce (method, context) {
  clearTimeout(method.tid)
  method.tid = setTimeout(function () {
    method.call(context)
  }, 100)
}

function test1 () { console.log('yo, hzfe') }
window.onresize = function () { debounce(test1) }

underscore - debounce 源码分析

// Returns a function, that, as long as it continues to be invoked, will not be triggered.
// The function will be called after it stops being called for N milliseconds.
// If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
  var timeout, result;

  var later = function(context, args) {
    timeout = null;
    if (args) result = func.apply(context, args);
  };

  var debounced = restArgs(function(args) {
    // 如果已有定时器 则清除
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      var callNow = !timeout;
      // 此处的回调函数later不会被执行进func的apply方法,因为没有args
      // immediate为真的情况下 仅在wait秒内,反复调用的第一次触发一个func
      // 之后在wait毫秒以内再次进入此处时,timeout已存在,later函数就用于重新记录新的定时器
      timeout = setTimeout(later, wait);
      // timeout为null 对func的第一次调用
      if (callNow) result = func.apply(this, args);
    } else {
      timeout = _.delay(later, wait, this, args);
    }

    return result;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};

Why Throttle

简单的说,函数节流能使得连续的函数执行,变为固定时间段间断地执行。还是以 scroll 事件为例,如果不加以节流控制:轻轻滚动下窗口,控制台打印了 N 多个 hello world 字符串。如果 scroll 回调不是简单的打印字符串,而是涉及一些 DOM 操作,这样频繁的调用,低版本浏览器可能就会直接假死,我们希望回调可以间隔时间段触发。

How Throttle

// 比如当 scroll 事件刚触发时,打印一个 hello world,然后设置个 1000ms 的定时器
// 此后每次触发 scroll 事件触发回调,如果已经存在定时器,则回调不执行方法
// 直到定时器触发,handler 被清除,然后重新设置定时器。

Throttle = function (method, context) {
  return function () {
    if (method.tid) return
    method.tid = setTimeout(function () {
      method.call(context)
      method.tid = null
    }, 1000)
  }
}
function test2 () { console.log('hello world') }
window.onscroll = Throttle(test2)

underscore - throttle 源码分析

// Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
// Normally, the throttled function will run as much as it can,
// without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass `{leading: false}`.
// To disable execution on the trailing edge, ditto.
_.throttle = function(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};

  var later = function() {
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  var throttled = function() {
    var now = _.now();

    // 首次触发时 若leading=false 则previous为当前时间戳
    // 目的是让remaining为wait毫秒,不会立即触发func
    if (!previous && options.leading === false) previous = now;

    var remaining = wait - (now - previous);
    context = this;
    args = arguments;

    // 如果remaining <= 0 或者 remaining > wait(表示客户端系统时间被调整过)时
    // 1.如果存在定时器,把定时器清除 + 重置id
    // 2.立即执行func,并将这次触发throttled方法的时间戳保存
    // 3.如果不存在定时器,把上下文 + 参数列表重置
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    }

    // 不存在定时器 且未指定 options.trailing = false
    // 1.则在remaining毫秒后执行later
    else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };

  throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = context = args = null;
  };

  return throttled;
};

difference between throttling and debouncing

Throttling enforces a maximum number of times a function can be called over time.
As in "execute this function at most once every 100 milliseconds."

Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called.
As in "execute this function only if 100 milliseconds have passed without it being called."

学习资料

The Difference Between Throttling and Debouncing

Debouncing and Throttling Explained Through Examples

JavaScript 函数节流和函数去抖应用场景辨析

underscore 函数去抖的实现

underscore 函数节流的实现

JSON.stringify()

奇淫技巧

有个面试题 - 实现一个remove函数,删除对象属性,有这样的方法

var test1 = {
  a: 123,
  b: 'bbb',
  c: false
}
// 删除属性
test1.b = undefined
JSON.parse(JSON.stringify(test1))

console.log(test1) // {a: 123, c: false}

引申分析 - JSON的值类型

在 JSON 的名称 - 值对中,值可以是字符串、数字、对象、数组、布尔值、null。

value

引申分析 - JSON.stringify(value[,replacer[,space]]) - value

The undefined value is not rendered.
The null value is rendered in JSON text as the String null.
The true value is rendered in JSON text as the String true.
The false value is rendered in JSON text as the String false.
  1. NaN || Infinity 将被显示为 String null.
  2. 其他的非JSON值类型(例如undefined、function)将不会生成字符串,而是生成了undefined。
    • 在数组中,这类值最终显示为String null。
    • 在对象中,拥有这类值的属性最终将被排除在被序列化的字符串外。
JSON.stringify(NaN)
// "null"

JSON.stringify(Infinity)
// "null"

JSON.stringify(undefined)
// undefined

JSON.stringify(function () {})
// undefined

JSON.stringify([ function(){} ])
// "[null]"

JSON.stringify({ a:  function(){} })
// "{}"

总结

因此,假如存在对象a如下,序列化后得到"{"a":null,"b":null}",也可以理解其中的缘由惹。同时也可以发现,本笔记开头的面试题JSON.parse(JSON.stringify())这种删除属性的方法,是存在坑的,需要根据实际场景谨慎使用。

var a = {
  a: NaN,
  b: Infinity,
  c: undefined,
  d: function() {}
}

JSON.stringify(a)
// "{"a":null,"b":null}"

学习资料

JSON
Ecma-262

my-apps

Mac

  • sublime
  • vscode
  • pycharm
  • Charles
  • wireshark
  • iterms
  • sourcetree
  • ilocker
  • Dr.Cleaner
  • Timing
  • Telegram
  • Skype
  • Grammarly
  • tunnelBear
  • shadowsocks
  • Photoshop
  • sketch
  • postman
  • LICEcap
  • the Unarchiver
  • appcleaner

redux-learning

tutorial

Actions

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

interface Action {
  type: string;
  [propName: string]: any; // It'd be better to pass as little data in each action as possible.
}

Action creators are exactly that—functions that create actions. In Redux, action creators simply return an action:

const ADD_TODO = 'ADD_TODO'
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Reducers

Reducers specify how the application's state changes in response to actions sent to the store. Remember that actions only describe the fact that something happened, but don't describe how the application's state changes.

// Reducer is a pure function. What a reducer do:
(previousState, action) => newState

As a pure function, here's are things you should never do inside a reducer:

  1. Mutate its arguments;
  2. Perform side effects like API calls and routing transitions;
  3. Call non-pure functions, e.g. Date.now() or Math.random().

That's said, given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

For example:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}
  1. We don't mutate the state. We create a copy with Object.assign(). You can also enable the object spread operator proposal to write { ...state, ...newState } instead.
  2. We return the previous state in the default case. It's important to return the previous state for any unknown action.

while your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state. The combineReducers helper function can turn an object whose values are different reducing functions into a single reducing function you can pass to createStore.

Store

You'll only have a single store in a Redux application. The store has the following responsibilities:

  1. Holds application state;
  2. Allows access to state via getState();
  3. Allows state to be updated via dispatch(action);
  4. Registers listeners via subscribe(listener);
  5. Handles unregistering of listeners via the function returned by subscribe(listener).
import { createStore } from 'redux'
import reducers from './reducers'
let store = createStore(reducers)

Demo1

Having the above knowledge, a simple store's update logic can be like this:

import {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters
} from './actions'

// Log the initial state
console.log(store.getState())

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// Stop listening to state updates
unsubscribe()

device-adaptation

移动端适配

  • 基于 CSS 的适配
    • 媒体查询
    • 视口相关的CSS单位
  • 响应式图像
    • HTML 5.2中的 picture 元素
    • HTML 5.2中的 srcset 属性
    • SVG

首先先捋清楚一些概念

分辨率

分辨率是显示器中设备物理像素的度量,通常以宽度x高度的测量值表示。 例如,1920 x 1080的显示器横跨1920像素,向下1080像素。

DPI / PPI

DPI: Dot’s per inch. The number of dots in a printed inch. The more dot’s the higher the quality of the print (more sharpness and detail).

PPI: Pixels per inch. Most commonly used to describe the pixel density of a screen (computer monitor, smartphone, etc…) but can also refer to the pixel density of a digital image.

不同物理设备屏幕,有不同的像素密度。比如对比不同屏幕上,同等一英寸的空间中,可以显示多少像素。会发现像素密度越大,显示的内容越清晰。因此存在单位 DPI (dots-per-inch) 以及 PPI (pixels-per-inch)。

不同设备可以存在不同的DPI值,但太多不同的DPI会导致设计开发的适配变得繁琐。为简化起见,设备屏幕被分组到称为 logic pixel densities 的类别中。比如一个设备的实际DPI为165,他会映射为MDPI。也就是设计者只需要把这种特殊尺寸归类为某一种 logic pixel density 去设计就好了。

LOGICAL DENSITY(DPI) FRIENDLY NAME SCALE RATIO
160 MDPI 1x 4
240 HDPI 1.5x 6
320 XHDPI 2x 8
480 XXHDPI 3x 12

160 DPI 是一个“特殊值”,在安卓中他被称为 baseline density。我们之后讲到的DIP单位,也是基于这个baseline density的。

DIP / DP

对 Android 而言至关重要的东西:DP / DIP(Density-independent pixels)。虚拟像素单位。等价于在MDPI设备上的 1 个物理像素,在或高或低的密度屏幕中等比缩放。也就是说,假如你在MDPI设备上有个dp,那它意味着是1个物理像素。又假如,这个dp是指在XHDPI上,那它意味着4个物理像素(two across and two down)。

为什么我们需要 dp 呢,当我们创建布局时,我们希望UI元素能在不同 DPI 设备上,保持大致相同的物理大小,而不是为每个单独的DPI去指定UI元素的大小。也就是说,dp可以让元素在不同DPI中保持大致相同的物理尺寸。

举例,假设我们有一个200dp高的块,它在MDPI设备下显示为200像素高(MDPI下,1dp == 1Pixel),而在XHDPI设备下,200dp则是400pixels高。而这些都是内置行为:当你定义某样东西应该为200dp,它能在不同设备显示成基本一致的物理尺寸。

正由于dp单位能够在不同设备上表达相同的物理尺寸,因此,我们可以开始用DPs来表示物理对象。比如,人的手指点触大小约50dps宽,这意味着当你设置按钮或其他可点击元素时,大小不应该小于50dps。以此来确保,元素不会因为过小导致普通用户误触。

接着我们看一个帮助我们理解物理像素与DP单位之间关系的转换公式。

 1 pixel    160dpi
-------- = --------- = 1x scale
   1 dp     160dpi

如果是在320DPI屏幕上,也就是XHDPI屏幕,则一个dp实际是4个物理像素(two across and two down)。

 2 pixel   320dpi
-------- = --------- = 2x scale
   1 dp     160dpi

做个测验:2013 Nexus 7's 屏幕的DP是多少?已知信息是:屏幕对角线7inchs;是XHDPI屏幕(320DPI);屏幕分辨率为 1920 x 1200
答案:960 x 600 DP。

那么这个计算的重点是什么呢?DP,即与密度无关的像素,能更好的表达物理像素大小。比如你有个MDPI的平板,屏幕分辨率 1280 x 800,还有个XHDPI的手机,屏幕分辨率 1280 x 720。他们的屏幕分辨率几乎是一样的,但是XHDPI手机的物理尺寸要小的多。

MDPI 1280 x 800 => 1280 x 800 DP
XHDPI 1280 x 720 => 640 x 360 DP

DPR

web开发中,DPR (device pixel ratio) 返回显示器在物理像素中的分辨率与CSS像素中的分辨率之比(双精度浮点值)。简单来说,DPR 告诉浏览器应该使用多少屏幕的物理像素来绘制单个CSS像素。

CSS像素可以类比一下DP相关的概念。DP的存在让设计师能用DP单位来描述物理尺寸。CSS像素同理,1个CSS像素在不同设备下,实际占有的物理像素不同。这个不同,我们可以通过 window.devicePixelRatio 来知晓。

DPR使用场景,比如canvas画布中的内容,显示模糊,可以配合使用 window.devicePixelRatio 确定应添加多少额外像素密度来获得更清晰的图像。主要的使用场景还在于网页内容中图片的适配。在不同DPR的设备下,开发者不希望强迫低DPR设备用户下载高分辨率图片,也不想高DPR设备用户下载一个低分辨率图片。为了提高用户体验,开发者可以使用CSS媒体查询,以满足不同设备需求。

#element { background-image: url('lores.png'); }

@media only screen and (min-device-pixel-ratio: 2) {
    #element { background-image: url('hires.png'); }
}

@media only screen and (min-device-pixel-ratio: 3) {
    #element { background-image: url('superhires.png'); }
}

随着越来越多的设备类型出现,为它们提供足够的位图资源变得更加棘手。在CSS3中,媒体查询是目前兼容性最好的方式(含IE9以上、主流浏览器),在HTML5中,picture元素允许您为不同的媒体查询使用不同的源,但是IE11不兼容。

如果开发者需要给用户呈现清晰的图像,图标,线条艺术,非照片的设计元素,应该使用SVG,它可以精确地扩展到所有分辨率,且兼容性很好(含IE9以上的主流浏览器)。

viewport

现在有很多手机分辨率都非常大,比如 768x1024,1080x1920 等,那在这样的手机上显示为桌面浏览器设计的网站似乎是没问题的。但是,如同前面讲到的 DIP / DPR 的概念,css中的 1px 并不是代表屏幕上的1px,屏幕密度越大,css中1px代表的物理像素就会越多,DPR 的值也越大,这样才能保证1px的东西在屏幕上的大小与那些低分辨率的设备差不多。

因此在移动设备上,浏览器的可视区域,远小于这些为桌面浏览器设计的网站所需要的视口大小。所以一般来讲,移动设备上的 viewport 都是要大于浏览器可视区域的。毕竟以浏览器的可视区域作为 viewport 的话,那些不是为移动设备设计的网站,在移动设备上显示时,会因为移动设备的viewport太窄,而挤作一团,甚至布局会乱掉。

为了防止某些网站因为 viewport 太窄而显示错乱,所以这些浏览器就决定默认情况下把 viewport 设为一个较宽的值,比如980px,这样的话即使是那些为桌面设计的网站也能在移动浏览器上正常显示了。这个浏览器默认的 viewport 被称为 layout viewport。这个 layout viewport 的宽度可以通过 document.documentElement.clientWidth 来获取。

然而,layout viewport 的宽度是大于浏览器可视区域的宽度的,所以我们还需要一个 viewport 来代表 浏览器可视区域的大小,我们叫他 visual viewport。visual viewport 的宽度可以通过 window.innerWidth 来获取。

现在大部分网站都会为移动端进行设计,所以必须还要有一个能完美适配移动设备的 viewport。所谓的完美适配指的是,不需要用户缩放和横向滚动条就能正常的查看网站的所有内容:ideal viewport 。ideal viewport 并没有一个固定的尺寸,不同的设备拥有有不同的 ideal viewport。

  • layout viewport 表示的是浏览器默认的viewport。
  • visual viewport 表示浏览器可视区域的大小。
  • ideal viewport 的宽度等于移动设备的屏幕宽度,也就是宽度为100%的效果

我们的html一般带有以下一条定义了viewport的元数据。

<meta name="viewport" content="width=device-width, initial-scale=1.0">

先看一下两个content值的定义:

  • width A positive integer number, or the text device-width: Defines the pixel width of the viewport that you want the web site to be rendered at.
  • initial-scale A positive number between 0.0 and 10.0: Defines the ratio between the device width (device-width in portrait mode or device-height in landscape mode) and the viewport size.

width 不设置时,则使用浏览器自己默认的layout viewport值,设置为 device-width 时,意味着使用ideal viewport。而看过定义后不难发现 initial-scale=1.0 也是把当前的渲染视口变成 ideal viewport。因为一些历史兼容问题,这两个值需要都写上。

CSS适配

媒体查询

https://www.w3.org/TR/css3-mediaqueries/

https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries

  1. 使用 @media@import
  2. 使用 <link> 元素的 media 属性指定外部样式表的目标媒体:

媒体查询由媒体类型(media type)和零至多个表达式(expressions),组成一个true或false的逻辑表达式,以此来检查特定的媒体功能(media features)的满足条件。如果媒体查询的媒体类型与运行中的设备类型匹配,并且媒体查询中的所有表达式都为真,则媒体查询为true。在CSS中使用媒体查询:

/* 
    The complete list of media types in HTML4 is:
    ‘aural’, ‘braille’, ‘handheld’, ‘print’, ‘projection’, ‘screen’, ‘tty’, ‘tv’.
    CSS2 defines the same list, deprecates ‘aural’ and adds ‘embossed’ and ‘speech’.
*/
@media <media-query-list> {
  <stylesheet>
}

/* 
    ‘all’ is used to indicate that the style sheet applies to all media types.
    If the media type is not explicitly given it is ‘all’.
    The most commonly used feature is to distinguish between ‘screen’ and ‘print’.
 */
@media { }
@media all { }

/* 
    Several media queries can be combined in a media query list.
    In the media queries syntax,
    the comma: ‘,’ expresses a logical OR,
 */
@media screen, print { }

/* 
    while the ‘and’ keyword expresses a logical AND.
 */
@media screen and (color), print and (color) { }

/* 
    the keyword ‘not’ at the beginning of the media query negates the result.(does not support in all user agent)
    Note that ‘not’ only works for the current media query, if you comma separate, it only affects the media query it is within.
    Also note that ’not‘ reverses the logic for the entire media query as a whole, not individual parts of it.
    That's said, not x and y = not (x and y) ≠ (not x) and y
    *However, if you use ‘not’ or ‘only’, you must also specify a media type.
 */
@media not all and (max-width: 600px) {
  html { background: red; }
}

/* 
    The keyword ‘only’ can also be used to hide style sheets from older user agents.
    User agents must process media queries starting with ‘only’ as if the ‘only’ keyword was not present.
    So it has no effect on modern browsers.
    *However, if you use ‘not’ or ‘only’, you must also specify a media type.
 */

视口相关CSS单位

https://www.w3.org/TR/css-values-3/#viewport-relative-lengths

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

与视口(viewport)相关的CSS单位:vwvhvminvmax 。假定任何滚动条都不存在时,视口百分比长度是相对于 初始包含块 的大小。当 初始包含块 的高度或宽度改变时,视口百分比长度相应地缩放。

  • vw
    Equal to 1% of the width of the initial containing block.
  • vh
    Equal to 1% of the height of the initial containing block.
  • vmin
    Equal to the smaller of vw or vh.
  • vmax
    Equal to the larger of vw or vh.

响应式图像

https://www.w3.org/TR/html52/semantics-embedded-content.html

https://dvcs.w3.org/hg/html-proposals/raw-file/9443de7ff65f/responsive-images/responsive-images.html

当前的img元素仅允许图像有单个资源。但是,在许多情况下,开发者希望设置多个图像资源,根据不同场景需求进行选择。比如:

  1. 用户的物理屏幕大小(physical screen size)可能彼此不同。
  2. 用户的屏幕像素密度(physical pixels density)可能彼此不同。
  3. 用户的缩放级别(zoom level)可能彼此不同。
    用户可以放大特定图像以获得更详细的外观。
    用户的缩放级别和屏幕物理像素密度都能影响“每个CSS像素的物理像素数”,即DPR。
  4. 用户的屏幕方向可能彼此不同:纵向(portrait)、横向(landscape)。
  5. 用户的网络速度,网络延迟和带宽成本可能彼此不同。
  6. 开发者希望根据视口宽度(the width of the viewport)来显示相同图像内容,不同大小的资源。这通常被称为基于 viewport-based 的选择。
  7. 开发者对于不同尺寸设备下,图片的布局模式可能不同。
    网页可能包含以列布局的图像,其中一列用于物理尺寸较小的屏幕,两列用于具有中等物理尺寸的屏幕,三列用于具有较大物理尺寸的屏幕,每种情况下图像的渲染大小各不相同。在这种情况下,尽管屏幕较小,但与双列布局相比,单列布局中图像的渲染大小可能更大。
  8. 开发者可能希望根据图像的渲染大小,显示不同的图像内容。这通常被称为 art direction (艺术指导)。
    当在具有大物理尺寸的屏幕上观看网页时,开发者可能希望图像上呈现更多的内容。当在具有小物理尺寸的屏幕上查看相同的网页时,开发者可能希望仅显示图像的关键部分。
  9. 开发者可能希望根据用户代理所支持的图像格式,显示相同的图像内容,不同的图像格式。这通常被称为基于 image format-based selection

上述情况并非相互排斥。另外,虽然可以使用脚本来解决这些问题,但这样做会引入一些其他问题:

  1. 一些用户代理在脚本运行前,下载了HTML中指定的图像,以便网页更快地完成加载。如果脚本更改了要下载的图像,则相当于做了两次下载资源动作,这可能会导致页面加载性能下降。
  2. 可以避免在HTML中指定任何图像,仅在脚本中进行图像实例化,避免问题1中的多次下载的问题。但是对于禁用脚本的用户来说,则体验不好,因为他根本不会下载任何图像。

在线例子 todo

https://www.youtube.com/watch?v=zhszwkcay2A
https://www.youtube.com/watch?v=g48Rxf22hrE
https://www.captechconsulting.com/blogs/understanding-density-independence-in-android
https://microscope-microscope.org/microscope-info/image-resolution/

Traverse binary tree

  1. binary-tree-preorder-traversal
  2. binary-tree-inorder-traversal
  3. binary-tree-postorder-traversal
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

Using Recursive algorithms is pretty easy

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var Traversal = function(root) {
    if (root === null) return [];
    const res = [];

    const _traversal = (_node) => {
        if (_node) {
            // preorder
            // res.push(_node.val);
            // _traversal(_node.left);
            // _traversal(_node.right);

            // inorder
            // _traversal(_node.left);
            // res.push(_node.val);
            // _traversal(_node.right);

            // postorder
            // _traversal(_node.left);
            // _traversal(_node.right);
            // res.push(_node.val);
        }
    }

    _traversal(root);
    return res;
};

Linux command notes and cheatsheet

Shortcuts

CTRL L # Clear the terminal
CTRL A # Cursor to start of line
CTRL E # Cursor the end of line
CTRL W # Delete word on the left
CTRL U # Delete left of the cursor
CTRL K # Delete right of the cursor
CTRL R # reverse search history
!! # repeat last command

Basic file manipulation

cp image.jpg newimage.jpg # copy and rename a file
cp image.jpg <folderName>/ # copy to folder
cp image.jpg folder/sameImageNewName.jpg
cp -R stuff otherStuff # copy and rename a folder
cp *.txt stuff/ # copy all of *<file type> to folder

mv file.txt Documents/ # move file to a folder
mv <folderName> <folderName2> # move folder in folder
mv filename.txt filename2.txt # rename file
mv <fileName> stuff/newfileName
mv <folderName>/ .. # move folder up in hierarchy

rm -i <fileName> .. # ask for confirmation each file
rm -f <fileName> # force deletion of a file
rm -r <foldername>/ # delete folder
rm <fileName> .. # delete file (s)

ln file1 file2 # physical link
ln -s file1 file2 # symbolic link

Show the manual for command

man xxx

Set the system's host name: root@xxx

# update hostname
echo "cutecute" > /etc/hostname

# Read the host name from the specified file.
hostname -F /etc/hostname

# next time you log in, you will see the new host name

Check IP address in linux

# ifconfig stands for "interface configuration".
# can view and change the configuration of the network interfaces on your system.

# display information about all network interfaces currently in operation.
ifconfig

# To view the configuration of a specific interface, specify its name as an option.
ifconfig eth0

Redirect an IP Address Using a Hosts File

# The hosts file is used to map hostnames (in other words domains) to IP addresses.
# With the hosts file,
# you can change the IP address that you resolve a given domain name to.
# This change only affects your own computer,
# without affecting how the domain is resolved worldwide.

# Before the advent of DNS,
# the host table was the only way of resolving hostnames on the fledgling Internet.

# check manual pages
man hosts

# tips: cannot map IP to IP.
# IP_address canonical_hostname [aliases...]

Choose time zone

# print current date
date

# select time zone
dpkg-reconfigure tzdata

Install package on Linux(Debian, Ubuntu)

Installing Software on Linux
What is the difference between dpkg and aptitude/apt-get
apt-vs-apt-get-difference

# you will need to become SuperUser to install software.
apt install ${packagename}
apt remove ${packagename}
# if you want to remove all its files including configuration files
apt purge ${packagename}

# Although the repositories that contain installable packages might live on the Internet
# or on a disc somewhere,
# APT keeps a local database on your hard drive with a list of all available packages
# and where to find them.
# This database needs to be explicitly updated.
apt update
# A common idiom is to update your package database,
# and then upgrade all the packages that have patches or security updates to install.
apt upgrade

Add/Modify user

linux-users-and-groups

# Leveraging Users and GroupsPermalink

# In many cases, user permissions are used to provide your system with greater security
# without any direct interaction.
# Many operating systems create specific system user accounts for different packages
# during the installation process.

# The best practice is to give each user their own login to your system. 
# This protects each user’s files from all other users.
# Furthermore, using specific accounts for users allows more accurate system logging,
# particularly when combined with tools like sudo.
# We recommend avoiding situations where more than one individual knows the password for a user account for maximum security.

whoami # root

apt-get install adduser
adduser aki
adduser aki sudo

sudo passwd <AccountName> # change a user's password

Change file/directory mode

# chmod <octal or letters> <file/directory name>

# we'd better use octal format to change mode, it's more quickly to set
chmod 111 test.sh

# show all files/diretories long list
ls -al

drwxrwxr-x 2 root root 4096 Feb  9 21:07 .
drwxr-xr-x 5 root root 4096 Feb  9 22:10 ..
---x--x--x 1 root root    5 Feb  9 21:07 test.sh

# each colum indicates:
# access permissions: drwxrwxr-x
# numbers of files or dirs: 2
# owner: root
# group: root
# size: 4096
# last access's time/date: Feb  9 21:07
# name: .

# The first ten characters show the access permissions.
# first characters indicates the type of file.
# next three define the owner’s permission to the file.
# next three define the permissions for the members of the same group as the file owner.
# The last three characters define all other users'.

# r: read 4
# w: write 2
# x: execute 1

Variable related

#!/bin/sh
echo Hello World

# The first line tells Unix that the file is to be executed by /bin/sh.
# This is the standard location of the Bourne shell on just about every Unix system.
# If you're using GNU/Linux,
# /bin/sh is normally a symbolic link to bash or more recently, dash.

# The only exception is when the very first line of the file starts with #! - as ours does.
# This is a special directive which Unix treats specially.

# use $ to refer a variable
echo $PATH

# export makes the variable available to sub-processes.
export var1="hello    world"

Searching / filter / replace: grep sed awk

grep-vs-awk-examples-for-pattern-search
what-are-the-differences-among-grep-awk-sed

# we can use grep to grab stuffs, while sed can grab and modify and do other things, awk do much more things as it is an entire programming language. For the basic usage, they all can do searching.

# search for pattern in files
grep [pattern] [files]

# search recursively for pattern in dir
grep -r [pattern] [dir]

# search for patterns in the output of command
[command output] | grep [pattern] 

# perform basic text transformations on an input stream
# s/regexp/replacement/
# -e script, --expression=script .   add the script to the commands to be executed
sed -e 's/day/night/g' -e 's/you/me/g' file 

# -i[SUFFIX], --in-place[=SUFFIX] .  edit files in place (makes backup if SUFFIX supplied)
sed -i.tmp s/dog/cat/g file

# scan per line and output the column $0 that includes 'aki' in aki.txt
awk '/aki/ { print $1 }' aki.txt

Process Management

linux-process-management

# show static process list
# By default ps selects all processes with the same effective user ID (euid=EUID)
# as the current user and associated with the same terminal as the invoker.
ps

# To see every process on the system using BSD syntax:
ps ax
ps axu

# show dynamic process list
top

# kill a process 
kill <PID>

# To start a process in the background (non-interactive), add a '&' at the end of a command
cp bigMovieFile.mp4 &

# know what is running in the background
jobs

# put a background process to foreground
fg %1 # (process 1)

Archive and compress data

how-to-archive-files-and-directories-in-linux-part-1

# c : Create an archive from a file(s) or directory(s).
# x : Extract an archive.
# f : Takes an argument that sets the name of the archive to operate upon.
# z : Compresses the archive using gzip compression method.
# v : View the progress while creating the archive.
# r : Append files to the end of an archive.
# t : List the contents of the archive.
# -C : To extract the archive in a different folder.

# create a new tar archive of the the directory akiFolder.
tar cf akitar.tar akiFolder/
# create a new gzipped archive of the the directory akiFolder.
tar czf akitar.tar.gz akiFolder/
# extracts the given archive file in anotherFolder directory
tar cf akitar.tar akiFolder/ -C anotherFolder/

# extract an archive in the current directory
tar xf akitar.tar
# extract the gzipped archive in the current directory
tar xzf akitar.tar.gz

# Exclude directories and/or files from while creating an archive
tar czvf akitar.tgz /home/aki --exclude=/home/aki/Downloads --exclude=/home/aki/Documents

# or using zip unzip package

SSH

SSH COMMAND
SSH PORT

# SSH, or Secure Shell, is a protocol used to securely log onto remote systems.
# It is the most common way to access remote Linux and Unix-like servers.

# `remote_host` is the IP address or domain name
# This command assumes that your username on the remote system
# is the same as your username on your local system.
ssh remote_host
# or you can specify it by using this syntax
ssh remote_username@remote_host
# connect to host on port. connect to port 22 by default
ssh -p port remote_username@remote_host

# configure SSH
# In Ubuntu, the main sshd configuration file is located at /etc/ssh/sshd_config.

# exit back into your local session
exit

# Create SSH Keys
ssh-keygen -t rsa

# Log Into SSH with Keys
# add your key to host for user to enable a keyed or passwordless login
# then next time you logging into the machine will not need to enter password
ssh-copy-id remote_username@remote_host

Network

ping hostname # ping host and output results
whois domain # get whois information for domain
dig domain # get DNS information for domain

# secure copy a file from remote server to the dir directory on your machine
scp user@host:file dir
# secure copy a file from your machine to the dir directory on a remote server
scp file user@host:dir

lsof -i tcp:1337 # list all processes running on port 1337

Machine Info

# disk: List information about all available or the specified block devices.
lsblk

# ram: Display amount of free and used memory in the system
free -g
free -m
free -k

# cpu: Display cpu info
cat /proc/cpuinfo

write your own promise from scratch

/**
 * Promise constructor receive a function as first param: (resolve, reject) => xxx
 * a pendding status: pendding; two settled status: resolved / rejected
 * api: THEN
 *   1. receive two function: onResolved & onRejected, these function receive result param
 *   2. THEN return a new promise
 */
let uid = 0;
const _status = Symbol('_status');
const _value = Symbol('_value');
const _thenlist = Symbol('_thenlist');
class Promise {
  constructor(executor) {
    this.uid = uid++;
    if (typeof executor !== 'function') {
      throw new Error('executor should be a function');
    }

    this[_status] = 'pending';
    this[_thenlist] = [];

    const resolve = (value) => {
      if (this[_status] !== 'pending') return;
      this[_value] = value;
      this[_status] = 'resolved';

      if (this[_thenlist].length) {
        for (let { resolvedHandler } of this[_thenlist]) {
          resolvedHandler(this.value);
        }
      }
    }

    const reject = (value) => {
      if (this[_status] !== 'pending') return;
      this[_value] = value;
      this[_status] = 'rejected';

      if (this[_thenlist].length) {
        for (let { rejectHandler } of this[_thenlist]) {
          rejectHandler(this.value);
        }
      }
    }

    try {
      // invoke resolve / reject will turn the current status.
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  get status () {
    return this[_status];
  }

  set status (data) {
    return this[_status];
  }

  get value () {
    return this[_value];
  }

  set value (data) {
    return this[_value];
  }

  then(resolvedHandler, rejectHandler) {
    return new Promise((resolve, reject) => {
      const _resolvedHandler = (_val) => {
        try {
          resolve(resolvedHandler(_val));
        } catch (error) {
          reject(error);
        }
      }

      const _rejectHandler = (_err) => {
        try {
          resolve(rejectHandler(_err));
        } catch (error) {
          reject(error);
        }
      }

      if (this.status === 'resolved') {
        _resolvedHandler(this.value);
      } else if (this.status === 'rejected') {
        _rejectHandler(this.value);
      } else {
        this[_thenlist].push({
          resolvedHandler: _resolvedHandler,
          rejectHandler: _rejectHandler,
        });
        console.log('uid:',this.uid, 'thenlist:', this[_thenlist].length);
      }
    })
  }
}

let a = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('test');
  }, 4000);
}).then((res, err) => {
  console.log('in 1 then handler, value is,', res);
  return 12
}).then((res, err)=> {
  console.log('in 2 then handler, value is,', res);
  throw new Error('123123');
}).then((res) => {}, (err) => {
  console.log('in 3 then handler, value is,', err);
});

the-style-processing-of-webpack

style-loader

Adds CSS to the DOM by injecting a <style> tag

css-loader

The css-loader interprets @import and url() like import/require() and will resolve them.

Sets importLoaders option to prevent some bugs like: @imported files do not get processed by postcss's autoprefixer. postcss-loader#35 (Solution2: uses postcss-import plugin before autoprefixer)

Sets minimize option to minimize the css files.

postcss-loader

Loader for webpack to process CSS with PostCSS. Use it after css-loader and style-loader, but before other preprocessor loaders like e.g sass|less|stylus-loader, if you use any. Remember to set up postcss.config.js file too.

The Autoprefixer PostCSS plugin is one of the most popular CSS processors. (cssnano is great too. !)

sass-loader

Loads a SASS/SCSS file and compiles it to CSS. The sass-loader requires node-sass and webpack as peerDependency.

To enable CSS source maps, pass the sourceMap option to the sass-loader and the css-loader.

When you use preprocessor loaders with its option sourceMap being set, don't omit the sourceMap option in postcss-loader(if you have this loader), or the previous source maps will be discarded by postcss-loader entirely.

That's said, if you want to enable css's source maps function, ensure the relavant style's loaders have set the sourceMap option. you also need to include devtool option for webpack too. sass-loader#7

ExtractTextWebpackPlugin

Extract text from a bundle, or bundles, into a separate file.

new ExtractTextPlugin(options: filename | object)

As the following example, it moves all the required *.scss modules in entry chunks into a separate CSS file. So your styles are no longer inlined into the JS bundle, but in a separate CSS file main.css. If your total stylesheet volume is big, it will be faster because the CSS bundle is loaded in parallel to the JS bundle.

// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
  entry: {
    main: path.resolve(__dirname, '../src/index.js')
  },
  devtool: 'eval-source-map' // for development mode
  module: {
    rules: [{
      test: /\.scss$/,
      use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: [
          {
            loader: 'css-loader',
            options: { importLoaders: 2, minimize: true, sourceMap: true },
          },
          {
            loader: 'postcss-loader',
            options: { sourceMap: true },
          },
          {
            loader: 'sass-loader',
            options: { sourceMap: true },
          },
        ]
      })
    }]
  },
  plugins: [
    new ExtractTextPlugin({ filename: '[name].css' })
  ]
}

use-async-await-with-array-map

Problem

stackoverflow: use-async-await-with-array-map
The problem is: trying to await an array.

let arr = [1, 2, 3, 4, 5],
  results: number[] = await arr.map(async (item): Promise<number> => {
    await callAsynchronousOperation(item)
    return item + 1
  })
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'. Type 'Promise<number> is not assignable to type 'number'.

await

The await operator is used to wait for a Promise. It can only be used inside an async function.

syntax :
[rv] = await expression;

expression :
A Promise or any value to wait for.

rv :
Returns the fulfilled value of the promise, or the value itself if it's not a Promise.

Solution

In the demo, it await an array, so it would return the value itself immediately. That's said, this map method return a Promise<number>[] type (is not assignable to number[]).

We can call Promise.all on the array returned by map, so to convert it to a single Promise before awaiting it.

let arr = [1, 2, 3, 4, 5],
  results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
    // ...
}));

Reference

MDN - await

binary operation in javascript

What are bits

At the smallest scale in the computer, information is stored as bits and bytes.

  • a "bit" is atomic: the smallest unit of storage
  • A bit stores just a 0 or 1
  • "In the computer it's all 0's and 1's" ... bits
  • Anything with two separate states can store 1 bit
  • In a chip: electric charge = 0/1
  • In a hard drive: spots of North/South magnetism = 0/1
  • A bit is too small to be much use
  • Group 8 bits together to make 1 byte

In JavaScript

Bitwise operators treat their operands as a sequence of 32 bits (zeroes and ones), rather than as decimal, hexadecimal, or octal numbers.

The numbers -2147483648 and 2147483647 are the minimum and the maximum integers representable through a 32bit signed number.

Related useful method in javascript

// works well for positive number
(314).toString(2); // "100111010"
parseInt('100111010', 2); // 314

// Note, if the number is negative, the sign is preserved.
// This is the case even if the radix is 2;
// the string returned is
// the positive binary representation of the number preceded by a - sign,
// not the two's complement of the number.
// For example:
-315..toString(2); // -100111011

// if you want to get the two's complement, use the Zero-fill right shift
// cause the zero-fill right shift converts it's operand to a [unsigned 32] bit integer.
(-315 >>> 0).toString(2); // '11111111111111111111111011000101'
parseInt('11111111111111111111111011000101', 2) | 0 // -315 // `|` converts it's operand [To Int 32]

// read more spec detail below if you wonder why >>> 0, why | 0 works.
const setBit = (num, position) => {
  let mask = 1 << position
  return num | mask
}

// Set the bit at position 1
setBit(12, 1) // return 14 -> 1110
const clearBit = (num, position) => {
  // We use the ~/NOT operator after placing the bit
  // We want 1s everywhere and 0 only where we want to modify
  let mask = ~(1 << position)
  
  // We use AND which will modify only the bits compared to 0
  return num & mask
}

clearBit(15, 1) // 12 -> 1100
const flipBit = (num, position) => {
  let mask = 1 << position
  // If the current state of the bit is 0, XOR will return 1
  // If the bit is 1, XOR will set it to 0
  return num ^ mask
}

flipBit(15, 1) // 13 -> 1101

Spec

BitwiseANDExpression & EqualityExpression
BitwiseXORExpression ^ BitwiseANDExpression
BitwiseORExpression | BitwiseXORExpression

Semantics

The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:

  1. Evaluate A.
  2. Call GetValue(Result(1)).
  3. Evaluate B.
  4. Call GetValue(Result(3)).
  5. Call ToInt32(Result(2)).
  6. Call ToInt32(Result(4)).
  7. Apply the bitwise operator @ to Result(5) and Result(6). The result is a signed 32 bit integer.
  8. Return Result(7).

ShiftExpression >>> AdditiveExpression

Semantics

Performs a zero-filling bitwise right shift operation on the left argument by the amount specified by the right argument. The production ShiftExpression : ShiftExpression >>> AdditiveExpression is evaluated as follows:

  1. Evaluate ShiftExpression.
  2. Call GetValue(Result(1)).
  3. Evaluate AdditiveExpression.
  4. Call GetValue(Result(3)).
  5. Call ToUint32(Result(2)).
  6. Call ToUint32(Result(4)).
  7. Mask out all but the least significant 5 bits of Result(6), that is, compute Result(6) & 0x1F.
  8. Perform zero-filling right shift of Result(5) by Result(7) bits. Vacated bits are filled with zero. The result is an unsigned 32 bit integer.
  9. Return Result(8).

Refs

Specification
Bits and Bytes
Binary Bit Flags: Tutorial and Usage Tips

Programming with JS: Bitwise Operations
What are the advantages of using bitwise operations?

Integers and shift operators in JavaScript
Bitwise_Operators
How do I convert an integer to binary in JavaScript?
es5

z-index

Z-INDEX

  1. z-index只对指定了position属性为非static的元素有效
  2. 没有指定z-index时,所有元素默认渲染在同一层(0层),布局顺序:
    • 块级元素(在最下层)
    • 浮动元素
    • 行内元素(在最上层)
  3. 同一层内z-index相同时,按照以下规则进行布局:
    • 定位元素(position值为非static):这些元素只按照其HTML结构中出现的顺序堆叠。
    • 非定位元素(position值为static):始终先于定位元素渲染,并出现在定位元素的下层。

CONCLUSION

  1. Background and borders: 形成层叠上下文的元素的背景和边框。层叠上下文中的最低等级。
  2. Negative Z-Index: 层叠上下文内有着负z-index值的子元素。
  3. Block Level Boxes: 文档流中非行内|非定位子元素。
  4. Floated Boxes: 非定位浮动元素。
  5. Inline Boxes: 文档流中行内级别非定位子元素。
  6. Z-index: 0: 定位元素。 这些元素形成了新的层叠上下文。
  7. Positive Z-index: 定位元素。 层叠上下文中的最高等级。

DEMO

学习资料

MDN - z-index
What You May Not Know About the Z-Index Property

guangzhou-food

【**菜】度小月 https://www.dianping.com/shop/22169006
人均:78元 | 口味:7.9 | 环境:8.3 | 服务:8.2
周一至周日 11:00-14:30 17:00-21:00
珠江新城华夏路28号富力盈信大厦三楼
(担仔面13 肉燥饭13 芙蓉豆腐22 台式三杯鸡42)
(备注:分量都是很小系列的,度小月促销 分享可以免费吃担仔面)
(评价:三杯鸡好吃,担仔面真的不值得 也没什么好吃的,芙蓉豆腐还可以,肉燥饭还可以)

【煲仔饭】超记煲仔饭 https://www.dianping.com/shop/15955491
人均:23元 | 口味:8.0 | 环境:7.4 | 服务:7.6
周一至周日 11:30-14:00 17:30-20:30
北京南路星光广场(近仓前直街)
(猪肝枸杞叶汤 窝蛋牛肉饭)
(备注:离天河区近)

【西餐】钟情焗 https://www.dianping.com/shop/2950975
人均:61元 | 口味:8.8 | 环境:6.4 | 服务:6.9
纺织路草芳围16号
(焗鸭胸 蛋黄酱焗青口 蒜蓉西兰花 柠檬茶)
(备注:6点开 赶紧点单 不然超级慢)

【手抓羊肉】撒尔塔东乡手抓餐厅 https://www.dianping.com/shop/26955239
人均:68元 | 口味:7.7 | 环境:6.8 | 服务:7.5
宝汉直街103号
(手抓羊肉 干锅土豆 牙签羊肉)

【虾饺】毕德寮(富力盈通店) https://www.dianping.com/shop/91009319
人均:78元 | 口味:8.5 | 环境:8.8 | 服务:8.8
华夏路30号富力盈通大厦四楼401-409房
(避风塘虾饺 功夫虾饺 油炸鬼 千褶牛肉肠)
(备注:好似月尾都有五折,总之有折扣再去,不然不抵)

【阳山鸡】洲记阳山农家菜 https://www.dianping.com/shop/18326439
人均:81元 | 口味:9.2 | 环境:8.0 | 服务:8.3
北山村桥头大街226-228号北山广鹰科技创意园A栋
(阳山鸡)
(备注:一只阳山鸡最少5斤起 可以叫多点人一起去这里吃)

【潮汕菜】歡喜豬咪家 https://www.dianping.com/shop/48008359
人均:177元 | 口味:8.1 | 环境:8.2 | 服务:8.1
细岗东二街2号101(近大参林药店)
(醉蟹 秘制乌耳鳗 红桃粿 生腌虾姑 猪肚丸汤)
(备注:貌似需要预约)

【海鲜】Wut Put活泼 · 活海鲜烧烤 https://www.dianping.com/shop/57394307
人均:177元 | 口味:8.7 | 环境:7.7 | 服务:8.6
建设六马路1号誉海食街5楼(禄鼎记楼上,1920旁)
(蟹棒 元贝 鳗鱼炒饭 白贝汤)

【早茶】
陶陶居大虾饺 ¥28
一口酥豆腐 ¥28
啫啫生菜煲 ¥36
木耳秋葵 ¥25
海皇鸡火炒饭 ¥36

  • 珠江夜游
  • 小蛮腰摩天轮
  • 沙面
  • 上下九
  • 红砖厂
  • 喝早茶

learning-canvas

Learning canvas api

codepen#learningCanvas1

var canvas = document.querySelector('#canvas1')

// default: width 300 height 150
canvas.width = window.innerWidth
canvas.height = window.innerHeight

var c = canvas.getContext('2d')

// 设置图形的填充颜色
c.fillStyle = '#099'

// 绘制一个填充的矩形 fillRect(x, y, width, height)
c.fillRect(200,150,100,100)

// 清除指定矩形区域,让清除部分完全透明 clearRect(x, y, width, height)
c.clearRect(225,175,50,50)

// 设置图形轮廓的颜色
c.strokeStyle = '#055'

// 绘制一个矩形的边框 strokeRect(x, y, width, height)
c.strokeRect(237, 188, 26, 26)

// 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
c.beginPath()

// 将笔触移动到指定的坐标x以及y上。指定你的起始位置。
c.moveTo(200, 150)

// 这个属性设置当前绘线的粗细。属性值必须为正数。默认值是1.0
c.lineWidth = 0.5

// 绘制一条从当前位置到指定x以及y位置的直线。
c.lineTo(300, 250);
c.lineTo(200, 250);

// 闭合路径,并将图形绘制命令重新指向到上下文中。非必需。
c.closePath()

// 通过线条来绘制图形轮廓。路径不会自动闭合。
c.stroke()

c.fillStyle = 'rgba(0,0,0,.1)'

// 通过填充路径的内容区域生成实心的图形。路径自动闭合。
c.fill()

// arc(x, y, radius, startAngle, endAngle, anticlockwise)
// 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
c.beginPath()
c.arc(250, 201, 13, Math.PI * 1.5, 0, true)
c.lineTo(250,189)
c.stroke()

// fillText(text, x, y [, maxWidth])
c.font = "20px serif"
c.fillStyle = 'rgba(255,255,255,.4)'
c.fillText("Hello world", 200, 170,100)

http cache headers and service worker

Why cache

Fetching something over the network is both slow and expensive. Large responses require many round trips between the client and server, which delays when they are available, and when the browser can process them, and also incurs data costs for the visitor. As a result, the ability to cache and reuse previously fetched resources is a critical aspect of optimizing for performance.

According to HTTP Archive, among the top 300,000 sites (by Alexa rank), the browser can cache nearly half of all the downloaded responses, which is a huge savings for repeat pageviews and visits. Of course, that doesn’t mean that your particular application can cache 50% of the resources. Some sites can cache more than 90% of their resources, while other sites might have a lot of private or time-sensitive data that can’t be cached at all.

Why service worker

By reading more on HTTP cache headers and service worker, I think there is two biggest difference between them:

  1. Caches in SW are more controllable by web developers.

    By using HTTP cache headers, origin server indicate how the resources can be cache, so the CDNs or private client like browser can store the responses, so to put less load on origin server.

    While using SW, web developers get charge of what can be cached, and are responsible for implementing how service worker handles updates to the cache.

  2. Users can get content offline by using SW.

    Using a Service worker you can easily set an app up to use cached assets first, thus providing a default experience even when offline, before then getting more data from the network (commonly known as Offline First). This is already available with native apps, which is one of the main reasons native apps are often chosen over web apps.

    While request something, the basic flow is:

    • Try Service Worker Cache – if found then response will be served from SW cache (Browser Cache will not be checked).
    • Try Browser Cache – if found then response will be served from Browser Cache (server will not receive the request).
    • Fetch data from the server – if all else fails, reply with server data.

Conclusion

It used to confused me why I need to use service worker to cache while using cache header can do it perfectly if we implement them porperly. It's much clear to me now, service worker is not disabling browser cache.

We will always need to repect cache header to know how to cache kinds of resource, following some rules like, using server revalidation while caching root page, and using long max-age for immutable content.

Service worker let us capable to intercept the request, add/update/delete cache for them in our browser, which let offline first available in web apps. It just a choice for us to get a better web experience in some scenarios.

references

webRequest
w3c - ServiceWorker
The Service Worker Lifecycle

caching-best-practices
pwa-and-http-caching
nginx-caching

Climbing stairs

Description

You are climbing a staircase. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.

# Example 1: Input: 2 Output: 2
# Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

# Example 2: Input: 3 Output: 3
# Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

My Solution

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    if (n === 1) {
        return 1;
    }
    if (n === 2) {
        return 2;
    }

    let result = [1,2];

    for (let i = 2; i < n; i++) {
        result[i] = result[i - 1] + result[i - 2];
    }

    return result[n - 1];
};

optimization-with-webpack

Optimize size

  1. Enable minification: Use uglifyjs-webpack-plugin for scripts and minimize option in css-loader for styles.
  2. Specify NODE_ENV=production: Set process.env.NODE_ENV for DefinePlugin as some libraries' behavior is based on this variable that, it would have a smaller library's size in production mode after removing the development-only code.
  3. Use ES modules: So becomes able to do tree-shaking, checks what dependencies are used and eliminates the unused code with a minifier.
  4. Optimize images: Use image-webpack-loader to compress images that go through it and url-loader to inline small static files into the app.
  5. Optimize dependencies: More than a half of average JavaScript size comes from dependencies, and a part of that size might be just unnecessary. check this.
  6. Scope hoisting: Enable module concatenation for ES modules by using ModuleConcatenationPlugin in production mode.
  7. Share dependencies Use externals if you have both webpack and non-webpack code and both of them have common dependencies.

N queens puzzle

Description

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle.

Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space respectively.

For example, Input: 4, Output:

 [
 [".Q..",  // Solution 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // Solution 2
  "Q...",
  "...Q",
  ".Q.."]
]

My Solution

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function (n) {
    var res = [];
    // use dict to record the existing position
    const dict = {
        x: {}, // "0": { used: true, y: number }
    };
    dfs(0, n, [], dict, res);
    return res;
};

var dfs = function (yIndex, xyMax, curSolution, curDict, res) {
    if (yIndex === xyMax) {
        res.push(curSolution);
        return;
    }

    // make sure every x-axis has only one Queen
    for (let xIndex = 0; xIndex < xyMax; xIndex++) {
        if (yIndex !== 0) {
            // make sure every y-axis has only one Queen
            if (
                curDict.x[`${xIndex}`] &&
                curDict.x[`${xIndex}`].used
            ) {
                continue;
            }

            let invalid = false;
            // make sure two diagonal has no else Queen
            // diagonal 1: xIndex--(min:0),i--(min:0)(max:n)
            // diagonal 2: xIndex++(min:n),i--(min:0)(max:n)
            for (let offset = 1; offset < xyMax; offset++) {
                let checkX;
                // check diagonal 1's Queen
                if (xIndex - offset >= 0 && yIndex - offset >= 0) {
                    checkX = xIndex - offset;
                    invalid = checkPosition(curDict, checkX, yIndex - offset)
                    if (invalid) {
                        break;
                    }
                }
                // check diagonal 2's Queen
                if (xIndex + offset < xyMax && yIndex - offset >= 0) {
                    checkX = xIndex + offset;
                    invalid = checkPosition(curDict, checkX, yIndex - offset)
                    if (invalid) {
                        break;
                    }
                }
            }

            if (invalid) {
                continue;
            }
        }

        const tmp = new Array(xyMax).fill('.');
        tmp[xIndex] = 'Q';
        curDict.x[`${xIndex}`] = { used: true, y: yIndex };
        dfs(yIndex + 1, xyMax, curSolution.concat(tmp.join('')), curDict, res);
        // clear this step, try others
        delete curDict.x[`${xIndex}`];
    }
}

var checkPosition = function (curDict, x, y) {
    return curDict.x[`${x}`] &&
        curDict.x[`${x}`].used &&
        curDict.x[`${x}`].y === y
}

generate-parentheses

Descprition

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

My Solution

/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {
    const res = []
    dfs(n, n, '', res);
    return res;
};

var dfs = function (left, right, curstr, res) {
    if (left === 0 && right == 0) {
        res.push(curstr);
        return;
    }

    if (left > 0) {
        dfs(left - 1, right, curstr + '(', res)
    }
    if (left < right) {
        dfs(left, right - 1, curstr + ')', res)
    }
}

formula

为了正常阅读公式,请安装Chrome插件:Github with MathJax
推荐学习网站:math is fun

速度

用位移与发生这个位移所用的时间的比值,描述物理运动的快慢。通常用字母$v$代表。矢量。如果在时间$\Delta t$内物体的位移是$\Delta x$,他的速度($m/s$)表示为:
$$
v = \frac {\Delta x} {\Delta t}
$$

加速度

速度的变化量与发生这一变化所用时间的比值,描述速度变化的快慢。通常用字母$a$代表。矢量。如果$\Delta v$表示速度在时间间隔$\Delta t$内的变化,他的加速度($m/s^2$)表示为:
$$
a = \frac {\Delta v} {\Delta t}
$$

匀变速直线运动

沿着一条直线,且加速度不变的运动。假设开始时刻$t=0$到$t$时刻的时间间隔为时间的变化量,$t$时刻的速度$v$与开始时刻的速度$v_0$(初速度)之差为速度的变化量。则,匀变速直线运动的速度与时间的关系为(换算过程):
$$
a = \frac {\Delta v} {\Delta t} = \frac {v - v_0} {t - 0}
$$
$$
v = at + v_0
$$
若有$v-t$图像(以$v$为纵坐标,$t$为横坐标),则其以$a$为斜率的直线与$x$轴包裹围成的梯形面积((上底+下底)x高/2)是物体的位移,以下是匀变速直线运动的位移与时间的关系(换算过程):
$$
x = \frac {(v_0 + v)(t - t_0)} {2}
$$
$$
x = \frac {1} {2} (v_0 + v)t
$$
$$
x = \frac {1} {2} (v_0 + at + v_0)t
$$
$$
x = v_0t + \frac {1} {2} at^2
$$
如果初速度为0,则公式简化为:
$$
x = \frac {1} {2} at^2
$$
根据位移与时间的关系公式,及速度与时间的关系公式,换算出速度与位移的关系式:
$$
v^2 - v_0^2 = 2ax
$$

自由落体运动

物体只在重力作用下从静止开始下落的运动。这种运动在没有空气的空间才发生,在有空气的空间,如果空气阻力的作用比较小,可以忽略,则物体的下落可以近似看做自由落体运动。自由落体运动是初速度为0的匀加速直线运动。

自由落体加速度/重力加速度

在同一地点,一切物体自由下落的加速度都相同。通常用$g$表示,方向竖直向下。地球不同地方,$g$的大小不同,一般按$g=9.8m/s^2$计算。

地点 纬度 重力加速度
赤道 9.780
武汉 30°33' 9.794
莫斯科 55°45' 9.816
北极 90° 9.832

物体与物体之间的相互作用力称为力,力的单位是牛顿,简称牛,符号是$N$。力是矢量。

重力

由于地球的吸引而使物体受到的力叫做重力(gravity),方向竖直向下。物体受到的重力$G$与物体质量$m$的关系是:
$$
G = mg
$$
一个物体各部分都受到重力作用,从效果上看,我们可以认为各部分受到的重力作用集中于一点,这一点叫做物体的重心。重心的位置只跟物体形状有关。形状规则的均匀物体重心比较容易确定。质量分布不均匀的物体,重心的位置除了跟形状有关外,还跟物体内质量的分布有关。

万有引力

相互吸引力的作用存在于任何物体之间。相互作用的强度随距离增大而减小。

弹力

物体与物体接触时发生的相互作用力为接触力,接触力分为摩擦力和弹力,他们在本质上都是由电磁力引起的。
弹力大小与形变大小有关。形变越大,弹力越大。形变消失,弹力消失。弹簧发生弹性形变时,弹力的大小$F$跟弹簧伸长/缩短长度$x$成正比(胡克定律),$k$为弹簧的劲度系数,单位牛顿每米$N/m$:
$$
F = kx
$$

摩擦力

两个相互接触的物体,当他们发生相对运动或具有相对运动的趋势时,就会在接触面上产生阻碍相对运动趋势的力。

静摩擦力

当两个物体之间只有相对运动的趋势,但没有相对运动,此时的摩擦力为静摩擦力。方向沿接触面,与物体相对运动的趋势方向相反。假设存在箱子在地面上,用力推箱子,随着推力增大,静摩擦力增大。只要箱子与地面间没有产生相对运动,则静摩擦力与推力大小相等,方向相反。
静摩擦力的增大有限度。最大静摩擦力$F_{max}$在数值上等于物体刚开始运动时的拉力。两物体间发生的静摩擦力$F$的范围是
$$
0<F \leq F_{max}
$$

滑动摩擦力

当一物体在另一物体表面滑动,会受到另一物体阻碍它滑动的力,即滑动摩擦力。方向沿着接触面,并与物体相对运动相反。滑动摩擦力$F$大小跟压力$F_N$成正比:
$$
F = μF_N
$$
μ是比例常数(两个力的比值,没有单位),叫做动摩擦因数,数值与相互接触的两个物体的材料有关,也和接触面的情况(如粗糙程度)有关

what-new-function-do

I read webpack/tapable recently, it's news to me when I found many new Function() there. Cause I mainly use function declaration and expression. So I check when we need it.

new Function() generate an anoymous function. It can accepts any number arguments, function args go first, function body is last, and all arguments are string. That's said, we can use new Function() to generate function dynamicly.

It performs a little difference in runtime, it's scope chain only has function inner variable object + global object which means it drops the lexical environment.

why-we-need-proxy

What is a proxy and reverse proxy server in web development?

proxy definition. A person authorized to act for another, or the written authorization to act for another.

  • Forward Proxy Server
    Hides the identity of the clients

  • Reverse Proxy Server
    Hides the identity of the servers

    The reasons for using the reverse proxy server
    - The web servers are not directly exposed to the internet
    - The proxy can act as a load balancer, spreading the load among multiple servers
    - The proxy can cache the response from the servers, reducing load on the servers

JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls

  • Why You Need a Proxy
    For example, the browser's same origin policy does not allow you to send cross-domain requests. There are a number of solutions to this problem but the most commonly-used one is to install a proxy on your web server. Instead of making your XMLHttpRequest calls directly to the web service, you make your calls to your web server proxy. The proxy then passes the call onto the web service and in return passes the data back to your client application.

5 Reasons Your Company Should Use Proxy Servers

  1. Improve Corporate and Institutional Security
    Proxy servers add an additional layer of security between your servers and outside traffic. Because proxy servers can face the internet and relay requests from computers outside the network, they act as a buffer.
  2. Carry Out Sensitive Tasks Anonymously
    Proxies are probably best known for their ability to anonymize web traffic.
  3. Balance Traffic So Your Server Doesn’t Crash
    A proxy server instead is used to create a single web address to serve as the access point. The proxy will also balance the requests to each server so none overloads. All of this works in the background to ensure a seamless customer experience on your website.
  4. Control Employee Internet Usage
    When the network is accessed through a proxy, network administrators control which devices have access to the network and which sites those devices can visit.
  5. Faster Speeds and Bandwidth Savings
    Proxy servers can easily be used to increase speeds and save bandwidth on a network by compressing traffic, caching files and web pages accessed by multiple users, and stripping ads from websites.

Types of Proxy HTTP, HTTPS, Socks

The main purpose of a proxy is a change of IP address.

Proxy server has several types. HTTP, HTTPS, and Socks proxies are used more often.

  • HTTP and HTTPS proxies designed for web browsing
    HTTP proxy is the most wide-spread type of proxy. The main purpose is the organization of the work of browsers and other programs that use the TCP protocol. Standard ports 80, 8080, 3128.
    HTTPS proxy in fact it is the HTTP-proxy, the letter "S" in this case means "secure" with support of SSL connection. These proxies are used when you want to send sensitive information (eg usernames / passwords, plastic card numbers). Standard ports 80, 8080, 3128.
  • Socks proxy sends all the data to the destination server as a client, therefore considered the most anonymous protocol
    Socks 4 supports only TCP connection
    Socks 5 supports TCP, UDP, authorization by login and password, and remote DNS-query (We recommend to use Socks 5 proxy)

TORRENT PROXY: SOCKS VS. HTTP

There are two common proxy types: SOCKS Proxies and HTTP (Or HTTPS) Proxies. Is one better than the other for downloading torrents anonymously? The answer is yes. SOCKS5 Proxies are far superior for use with bittorrent.(233

what-is-void-in-javascript

the void Operator

Syntax: void UnaryExpression

  1. Let expr be the result of evaluating UnaryExpression.
  2. Perform ? GetValue(expr).
  3. Return undefined.

NOTE: GetValue must be called even though its value is not used because it may have observable side‐effects.

Examples

> void 0 // same as void(0)
undefined

> void 4+7 // same as (void 4)+7
NaN

> void(4+7)
undefined

> var x = 3;
3
> void(x = 5);
undefined
> x
5

Use case

  1. void 0 as undefined

void 0 and void(0) are the same as undefined, with one exception: undefined can be shadowed or redefined. So if you are paranoid about shadowing or globally changing undefined, use void 0.

  1. Immediately Invoked Function Expressions(IIFE)

When using an IIFE, void can be used to force the function keyword to be treated as an expression instead of a declaration.

> void function () { console.log(1) }()
1
undefined
  1. JavaScript URIs

A javascript: URI evaluates the code in the URI and then replaces the contents of the page with the returned value unless the returned value is undefined.
So we can often find <a href="javascript:void(0);"> in HTML. The reason you’d want to do this with the href of a link is that the void operator can be used to return undefined.

Reference

http://2ality.com/2011/05/void-operator.html
http://adripofjavascript.com/blog/drips/javascripts-void-operator.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void

difference between return await promise and return promise

difference between

function test1 () {
  try {
    return waitASecond();
  } catch (e) {
    return 'ahhhhh';
  }
}
function test2 () {
  try {
    return await waitASecond();
  } catch (e) {
    return 'ohhhhh';
  }
}

Disallows unnecessary return await (no-return-await)

Inside an async function, return await is seldom useful. Since the return value of an async function is always wrapped in Promise.resolve, return await doesn’t actually do anything except add extra time before the overarching Promise resolves or rejects. The only valid exception is if return await is used in a try/catch statement to catch errors from another Promise-based function.

Refs

https://jakearchibald.com/2017/await-vs-return-vs-return-await/
https://github.com/eslint/eslint/blob/master/docs/rules/no-return-await.md

writting a simple JSON parser in js

// https://www.json.org/json-en.html
const err = () => {
  throw new Error('Not valid JSON');
}

/**
 * 1. item splitted by comma
 * 2. item type are 'string/array/object' and others 
 *  I. first type, need to find out the right startIndex and endIndex
 *  II. second type, basic type without complex string
 * @param {string} data
 */
const _parseArray = (data) => {
  const stack = [];
  const comma = ',';
  const token = {
    '{': '}',
    '[': ']',
    '"': '"',
  }
  const itemString = data.slice(1, -1);
  let startIndex = 0;

  if (itemString.length < 1) return [];

  const res = [];
  console.log('itemString:', itemString);
  for (let i = 0, l = itemString.length; i <= l; i++) {
    let checkStack = true;

    if (stack[stack.length - 1] === '\\') { // handle \" in string
      stack.pop();
      if (itemString[i] === `"`) {
        checkStack = false;
      }
    } else if (itemString[i] === '\\') {
      stack.push('\\');
      checkStack = false;
    } else if (stack[stack.length - 1] === '"') { // handle the special token in string "xxx"
      if (itemString[i] !== `"`) {
        checkStack = false;
      }
    }

    // use stack to record special token pair
    if (checkStack) {
      if (itemString[i] === stack[stack.length - 1]) {
        stack.pop()
        console.log('-', token[itemString[i]]);
      } else if (token[itemString[i]]) {
        stack.push(token[itemString[i]])
        console.log('++', itemString[i], i, stack);
      }
    }

    if (!stack.length && itemString[i] === comma || i === itemString.length) {
      console.log('==', i, itemString.length, itemString[i], itemString.slice(startIndex, i))
      res.push(parseJSON(itemString.slice(startIndex, i)));
      startIndex = i + 1;
    }
  }
  return res;
}

/**
 * key:  string
 * value:  any type
 * @param {string} data
 */
const _parseObject = (data) => {
  const stack = [];
  let key = null;
  let startIndex = 0;
  const colon = ':';
  const comma = ','
  const keyToken = '"';
  const token = {
    '{': '}',
    '[': ']',
    '"': '"',
  }

  const itemString = data.slice(1, -1);
  const res = {};
  for(let i = 0, l = itemString.length; i <= l; i++) {
    // <string:>
    if (key === null) {
      if (stack.length === 0 && itemString[i] === keyToken) {
        stack.push(keyToken);
        startIndex = i;
      } else if (stack[stack.length - 1] === '\\') {
        stack.pop();
      } else if (itemString[i] === '\\') {
        stack.push('\\');
      } else if (itemString[i] === keyToken) {
        stack.pop();
      }

      if (stack.length === 0 && itemString[i] === colon) {
        key = parseJSON(itemString.slice(startIndex, i));
        startIndex = i + 1;
        console.log('key:', key);
      }
    } else {
      // this condition mostly reuse the parseArray's code
      let checkStack = true;

      if (stack[stack.length - 1] === '\\') {
        stack.pop();
        if (itemString[i] === `"`) {
          checkStack = false;
        }
      } else if (itemString[i] === '\\') {
        stack.push('\\');
        checkStack = false;
      } else if (stack[stack.length - 1] === '"') {
        if (itemString[i] !== `"`) {
          checkStack = false;
        }
      }

      if (checkStack) {
        if (itemString[i] === stack[stack.length - 1]) {
          stack.pop()
        } else if (token[itemString[i]]) {
          stack.push(token[itemString[i]])
        }
      }

      if (!stack.length && itemString[i] === comma || i === itemString.length) {
        console.log('value:', itemString.slice(startIndex, i));
        res[key] = parseJSON(itemString.slice(startIndex, i))
        startIndex = i + 1;
        key = null;
        console.log('res:', res);
        console.log('========')
      }
    }
  }

  return res;
}

/**
 *
 * @param {string} str
 */
function parseJSON(str) {
  if (str.startsWith('"')) { // string
    return str.slice(1, -1);
  } else if (str === 'true') { // true
    return true;
  } else if (str === 'false') { // false
    return false;
  } else if (str === 'null') { // null
    return null;
  } else if (!Number.isNaN(+str)) { // number
    return +str;
  } else if (str.startsWith('[')) { // array
    return _parseArray(str);
  } else if (str.startsWith('{')) { // object
    return _parseObject(str);
  } else {
    err();
  }
}

// console.log(parseJSON(JSON.stringify([null,2,true,[4],5])))
// console.log(parseJSON(JSON.stringify([4, 8, ["1[\"", 3, 4]])))
console.log(parseJSON(JSON.stringify({ 'a\,\"': [1, [1]], b: 2 })))

tapable-in-webpack

想要编写 webpack 的插件,官方建议开发者要对 CompilerCompilation 要有一定的学习掌握。当看到 Compiler 继承自 Tapable 时,我就直接进入 Tapable 的源码学习环节。

首先,一上来就看到 Tapable 构造函数中实例化了一个 SyncBailHook 类,并执行了该实例的 tap 方法。而 SyncBailHook 类是什么呢?再看向 Tapable 库的目录:它的 lib/index 包内容如下,它暴露了 9 个类似于 SyncBailHook 命名结构的方法。

exports.__esModule = true;
exports.Tapable = require("./Tapable");

// tapable包 暴露了 9 个类似命名结构的方法。 begin
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
// tapable包 暴露了 9 个类似命名结构的方法。 end

exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

从命名上,我们可以把他们进一步分类:

hook

大致浏览一遍以上 9 份文件代码,文件中,类的定义写法基本一致,皆用了工厂模式,看下面代码。

子类继承自 Hook 类。而 Hook 类是一个抽象类,该抽象类暴露出一个名为 compile 的接口,在子类中被重载。子类们对 compile 接口的实现,是使用了继承自抽象类 HookCodeFactory 的子类实例的 setup create 等接口。

即大致的模式为,Hookcompile 暴露给子类进行重写,重写的规则一致,通过传递 options 参数给工厂类加工,创建返回不同的钩子行为。

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class __xx__HookCodeFactory extends HookCodeFactory {
}
const factory = new __xx__HookCodeFactory();

class __xx__Hook extends Hook {
  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = __xx__Hook;

to be continued

removing-duplicates-from-an-array

根据实际场景选择不同去重方式

let uniq

// 一般来说,我们会选择遍历数组,
// 看一下当前元素在数组中第一次出现时的index,是否等于当前元素的index
// 等于则说明第一次出现,反之重复。
// 但是,indexOf 底层使用 === 判断,所以indexOf不能去重 NaN 元素:NaN === NaN // false
uniq = arr => arr.filter((item, index) => arr.indexOf(item) === index)

// ES7中的includes方法则可以区分NaN:http://www.ecma-international.org/ecma-262/7.0/#sec-array.prototype.includes
uniq = arr => arr.filter((item, index) => !arr.includes(item))

// 在filter方法的回调函数中使用第三个参数(array),so we can avoid a closure of the array variable
uniq = arr => arr.filter((item, index, self) => self.indexOf(item) == index)
// 如果对数组元素顺序无要求,可以排序后去重
// 排序后,过滤数组,过滤条件为如果是第一个元素或者相邻的元素不相同时,才为真
// 对一个已经排好序的数组去重,这种方法效率肯定高于使用 indexOf,且比indexOf(ES5)兼容性好
uniq = arr => arr.concat().sort().filter((item, index, self) => !index || item !== self[index - 1])
// 尽管以上的写法很简洁,但是如果需要去重的数组很大,那么就十分低效[O(n^{2})].
// 最好的办法是这样的:
// 将每个元素放在一个哈希表,然后立即检查它的存在。[O(n)]
// 但是却有些缺点:
// 1.因为hash keys在Javascript中只能为strings,所以不能区分1和'1':uniq([1,"1"]) 将返回 [1]
// 2.同样的原因,所有的objects会被认为是同一个:uniq([{foo:1},{foo:2}]) 将返回 [{foo:1}]
// 因此,如果你的数组元素都是原始值且你不需要区分数据类型,那么这就已经是最好的解决方案了:
uniq = arr => {
  let seen = {}
  return arr.filter(item => seen.hasOwnProperty(item) ? false : (seen[item] = true))
}
// ES6 provides the Set object, which makes things a whole lot easier:
uniq = arr => Array.from(new Set(arr))

// 或者
uniq = arr => [...new Set(arr)]
// A universal solution combines both approaches:
// it uses hash lookups for primitives and linear search for objects.
function uniq(arr) {
  let prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];

  return arr.filter(item => {
    let type = typeof item
    if(type in prims) 
      return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true)
    else
      return objs.indexOf(item) >= 0 ? false : objs.push(item)
  })
}

underscore源码分析:uniq方法

// 生成无重复元素的数组。
// 如果数组已经排序过,你可以传入第二个参数为true,以选择使用更快的算法。
// 第三个参数方便传入方法,在去重同时对元素进行操作
_.uniq = _.unique = function(array, isSorted, iteratee, context) {

  // isSorted未传入布尔值,则默认初始化部分参数
  if (!_.isBoolean(isSorted)) {
    context = iteratee;
    iteratee = isSorted;
    isSorted = false;
  }
  if (iteratee != null) iteratee = cb(iteratee, context);

  var result = [];
  var seen = [];

  for (var i = 0, length = getLength(array); i < length; i++) {
    var value = array[i],
        computed = iteratee ? iteratee(value, i, array) : value;
    if (isSorted) {
      // 与前一个元素作比较
      if (!i || seen !== computed) result.push(value);
      seen = computed;
    } else if (iteratee) {
      // 元素被操作改变过,则在seen数组中查找是否有相同元素
      if (!_.contains(seen, computed)) {
        seen.push(computed);
        result.push(value);
      }
    } else if (!_.contains(result, value)) {
      // 直接在result数组中查找是否有相同元素
      result.push(value);
    }
  }
  return result;
};

学习资料

Remove Duplicates from JavaScript Array

JavaScript专题之数组去重

也谈JavaScript数组去重

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.