Giter VIP home page Giter VIP logo

note's Introduction

领导自己

  1. 保持好奇心;
  2. 不分你我,不分边界;
  3. 坚持不懈;
  4. Done is done。

何为靠谱

  • 凡事有交代;
  • 件件有着落;
  • 事事有回应。

image

note's People

Contributors

yaofly2012 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

note's Issues

ES6: Set/WeakSet, Map/WeakMap

Set

一、语法

1.1 功能

值的集合,并且值不能重复。数组也是值的集合,但是数组的元素可以是重复的。

1.2 Set和数组

数组转成Set

  1. new Set(array)

Set转成数组

  1. Array.from

数组元素去重

var a = [1, 2, 3, 2];
a = Array.from(new Set(a)); // 或者a = [...new Set(a)]

注意:Array.indexOfSet使用的是不同的相等判断逻辑

二、Set是如何实现的

Map的实现(下面会说),只不过Set只有key,没有value。

参考

  1. ryf set-map
  2. MDN Set

expressjs 源码

Layer.js

Layer(path, options, fn)

怎么理解这个对象layer呢?本身的功能是路由匹配和调用中间件函数。但是Router和Route内部是使用了Layer对象。

  1. Router对象对应多个Route对象,对于Router对象来说每个Route对象就是一个“层”,Layer对象会被添加个route属性保存对应Route对象;但是use方法会创建个特殊的Layer对象,route属性为undefined的layer对象。
  2. Route对象对应多个Http Method处理函数,对于Route对象来说每个处理函数就是一个“层”,Layer对象
    会被添加个method属性,保存该层对应处理函数的Method。但是all方法会创建个特殊的Layer对象,method属性为undefined的layer对象。

match方法

匹配path并解析路径参数

handle_request方法

统一调用中间件函数的方式

handle_error方法

统一调用错误处理中间件函数方式

Layer对象属性

  1. handle
    中间件函数
  2. name
    handle函数名字就是Layer对象的名字,如果handle是匿名函数,则name取值为<anonymous>.
this.name = fn.name || '<anonymous>';
  1. path
    当前匹配的目标路径字符串
  2. params
    当前匹配的目标路径字符串获取的命名路径参数集合。
  3. regexp
    path-to-regex生成的正则表达式
  • fast_star
  • fast_slash
  1. keys
    path-to-regex生成的路径参数列表。

fast_slash和fast_star有什么区别?

// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
  1. fast_star是通配符*,表示匹配所有的path
  2. fast_slash匹配不带结束符的'/'
  3. 1和2都是概念上的区别,那实际用法场景有什么区别?

移动端适配

基本概念

一、三种视口(viewport)

1.1 可见视口

移动设备屏幕大小,能看见内容的区域。

1.2 布局视口

移动页面是从PC过渡过来的,移动端可见视口相对于PC可见视口是比较小的。PC页面放到移动端展示就会挤在一起了(如375px宽的屏幕肯定无法正常显示980px宽的PC页面),为了避免这种情况移动设备会用一个比较大的视图(一般980px)进行页面布局。这样布局视口的内容就不能完全展示了,要么进行左右滑动查看,要么缩小页面完全展示。浏览器选择了后者,即缩小布局视口展示在可见视口里(毕竟用户可以手动放大页面)。

这个layout viewport的宽度可以通过document.documentElement.clientWidth 来获取。

在基于REM布局中html的font-size计算就是按照布局尺寸算的。

1.3 理想视口

就是可见视口。讲布局视口的宽度和可见视口宽度一致。本质上采用可见视口的宽度进行布局。

茴字的四种写法—移动适配方案的进化

二、像素

一直以为最小只能显示1px,但其实并不是这样的。

2.1 物理像素(设备像素,device pixel)

<meta name="viewport" content="width=device-width">

2.2 独立像素(设备独立像素,device independent pixels,简写dips)

CSS布局像素,即PX。CSS里的1px并不是物理像素的1像素,那还得看独立像素比

1px的东西在屏幕上的大小与那些低分辨率的设备差不多

这里的1像素指的就是1 CSS px,CSS里px也是相对单位(相对于物理像素)。

在不同的屏幕上,CSS像素所呈现的物理尺寸是一致的,而不同的是CSS像素所对应的物理像素具数是不一致的。在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素。

2.3 独立像素比(window.devicePixelRatio)dpr

  1. 浏览器可以通过缩放操作调整dpr的值,同时window.devicePixelRatio的值也会跟着变化;

CSS像素、物理像素、逻辑像素、设备像素比、PPI、Viewport

2.4 视网膜屏幕

一般指ddr > 1的屏幕

三、0.5px问题

1px是CSS最小的单位,并且各个屏幕看起开擦不多。但是理论上在dpr >1的屏幕中可以展示更细的线条,也是大家经常说到的0.5px线条或则Retina屏幕1px问题。

总体方式是:如果设备支持(限iOS 8及以上设备)更好,不支持再hack

hack实现

判断设备是否支持0.5px

目前只能利用JS,可以参考flexible 2.0

// detect 0.5px supports
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }

hack实现方式

  1. viewport meta + rem
    有坑,flexible都放不用这个了。

  2. 伪类 + transform 的实现

.hairlines li:after{
    content: '';
    position: absolute;
    left: 0;
    background: #000;
    width: 100%;
    height: 1px;
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
    -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
}

只考虑了drp=2都case,严谨点可以利用媒体查询处理多种drp都场景(当然了代码量不小)
详细参考[前端]移动端Retina视网膜屏1px解决方案(H5))

参考

  1. 茴字的四种写法—移动适配方案的进化
  2. 使用Flexible实现手淘H5页面的终端适配
  3. [前端]移动端Retina视网膜屏1px解决方案(H5)
  4. 如何在Vue项目中使用vw实现移动端适配
  5. https://www.w3cplus.com/css/vw-for-layout.html
  6. Retina屏的移动设备如何实现真正1px的线?](https://jinlong.github.io/2015/05/24/css-retina-hairlines/)
  7. 知乎 移动端1px解决方案总汇

正则表达式

  1. 元字符作为一般字符匹配时需要转移;
  2. 除了可以匹配字符外,还可以匹配位置,即图中【位置匹配】部分;
  3. 量词
  • 修饰【字符匹配】,【捕获分组】,【非捕获分组】,【前瞻】;
  • 不能修改【位置匹配】(包含后瞻);
    可以包成分组进行匹配。
  1. 【候选】的优先级最低;
  2. 【位置匹配】
  • 都是0宽度,不影响匹配位置;
  • 可以对同一个位置同时进行多个匹配模式,相当于该位置要满足所有的匹配模式。
 /^^hello\b(?!\w)/.test("hello "); // true 

位置匹配^重复匹配一个位置,相当于一个位置匹配;
位置匹配\b(?!\w)都是对同一个位置进行匹配,字符b后面的位置即是个单词结尾又是非单词字符(本例是空格字符)的前面

  • 密码规则校验问题
    密码字符集一般是有安全要求的,比如:必须包含数字,大小写字母等。可以使用【位置匹配】:
// 规则1 = 长度6到15位,必须是数字,字母,下划线,感叹号,中划线
/^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // true,很容易实现

// 规则2 = 规则1 + 必须包含大写字母
/(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // false
/(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // true

// 规则3 = 规则2 + 必须包含下划线
/(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // false
/(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123_'); // true

原理就是通过找满足条件的位置来判断目标字符串是否符合指定的规则。规则2(规则3同理)的正则表达式相当于对起始位置添加了多个匹配条件。
6. 回溯
为了判断是否匹配完成,进行了一些尝试。尝试的副作用就是造成回溯(造成匹配位置倒退的行为)。
回溯会影响性能,写正则尽量规避造成回溯。

参考

  1. 《JavaScript正则迷你书》

CSS 选择器

概念

选择器是匹配DOM元素的模式。
选择器是应用整个DOM树的,不论在HTML文档任意位置定义的CSS都会在整个DOM树中查找匹配。

一、伪元素 & 伪类

他们都是选择器

1.1 伪类

  1. 伪类是基于元素的状态(active, focus等),位置(根元素,第几个等),结构类型等属性进行匹配。
  2. 伪类可以修饰除了伪元素之外的任意选择器,伪类可以修饰伪类

1.2 伪元素

  1. 伪元素不是DOM元素,它只是选择器。主要用来匹配元素的内容,或者用于生成内容(::after/before);
  2. 不存在DOM树中,但可以渲染到页面上的元素(比如生成内容);
  3. 一个选择器中只能包含一个伪元素选择器部分。

给伪元素绑定事件

  1. 伪元素不是DOM元素,它不存在DOM树中,所以无法进行事件绑定。
  2. 但是伪元素也是可以触发事件的(比如点击::after/before生成元素),但是事件的target是伪元素依附的DOM元素,即相当于是伪元素依附的DOM元素触发的事件。

参考

  1. Selectors Level 3
  2. w3schools css_selectors
  3. CSS3 Selectors Test

Generator函数

一、Generator function(生成器函数)

1.1 语法

function* name([param[, param[, ... param]]]) {
   statements
}
  1. 格式上跟普通函数就多个了星号*
    星号*function,函数名之间可以没有空格,一般把星号和function放一起。

  2. 生成器函数返回值是个生成器对象

vvar toString = Object.prototype.toString;
var gen = function* () {
    console.log('begin')
    yield 1;
    yield 2;
    return 5;
}

var genObj = gen();
console.log(typeof gen) // function
console.log(toString.call(gen))     // [object GeneratorFunction]
console.log(gen instanceof Function) // true
console.log(toString.call(genObj))  // [object Generator]
console.log(typeof genObj) // object
  1. 函数表达式,匿名函数都可以定义生成器函数;
  2. 生成器函数可以作为对象和类的成员方法,但是不可以作为
  • 构造函数
    构造函数有自己的返回值逻辑,并且构造函数是解决继承写法问题。虽然不能作为构造函数,但生成器函数也是有prototype属性的。
  • 箭头函数
    没有function关键字。

1.2 特性

  1. 生成器函数可以终止,可以继续执行(反人类);
    再次执行时上下文跟上次暂停时一样。
  2. 生成器函数是“惰性的”,当调用生成器函数的时候不会立马执行,而是等继续(next/throw/return)执行时才执行相关代码,遇到下一个yield表达式时暂停(或者return/throw语句结束);
var gen = function* () {
    console.log('begin')
    yield 1;
    yield 2;
    return 3;
}

var g = gen();
console.log('Generator created')
// [object Generator]
console.log(Object.prototype.toString.call(g))

console.log(g.next()) // 
console.log(g.next())

image

  • 可以简单理解为yield关键字把生成器函数分割成一段段可执行代码片段,调用next方法时就执行其中的代码片段
  • returnthrow方法会立即终止,不会再次执行。
  1. return语句表示生成器函数最终的值
var gen = function* () {
    yield 1;
    yield 2;
    return 3;
}

var g = gen();

console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.next()) // {value: 3, done: true}
console.log(g.next()) // {value: undefined, done: true}
  1. 如果生成器函数发生异常,则next方法直接向外抛:
var gen = function* () {
    yield 1;
    throw new Error('error occured')
    yield 2;
    return 5;
}

var g = gen();

console.log(g.next())
try {
    console.log(g.next())
} catch(e) {
    console.error(e)
}
console.log(g.next())
console.log(g.next())

image

  • 相当于执行next方法内部发生了异常;
  • 并且记录了错误栈信息
  1. yield* 写法表示遍历其他生成器对象
function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i) {
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

yield*相当于:

function* generator(i) {
  yield i;
  let ag = anotherGenerator(i);
  for(let val of ag) {
    yield val ;
  }
  yield i + 10;
}

本质上yield*后面可以是任意可迭代对象

var gen = function* () {
    yield 1;
    yield* [12, 123];
    return 3;
}

var g = gen();

for(let val of g) {
    console.log(val)
}

二、生成器对象(Generator)

生成器对象是一个内部含有状态的对象,通过并且只能通过生成器函数创建。

2.1 语法

生成器函数的返回值。

  1. 利用Object.prototype.toString判断生成器函数和生成器对象类型
function* gen(i) {
  yield i.count + 1;
  yield i.count  + 2;
  yield i.count  + 3;
}

var a = {
    count: 1
};
var g = gen(a)

Object.prototype.toString.call(gen)  // [object GeneratorFunction]
Object.prototype.toString.call(g) // [object Generator]

// 可迭代对象
var arr = [...g]
console.log(arr)  // [2, 3, 4]
console.log(Symbol.iterator in g) // true
  1. 跟null,undefined类型类似,并没有定义全局对象GeneratorFunctionGenerator
  2. 生成器函数内部的yield表达式和return语句用于定义生成器对象的内部状态;
  3. 生成器对象既是可迭代对象也是迭代器(其next方法符合迭代器协议)。

2.2 遍历

生成器对象本身就是个可迭代对象,但是只能一次性遍历。

function* gen() {
    yield 1;
    yield 3;
    yield 5;
}
var g = gen();

// 第一次遍历
for(let num of g) {
    console.log(num);
}

// 第二次遍历- 
for(let num of g) {
    console.log(num); // 不会再执行了
}

2.3 APIs

1. next(value)

  • next方法用于获取生成器对象下一个状态,即 yield后面表达式的值
  • 参数value可以指定生成器函数里 yield表达式的值 (注意区分“yield后面表达式的值”和yield表达式本身的值
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false},yield表达式的值为undefined
a.next() // Object{value:NaN, done:false},yield表达式的值为undefined
a.next() // Object{value:NaN, done:true},yield表达式的值为undefined

var b = foo(5);
b.next() // { value:6, done:false },yield表达式的值为undefined
b.next(12) // { value:8, done:false },yield表达式的值为12
b.next(13) // { value:42, done:true },yield表达式的值为13
  • next方法的实参作为yield表达式的值;
    外部可以用作向生成器对象传递值,并告诉生成器对象继续执行。

  • next方法的返回值就是yield后面的表达式的值。
    外部可以用作获取生成器对象内部的值

这种机制很重要,是实现异步同步化的关键。

2. return(value)

让生成器函数在上次暂停的地方(或者函数开始处)立马结束,并返回指定的状态值(return方法实参):

{
  done: true,
  value: value // return方法的实参
}
  1. return方法可以多次调用,返回值的value属性是调用时的实参;
    这个跟next方法的返回值逻辑不一样。
  2. 调用return方法时,生成器函数从上一次暂定的地方或者函数开始处就结束(即后面的代码不会再执行);
var gen = function* () {
    console.log('begin run')
    throw new Error(1);;
    yield 1;
    yield 2;
    return 5;
}

var g = gen();
// 立马结束,生成器函数没有执行代码的机会
console.log(g.return(1)) // {value: 1, done: true}
  1. 立马结束和finally语句块必须执行的平衡
    try-finally语句块中finally语句块中语句必须要执行,这跟return方法立马结束有点冲突。
    当调用return方法时,如果上个yield表达式在try语句块中则return方法会推迟到finally代码块执行完再执行,并且可以修改返回值。即保证了finally的语法规则不变(一定要执行,可以修改try/catch中的返回值)。
function* foo() {
    yield 1;
    try {
        yield 4;
    } finally {
        yield 5
    }
}

var a = foo();
console.log(a.next()) // {value: 1, done: false}
console.log(a.next()) // {value: 4, done: false}
console.log(a.return(2)) // {value: 5, done: false} ,继续执行finally语句块(新的状态,修改原return的值)
console.log(a.next()) // {value: 2, done: true} , return方法指定的终态值 
console.log(a.next()) // {value: undefined, true}

3. throw(exception)

让生成器函数在上次暂停的地方(或者函数开始处)抛出指定异常(throw方法实参)。

  1. throw返回值的取值逻辑同next方法;
    可以把throw方法视为抛出异常的next方法。
  2. 生成器函数在什么节点抛异常?
function* gen() {
  yield 1;
  yield 2;
  try {
    yield 3;
  } catch(e) {
    console.log('error catch in gen')
  }
}

var gg = gen();
console.log(gg.next());
console.log(gg.next());
console.log(gg.next());
console.log(gg.throw('error occured'));

throw方法相当于让生成器函数在上次暂停的地方(或者函数开始处)处抛一个异常,如果生成器函数没有捕获,则会向外抛,则相当于调用throw处抛一个异常,也说明了函数开始处抛出的异常无法内部捕获。

function* gen() {
  yield 1;
  yield 2;
  try {
    yield 3;
  } catch(e) {
    console.log('error catch in gen')
  }
}

var gg = gen();
console.log(gg.next());
console.log(gg.next());
console.log(gg.throw('error occured')); // 这个异常生成器函数没有捕获到

如果要捕获生成器没有捕获的异常,得在调用throw处捕获:

function* gen() {
  yield 1;
  yield 2;
  try {
    yield 3;
  } catch(e) {
    console.log('error catch in gen')
  }
}

var gg = gen();
console.log(gg.next());
console.log(gg.next());
try {
  console.log(gg.throw('error occured'));
} catch(e) {
   console.log('error catch in callee')
}
  1. 如果生成器函数如果没有捕获抛出的异常,则生成器函数会被终止。

4. 总结

  1. 生成器函数是创建生成器对象的工厂方法,可以参数化创建;
  2. 生成器对象的next/return/throw方法都是用于恢复生成器函数执行,区别是:
  • next:继续执行;
  • return:终止执行,并返回指定的值;
  • throw:终止执行,并抛出指定的异常。

这三个方法是外部控制生成器对象的执行,可用于实现异步逻辑同步化关键。

  1. 阮老师这个总结的到位 next()、throw()、return() 的共同点

next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

用“语句替换”描述只是便于理解吧,但是有点误导。起码returnthrow方法可以在函数开始处立马结束(生成器函数都没有执行)

var gen = function* () {
    console.log('begin run')   
    yield 2;
    return 5;
}

var g = gen();
console.log(g.return(1)) //  或者console.log(g.throw(1))都是的生成器函数没有执行

三、生成器原理

先看看下面输出是什么:

// Demo1
var a = 0
function* gen(x) {
    a = a + (yield x);
    console.log(3, a)
}

var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);

稍微调整代码,下面输出是什么:

// Demo2
var a = 0
function* gen(x) {
    var b = yield x;
    a += b;
    console.log(3, a)
}

var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);

再稍微调整代码,下面输出是什么:

// Demo3
var a = 0
function* gen(x) {
    a =  (yield x) + a;
    console.log(3, a)
}

var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);
  1. 执行下文会被暂存
    遇到yield表达式时,生成器函数暂停执行,退出调用栈,但是执行下文会被暂存。

  2. 恢复执行
    暂存的执行上下文也被恢复。

回到上面的问题Demo1和Demo2的结果为啥不一样?
Demo1中语句 a = a + (yield x);算术运算从左向右执行的,等执行到yield x时变量a已经参与运算了,暂停执行的时候,会存在临时变量里了。相当于:

function* gen(x) {
    var tem = a;
    a =  tem + (yield x);
    console.log(3, a)
}

参考

  1. [译] 关于 ES6 生成器 Generator 的探索
  2. MDN generator function(生成器函数)
  3. MDN Generator(生成器对象)
  4. 阮一峰

WorkBox

1 route模块

1.1 缓存策略

statleWihieRevalidate PK netWorkFirst
serving-suggestions

window和sw scope共享CacheStorage ? !

1.2 跨域问题

opaque responses

1.3 Route.handleCallback

处理函数的content.params的值来自match函数的返回值,但是如果返回值是bool值则content.params为undefined(要看源码才知道为啥这样)。

1.4 Router, DefaultRouter

用来管理注册的route。处理流程见图
workbox.routing是DefaultRoute对象

  • registerNavigationRoute(cachedAssetUrl, options)
    注册个NavigationRoute,匹配的请求都返回由cachedAssetUrl指定的预缓存Response。如果预缓存里没有,则从网络获取(此时Response不会被缓存)。

  • registerRoute(capture, handle)
    capture可以是

  1. String -> 创建Route
    字符串是精确匹配的方式:
const matchCallback = ({ url }) => {
      return url.href === captureUrl.href;
};
  1. RegExp -> 创建RegExpRoute

  2. Function -> 创建Route

  3. Route -> 直接使用参数Route,忽略handle参数了

1.5 三种route:Route, RegExpRoute, NavigationRoute

const matchCb = ({url, event}) => {
  return (url.pathname === '/special/url');
};
  • RegExpRoute

For requests from the same origin, this regular expression will match as long as the request’s URL matches the regular expression.
However, for cross-origin requests, regular expressions must match the beginning of the URL

主要目的是为了开发自己明白自己是处理跨域的资源还是要处理同域的资源(同域认为是自己的)。
RegExpRoute的源码:

const match = ({ url }) => {
        const result = regExp.exec(url.href);
        // Return null immediately if there's no match.
        if (!result) {
          return null;
        }
        if (url.origin !== location.origin && result.index !== 0) {
          return null;
        }
        return result.slice(1);
      };

通过判断 result.index是否为0来确定的(故意的)。这样我们可以通过正则的写法来绕过跨域的限制。
所以workbox的“免责”来了:

If you wanted to match both local and third parties you can use a wildcard at the start of your regular expression, but this should be done with caution to ensure it doesn’t cause unexpected behaviors in you web app

  • NavigationRoute

It will only match incoming Requests whose mode is set to navigate

  1. 扫盲什么是navigation request

  2. NavigationRoute只匹配navigation Request,默认情况下其他的都通过,可以使用blacklist正则表达式数组配置黑名单增加更严格的限制。

  3. 如果要修改默认行为可以使用whitelist配置,whitelist的默认值是[/./],即匹配任意URL。

_math源码如下:

_match({ event, url }) {
      if (event.request.mode !== 'navigate') {
        return false;
      }
      const pathnameAndSearch = url.pathname + url.search;
      if (this._blacklist.some(regExp => regExp.test(pathnameAndSearch))) {
        return false;
      }
      if (this._whitelist.some(regExp => regExp.test(pathnameAndSearch))) {
  
        return true;
      } 
      return false;
    }

注意:

  1. request.mode, blacklist和whitelist三个条件的判断逻辑
  2. 只对路径和参数进行判断,不对域名进行check,即正则里不要包含域名部分

2 precaching模块

workbox.precaching.precacheAndRoute([
  '/styles/example.ac29.css',
  {
    url: '/index.html',
    revision: 'as46',
  }
]);
  1. url
    表示资源的路径

  2. revision
    是可选的,如果省略则取值为url的值,即认为资源的url也可代表资源的版本。
    版本可以是任意字符串,一般以资源内容的hash值作为版本号。

cacheable Response(可缓存的响应)

  1. 只针对运行时缓存
    workbox只对“有效的”响应进行缓存,那判断响应的有效性没有统一的标准,开发者可以使用cacheableResponse模块进行自定义“什么是有效的响应”。

  2. 默认行为

    • workbox各种内置策略
    • sw add/addAll行为

调试

Chrome

查看浏览器的serviceWorker

chrome://inspect/#service-workers

参考

  1. 官网
  2. Google ServiceWork
  3. concept-request-destination

源码分析

写的不错,必须看看

Nextjs

Nextjs是什么?

Docs

  1. Nextjs围绕Page这个概念构建。

React脚手架

create-react-app
umi
next

path to regexp

参考

  1. path-to-regexp

功能

将路径(path)字符串模式,转成正则表达式。可以用于对request进行匹配,并解析路径参数。

注意

虽然path-to-regexp目前已经是2.4.0版本了,但是ExpressJs 4.X依赖的是0.17版本的,有些特性在ExpressJs还不能使用。

语法

var pathToRegexp = require('path-to-regexp')
var re = pathToRegexp(path, keys?, options?)

怎么理解?

Please note: The RegExp returned by path-to-regexp is intended for ordered data (e.g. pathnames, hostnames). It does not handle arbitrary data (e.g. query strings, URL fragments, JSON, etc).

path-to-regexp只处理URL中有序的数据部分(参数匹配依赖顺序),即URL的host, path部分,不处理QueryString, URL片段(这部分数据是无序的,如?a=1&b=2?b=2&a=1是一样的)。

keys

  1. keys数组返回所有path参数;
    keys数组元素的位置和path-to-regexp返回的正则分组一一对应的,这样可以找到path参数名字和值。
  2. 如果没有路径参数,则keys为空数组

路径参数

1. 命名路径参数

格式:

:参数名称
  1. 参数名称字符集必须是单词字符:[a-zA-Z0-9_]
  2. 可以获取指定位置的path片段(字符串)作为参数值。
  3. 本质上路径参数会被转成正则表达式的捕获分组格式([^\/]+?),并且是懒惰模式的

2. 匿名路径参数

只有捕获分组正则表达式字符串

路径参数修饰符

  1. 可选性?
  2. 重复性+, *
    0.1.7不支持
  3. 自定义路径参数正则
    默认情况下对于路径参数会被转成正则表达式的捕获分组格式([^\/]+?),可以修改这个正则:
:参数名称(自定义正则字符串)

options(控制正则表达式生成规则)

  1. sensitive
    <bool>, 是否大小写敏感,默认false,即大小写不敏感
  2. strict
    <bool>, 是否是严格模式,默认false,即非严格模式。什么是“严格模式”?
    严格模式下path尾部有无"/"将视为不同的path,如/index/index/是不同的path,而非严格模式则视为相同的path。
  3. end
    <bool>, 是否添加结束符$,默认true,即匹配结束符。
    需要注意的是即使end为false,并不是表示匹配path字符串任意位置,此时它是以'/'和'$'作为可选的结束位置。可以看看生成的结果:
var regExp1 = pathToRegexp('/foo', []) // => /^\/foo\/?$/
var regExp2 = pathToRegexp('/foo', [], {end: false}) // => /^\/foo\/?(?=\/|$)/

regExp2会匹配'/foo', '/foo/bar',但不会匹配'/fooBar'.
5. start
<bool>, 是否添加起始符^,默认true,即匹配起始符。0.1.7不支持,即一直为true。

ExpressJs的路由匹配规则

使用在线的Tool Express Route Tester

ReactJS

一直忙于搬砖,忽然觉得对Reactjs即属性,又陌生。从第一次写Reactjs相关的笔记到现在也过去几年了

Slogan

  1. Declarative【声明式】

React makes it painless to create interactive UIs.
擅长处理可交互的页面。

  1. Component-Based【基于组件的,组件化】
  • 封装->组件
  • 组合->组件
  1. Learn Once, Write Anywhere【学习一次,到处使用】

途径

官方文档Docs, Blog

官方文档路线

教程

快速入门的使用教程

文档

详细说明文档

  1. 核心概念
  2. 高级指引
  • 高级概念
    • Portal
  • 性能优化
  • 编码质量
    • 严格模式
    • 静态类型检查
    • 使用 PropTypes 类型检查

API文档

Controlled Component

React组件的状态是自己维护的,一般是在JS环境中调用setState方法更新的,但是对于表单元素他有自己的状态,数据的更改可能来自用户的输入。为保证状态的一致性,需要实现表单数据“双向绑定”。

  1. React实现起来确实麻烦,Vue通过指令帮我们做了。

antd

Form

组织:
表单,表单域

  1. 多个校验规则如何执行的?

属性

  1. layout
  • inline
  • vertical
  • horizontal

方法

  1. getFieldDecorator
  2. getFieldsError
  3. getFieldError
  4. isFieldTouched
  5. validateFields
  6. resetFields

静态方法

  1. Form.create

field decorator

Form.Item

  1. validateStatus

IOS12 软键盘收起问题

IOS12 软键盘收起问题页面会有空白区域,导致fix定位的元素热不偏移。
解决方案:
显示执行scrollTo,如:

document.querySelectorAll('input[type="text"]').forEach(function($input){
				$input.addEventListener('blur', function() {
	    			window.scrollTo(window.scrollX, window.scrollY) // 原位置滚动
	    		})
    		})

https://segmentfault.com/n/1330000016760295

ServiceWorker生命周期

SW生命周期

  1. SW状态可分为两个独立的过程:
  • 安装过程
  • 激活过程
  1. 从安装过程进入激活流程条件:
  2. 进入废弃 (redundant) 状态的原因可能为这几种
    Lavas Service Worker 生命周期the-service-worker-lifecycle参考中列举了3中,但是测试发现激活失败并不会导致哎,见下面【activate事件 & ActivateEvent对象】。在这本书里Building Progressive Web Apps by Tal Ater的观点貌似论证了我们代码。这里再汇总下进入redundant的case:
    • register失败,如多次调用register,后调用注册的SW会变成redundant
    • install失败
    • 被新的SW代替

install 事件 & InstallEvent对象

  1. 多个事件回调installEvent对象事同一个对象;
  2. 如果回调函数里调用installEvent.waitUntil(promise1)方法,则表示promise1被resolved时SW对象才被installed,如果prmose1被rejected则SW安装失败(redundant)
  3. 同一个事件回调函数或者多个事件回调函数可以多次调用installEvent.waitUntil(promise)方法,则表达所有的promise都被resolved时SW对象才被installed,只要存在promise被rejected则SW安装失败(redundant)
self.addEventListener('install', function(event) {
    event._from = 'callback1'
    console.log('install callback 1: ' + (Date.now()/1000))
    var i = 0;
    while(i < 1000000) {
        ++i;
    }
    // 多次调用
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('resolve 2s')
            resolve()
        }, 2000)
    }))
    event.waitUntil(new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log('resolve 3s')
            reject() // Reject promise
        }, 3000)
    }))
})

// 多次绑定Install事件
self.addEventListener('install', function(event) {
    event._from = 'callback2'
    console.log('install callback 2: ' + (Date.now()/1000))
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('resolve 5s')
            resolve()
        }, 5000)
    }))
})

activate事件 & ActivateEvent对象

  1. 多个事件回调ActivateEvent对象是同一个对象;
  2. 如果回调函数里调用activateEvent.waitUntil(promise1)方法,则表示promise1被resolved/rejected时SW对象才被activated;注意这里和installEvent不一样的,promise被rejected SW依旧可以被activated。
  3. 同一个事件回调函数或者多个事件回调函数可以多次调用activateEvent.waitUntil(promise)方法,则表达所有的promise都被resolved/rejected时SW对象才被activated。注意这里和installEvent不一样的,某个promise被rejected SW依旧可以被activated。
  4. 新SW激活过程中说明页面已经没有被其他SW控制了,所以activate事件回调函数的执行失败并不会影响SW被激活。

fetch事件 & FetchEvent对象

可以绑定多个fetch事件。

  1. 多个回调函数的fetchEvent是同一个对象;
  2. 如果回调函数内成功调用了fetchEvent.respondWith方法,则后面的回调函数不会被执行;
  3. 回调函数里多次调用fetchEvent.respondWith会报错的,即一个request只能有一个reponse;
  4. 回调函数里最好是同步的方式调用fetchEvent.respondWith,异步调用不会阻止后面的后调函数执行, 很容易会造成多个reponse,导致错误3;
  5. 如果所有的回调函数里都没有调用fetchEvent.respondWith方法则会采用浏览器默认的fetch事件回调函数处理方式,即走网络请求。
  6. 回调函数里调用event.waitUntil并不能延迟后面的回调函数执行。
    waitUntil方法只能延迟SW的生命周期事件回调(intall事件影响activate, activate影响fetch事件)
  7. 综上fetch事件多个回调函数是同步循环调用的。
  8. fetch回调函数还可以调用waitUntil函数,来延长FetchEvent事件对象的生命,如果有FetchEent对象还未处理完浏览器是不会自动关闭SW的。
self.addEventListener('fetch', function(event) {
    debugger
    event._from = 'callback1'
    console.log('fetch callback 1')
    //event.respondWith(fetch(event.request))
})
self.addEventListener('fetch', function(event) {
    console.log(`fetch callback 2, from:${event._from}`)
    event._from = 'callback2'
    //throw 'abc'
    event.respondWith(fetch(event.request))
})
self.addEventListener('fetch', function(event) {
    console.log(`fetch callback 3, from: ${event._from}`)
})

参考

  1. Google Service Worker 生命周期
  2. Lavas Service Worker 生命周期
  3. the-service-worker-lifecycle
  4. Building Progressive Web Apps by Tal Ater

Nodejs——fs

fs.writeFile(file, data[, options], callback)

文档中有这么一段话:

对同一文件多次使用 fs.writeFile 且不等待回调,是不安全的。 对于这种情况,建议使用 fs.createWriteStream。

因为方法是异步的,多次调用无法保证执行的顺序。使用fs.createWriteStream可以显示的控制写入顺序。

var fs = require('fs');

fs.writeFile('demo.txt', 'Node.js 中文网', (err) => {
  if (err) throw err;
  console.log('文件已保存!');
});

fs.writeFile('demo.txt', 'Node.js 中文网 2', (err) => {
  if (err) throw err;
  console.log('文件已保存 2!');
});

fs.writeFile('demo.txt', 'Node.js 中文网 3', (err) => {
  if (err) throw err;
  console.log('文件已保存 3!');
});

顺序无法控制

var ws1 = fs.createWriteStream('demo.txt');
ws1.end('文件已保存!', 'utf-8', (err) => {
  if (err) throw err;
  console.log('文件已保存!');
});

var ws2 = fs.createWriteStream('demo.txt');
ws2.end('文件已保存!2', 'utf-8', (err) => {
  if (err) throw err;
  console.log('文件已保存!2');
});

var ws3 = fs.createWriteStream('demo.txt');
ws3.end('文件已保存!3', 'utf-8', (err) => {
  if (err) throw err;
  console.log('文件已保存!3');
});

顺序可控。

issues/concern:

  1. file-system解决了哪些痛点

Babel

Babel handbook

Study

一、Babel是什么

  1. Babel is a JavaScript compiler.
  2. Use next generation JavaScript, today。Beyond!

是"源码到源码"的编译器。

说人话

  1. 最初的工作就是把ES6+语法和API转成目标环境可以执行的语法(ES3,ES5);
  2. 后来强大到把各种形式的JS(Reat, TypeScript等)都转成目标环境可以执行的语法(ES3,ES5,ES6+);
  3. Beyond,反正可以通过操作AST语法树完成的工作都可以借助Babel完成。
  • JS相关的优化(压缩等)工作

二、babel模块组织(version-7)

从v7开始,babel的所有模块都单独的定义在@babel下面,可以单独使用各个模块。
模块也采用新的命名规则,详细见Upgrade to Babel 7

  1. @babel/核心模块
  2. @babel/preset-{预设名字}
    可以简写@babel/{预设名字}
  3. @babel/plugin-{插件名字}
    可以简写@babel/{插件名字}
  4. @babel/polyfill

babel/core

babel核心模块,转换工作的依赖的模块。所有babel的工作都离不开这个模块。
大胆猜测大概的功能:

  1. babel配置加载并初始化;
  2. 把源码转成AST;
  3. 调度管理plugins和presets操作AST;
  4. 把最终的AST转成源码,并输出到目标文件。

@babel/core模块只是负责转换代码,作为dev deps

babel/cli

通过命名行使用babel编译文件,内部依赖@babel/core

polyfill

三、plugins

具体的转码工作由plugins完成的。

{
    "plugins": ["@babel/plugin-transform-arrow-functions"]
}

语法插件和转换插件

  1. 语法插件
    只是让babel能够解析特定类型的语法(即让Babel知道怎么生成对应的AST),但不做转换操作。
  2. 转换插件 = 语法插件 + 执行转换操作

plugins执行顺序

  1. 先于presets执行;
  2. 由前到后一次执行。
    各个plugin一般是互相独立的,都是完成具体某个的转换工作。所以由前到后依次执行。但是有些plugins(如类型属性和装饰器)可能是先后依赖的,注意他们的顺序。

自定义plugin ???

四、presets

预定义的一组plugin集合,方便书写配置。

presets执行顺序

  1. 后于plugins执行;
  2. 由后到前依次执行(跟plugins执行顺序相反)。
    原因:
    presets是具有语义环境的(es2015,es2016分表示ES的不同版本),一般是由依赖关系的。
    书写的顺序符合人类的思维(由老到新),但是在转换的时候考虑到向后兼容,是由后到前依次执行。

env预设

代替之前各种ES版本的presets(es-2015,es-2016)。现在统一使用env表示各种版本ES(简化了ES的配置)。

五、配置

https://babeljs.io/docs/en/configuration
https://babeljs.io/docs/en/config-files#configuration-file-types

默认配置和显示的配置

  1. 默认配置
    没有配置文件,命令行不指定配置

  2. 编程的方式

  3. 命令行参数方式

  4. .babelrc,静态的JSON文件
    在项目根目录(即package.json所在的目录)有这个文件就读取这个文件的配置,就不存在默认的配置了,必须显示的配置。

  5. .babelrc.js

  6. package.json

  7. babel.config.js

七、 babel 能搞定一切吗?

Caveats

Issues ?

  1. babel如何处理扩展名不是js文件的 ?
  2. inde.jsx文件为啥没有输出?
  3. Since Babel assumes that your code will run in an ES5 environment it uses ES5 functions
    如何修改到ES3环境?

好文章

  1. 知乎:Babel是如何读懂JS代码的

Next... 研究

Babel DOC 使用章节。Babel是个需要认知研究的工具,

当项目使用多个npm包,如何解决babel代码重复问题?

打包工具

grunt
gulp
webpack

vuejs为什么放弃webpack改成gulp打包工具了

expressjs- Study

What is Express ?

一、概述

  1. 基于Nodejs的Web框架。
  2. slogan:
  • Fast(快速)
    对比数据?
  • Unopinionated(开放)
    是什么是开放?
  • Minimalist (极简)
    怎么讲?

二、构成

express = 路由匹配(routing) + 中间件(middleware) ?

核心概念:

  1. 中间件
  2. 路由匹配
  3. 视图引擎
  4. 静态文件

requestIdleCallback

参考

  1. MDN window.requestIdleCallback()
  2. Google Using requestIdleCallback
  3. 你应该知道的requestIdleCallback
  4. 加载时间从4.6s降到0.7s,谷歌开源quicklink
  5. Background Tasks API

一、语法

window.requestIdleCallback()注册个函数(插入一个队列里),等浏览器空闲时(browser's idle periods)才执行逐个这些函数。

1.1 什么时浏览器空闲

requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.
A reference to a function that should be called in the near future, when the event loop is idle.

  1. Event Loop为空时;
  2. a frame是什么?

1.2 callback执行顺序 。

  1. 先依次执行已超时的回调;
  2. 再依次执行未超时的回调;
  3. 每一帧不一定执行回调,取决于该帧中是否有空闲时间。

注意:callback的具体什么时候调用是未知的,不要做一些依赖执行顺序的操作。

1.3 回调函数的参数 IdleDeadline

13.1 方法:timeRemaining

获取从调用requestIdleCallback函数注册回调函数,到函数被执行时距离超时还有多久(单位ms)

  1. 因为剩余时间需要实时计算,所以是通过方法获取的,这样每次调用都要重新计算;

  2. callback最好在剩余时间内执行,如果执行不完最好终止执行,并重新调用requestIdleCallback注册新的回调。
    时间分片

  3. 当EventLoop空闲时,timeRemaining会比较大,一般最大是50ms。
    以防止突出插入新的任务(比如用于突然发生交互,获取请求返回等)。

1.3.2 属性:didTimeout

表示是否因为超时而造成的回调执行

1.4 回调函数里不宜做的事情

  1. DOM操作
  2. Promise的resolve(reject)操作也不建议放在里面,因为Promise的回调会在idle的回调执行完成后立刻执行,会拉长当前帧的耗时,所以不推荐

不懂?看看这个从 event loop 规范探究 javaScript 异步及浏览器更新渲染时机

二、polyfill

setTimeout模拟。

window.requestIdleCallback = window.requestIdleCallback || function(handler) {
  let startTime = Date.now();
 
  return setTimeout(function() {
    handler({
      didTimeout: false,
      timeRemaining: function() {
        return Math.max(0, 50.0 - (Date.now() - startTime));
      }
    });
  }, 1);
}
  1. 实参didTimeout是无法模拟的,直接赋值false;
  2. 实参timeRemaining函数记得设置个最大值50ms。

SW——ServiceWorkerContainer->ServiceWorkerRegistration->ServiceWorker

ServiceWorkerContainer

  1. 保证一个页面同一时刻只能受一个serviceWorker控制

register(scriptsURL, scope)

页面通过register方法创建/更新ServiceWorkerRegistration对象。注意不是直接操作ServiceWorker对象而是ServiceWorkerRegistration对象。调用register方法的时候如果页面已经被SW控制了则是更新操作了

页   面:hi,浏览器。帮我向这个ServiceWorker发起注册申请。ServiceWorker信息是:scriptsURL, scope
浏览器:
  1. 页面可以多次调用register方法;

    • 多次注册相同的sw,则相当于注册多个registation回调
    • 多次注册的sw不同,则先注册的的SW会注册失败,变成redundant状态
  2. SW的scope最大范围是JS所在的目录,不能大于这个范围,register方法指定的scope只能比默认范围小;

  3. scriptsURL必须是同域的【保证安全性】

  4. 源,scope, scriptURL三个维度唯一标示一个SW。

  5. 一个origin下同一个scope下面可以有多个sw么?
    不可以,这是为保证【一个页面同一时刻只能受一个serviceWorker控制】。如果出现多个,则行为同更新sw流程。

浏览器是如何加载scriptsURL脚本的?

ready属性

是个Promise对象,当ServiceWorker对象变成activated后ready属性才被resolve,否则会一直等。

oncontrollerchange属性

controllerchange事件的事件处理函数。当页面对应的ServiceWorker对象被新的ServiceWorker对象替换时会触发controllerchange事件。第一次注册的ServiceWorker不会触发该事件。

避免更改服务工作线程脚本的网址

如果每次都调整sw版本号发现会导致每次都作为新的sw,即执行update sw操作。

if ('serviceWorker' in navigator) {
  // Use the window load event to keep the page load performant
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('sw-fetch.js?v=' + Date.now()).then((registration) => {
			var sw = null, state;
			if(registration.installing) {
				sw = registration.installing;
				state = 'installing';
			} else if(registration.waiting) {
				sw = registration.waiting;
				state = 'installed'
			} else if(registration.active) {
				sw = registration.active;
				state = 'activated'
			}
			state && console.log(`sw state is ${state}`);
			if(sw) {
				sw.onstatechange = function() {
					console.log(`sw state is ${sw.state}`);
				}	
			}
		});
  });
}

如何避免缓存sw.js ?

当SW.js文件发生变更时修改避免缓存呢,修改url或者url追加版本参数都会导致注册新的ServiceWorker。

  1. 避免SW缓存sw.js ? 测试发现SW并不会捕获register注册的JS
  2. 浏览器缓存呢?
    URL适当的时候变化,md5重命名?

参考

  1. MDN
  2. 深入了解 Service Worker ,看这篇就够了
  3. PWA 在饿了么的实践经验

微信小程序——学习

MVVM

model:管理数据状态
view:页面结构
viewModel:描述状态和界面结构的关系的一种模板语法

rpx可靠吗?

所以运算结果会和预期结果有一点点偏差

框架

app.json,app.wxss分别是小程序的公共配置,公共样式。但app.js不是小程序的公共逻辑,它是小程序的逻辑,一个小程序只有一个【实例对象】,它使用创建小程序实例的逻辑。

配置

  1. window中background相关的配置是配置窗口的背景,不是页面的背景哦。
  2. navigationBar包含手机状态栏的位置,并且navigationBarTextStyle还会影响状态栏的文本颜色。
  3. 页面的配置记住这句话就行了:

页面的.json只能设置 window 相关的配置项,以决定本页面的窗口表现,所以无需写 window 这个键。

逻辑层

  1. 小程序逻辑层和视图层原理(通信)

作用域

  1. 每个JS是一个单独的模块作用域
  2. 全局作用域?
  • APP函数
  • Page函数
  • getApp函数

App函数

  1. onLaunch和onShow的参数是绝对相对的(===)
//app.js
App({
  onLaunch: function (param) {
    this.param = param;
    this.param.from = 'onLaunch'
  },
  onShow: function(param) {
    console.log('onShow')
    console.log(this.param === param); // true
    console.log(param.from); // onLaunch
  }
})

这样保证了小程序切到前台时保留最原始(onLauch)的入参。
2. 统一的异常和404处理回调

Page函数

当通过APP函数实例话小程序对象后,开始创建页面对象了。
页面对象包含生命周期函数,窗口的事件监听函数,以及和视图的通信?。

  1. 生命周期函数
    onLoad: 创建并初始化页面对象完毕后触发
    onShow: 页面显示的时候触发
    onReady: 页面首次渲染完毕后触发【】
    onHide:页面隐藏时触发
    onUnload: 页面销毁的时候触发

? onLoad,onShow中指向setData的影响?
一个页面对象只会创建一次,也只会有一个第一次渲染,所以onLoad, onUnload, onReady在页面对象整个生命周期中只会触发一次。
?APP的生命周期和Page的生命周期的关系

  1. 事件监听函数
  2. 属性
  • route
    页面路径,以/开头的完整路径

路由

  1. 几种页面跳转方式 & 对页面栈对影响

  2. 重定向:就是把当前page对象替换掉,所以当前page对象也会被销毁掉。只有路由有记录的page对象才会被缓存?

  3. tabBar页面和非tabBar页面路由方式的不同?
    页面跳转会导致tabBar消失?难道tabBar只在首页展示或者tabBar页面才会显示tabBar?

issues/concern

  1. 小程序的逻辑层就几大块:App,Page,路由

web安全

  1. XSS
  2. MIME混淆攻击
  3. CSRF攻击:
    https://segmentfault.com/a/1190000016659945#articleHeader0
  4. XXSI
  5. 幽灵和熔断漏洞(Spectre & Meltdown)
  1. Site Isolation
  2. 流量攻击,DDoS(Distributed denial-of-service, 分布式拒绝服务) 攻击
    利用大批量“流量”消耗被攻击服务器资源。
    分布式:攻击者利用傀儡机(分布式的代理)发起攻击,主要目的隐藏自己身份;
    拒绝服务:是指攻击的结果导致被攻击的服务器“拒绝服务”。
  • CC
    CC攻击是DDOS(分布式拒绝服务)的一种形式。主要针对应用本身的攻击(如访问网站耗时资源)。
    带宽资源严重被消耗,网站瘫痪;CPU、内存利用率飙升,主机瘫痪;瞬间快速打击,无法快速响应。

肉鸡

  1. [网站被劫持攻击]

前端安全系列(二):如何防止CSRF攻击?
egg Web 安全

Nodejs-stream

流(stream)

关于Nodejs的标签有异步的事件驱动的...
流是异步的

什么是流数据?

文档中关于流的介绍有这么一段话>A stream is an abstract interface for working with streaming data in Node.js
那什么是streaming data呢?动态、持续生成的数据。见参考文档。

关于Nodejs的stream模块

提供流数据操作的抽象api。

部件:流的源,流的目标,缓存(缓冲)

  1. 各部件怎么协作的?

数据块

write, end方法第一个参数是数据块

可写流API

cork

可以分批执行缓存。跟uncork成对操作。

uncork

可读流API

只有提供了消费或忽略数据的机制后,可读流才会产生数据。
有消费才有生成。

issues/concern:

  1. 流是可逝去有序的的 ?!

    • write,end方法调用的顺序决定了数据块写入的顺序
  2. 流是什么使用缓存的?

  3. 可写流的写入缓存满了后才会传给底层系统?
    貌似不是的,显示调用cork方法才会强制写入内存中!?

  4. 流和管道

深入理解 Node.js Stream 内部机制
What exactly are streams?
NodeJs中的stream(流)- 基础篇

事件

data事件

当流转换到 flowing 模式时会触发data事件,
在流处于处态时绑定data事件会切换流状态为flowing,进而触发data事件(这只是触发事件方式之一)。

  1. 多个data事件回调函数接收的参数是一样的。

end事件

流没有数据后触发

readable事件

事件表明流有了新的动态:要么是有了新的数据,要么是到了流的尾部

?是表示流是否数据还是表示缓存池是否有数据
? 处理 'readable'事件可能造成吞吐量升高

close & error事件

方法

destination readable.pipe(destination[, options])

返回目标流引用,可以构建管道链

数据流将被自动管理

var fs = require('fs')

var readable = fs.createReadStream('w.stream.text', {encoding: 'utf-8', highWaterMark: 7});

/* ----------- Event data & end----------------*/
// readable.on('data', (chunk) => {
// 	console.log(`1 接收到 ${chunk.length} 字节的数据。`);
// 	// readable.pause();
// 	// setTimeout(()=> {
// 	// 	readable.resume();
// 	// }, 2000)
// })

// readable.on('data', (chunk) => {
// 	console.log(`2 接收到 ${chunk.length} 字节的数据。`);
// })


/* ----------- Event readable & end ----------------*/
// readable.on('readable', () => {
//   	console.log(`${readable.read()}`);
// });

// readable.on('end', () => {
// 	console.log(`结束了`);
// })

/* -------------- 方法 pipe ------------- */
// var w1 = fs.createWriteStream('r.stream1.text')
// var w2 = fs.createWriteStream('r.stream2.text')

// readable.on('end', () => {
// 	console.log(`r-> 结束了`);
// })
// w1.on('end', () => {
// 	console.log(`w1-> 结束了`);
// })
// w2.on('end', () => {
// 	console.log(`w2-> 结束了`);
// })

// var pR1 = readable.pipe(w1)
// var pR2 = readable.pipe(w2)

// console.log(pR1 === w1)
// console.log(pR2 === w2)

/*----------------- 方法 read ------------------- 
1. 从缓存区里去取数据
2. 需要readable事件配合,要不然不知道啥时候缓存区有数据

*/

//console.log(readable.readableHighWaterMark)

// var count = 0;
// readable.on('readable', () => {
// 	let chunk;
// 	var insideCount = 0

// 	console.log(`Loop times: ${++count}`); // undefined ?
// 	while (null !== (chunk = readable.read(2))) {
// 		console.log(`\tRead loop times: ${++insideCount}`); 
// 		console.log(`\tReceived ${chunk.length} bytes of data.`);
// 	}
// })

// readable.on('end', () => {
// 	console.log(`r-> 结束了`);
// })


/* ------------------ 方法 resume -------------------- 
1. flowing模式的流即使没有显示指定消费者,也会被消费,返回触发end事件

*/

readable.resume().on('end', () => {
	console.log('Reached the end, but did not read anything.');
})

setTimeout(function() {
	readable.on('data', (chunk) => {
		console.log(chunk)
	})	
}, 4)


// 为什么消费流数据只是用一种方式?
// 两种mode下的readable事件和read方法

参考

  1. 什么是流数据?
  2. The Basics of Node.js Streams
  3. 官方文档

JS-ES6- class

一、介绍

JS是基于原型的面向对象模式,而不是基于类的面向对象。ES2015提供了一个语法糖,可以像写基于类的面向对象模式的代码,但本质还是基于原型的面向对象。

注意:
不能以类语言中的类来思考ES2015的类,而是以JS构造函数的方式来思考ES2015的类。

二、语法

2.1 定义

  1. 跟函数类似,类也有两种定义方式类声明方式 类表达式

  2. 构造方法constructor是可选的,并且一个类最多只能有一个构造方法。如果含有多个构造方法,则报语法错误;

  3. 命名的类表达式的名字只在类内内可见(跟函数表达式类似);

  4. class也是可以声明块作用域变量,特性跟let类似;
    可以把class视为只能声明类变量的特殊的let

  5. 必须使用new方式调用class(构造函数没法做的事,class做到了);

  6. class的所有方法(包括静态方法和实例方法)都没有prototype属性,也没有实现[[construct]]内部方法,不能使用new来调用。
    类成员方法已经有明确的this变量取值对象了(即本类的实例),防止滥用。

  7. 类实例对象的原型的属性都是不可枚举的
    JS所有内置对象的prototype属性都是不可枚举的,class声明的“构造函数”的prototype属性同样也是不可枚举的,所以类实例对象的原型的属性都是不可枚举的

  8. ES2015支持实例创建,构造方法,静态方法,继承,父类调用。

2.2 成员方法

  1. 方法以严格模式方式执行。

2.3 成员变量

在类语言中可以在类体中直接定义成员的方法,但是ES2015中是不可用的。成员变量只能在构造方法和非成员方法中声明。为了统一类实例的成员变量,一般在构造方法中声明所有的成员变量。

2.4 静态成员方法

用static关键字修饰的成员方法表示静态成员方法。静态成员方法只能通过类名引用。"prototype"不能作为静态方法的名字(该名字是用于原型继承用的)。

var a = new A();
a.show();
// a.count(); 报类型错误
A.count();

2.5 静态成员变量

ES2015中并没有定义如何定义静态变量,但是可以通过以下几种方式实现。

2.5.1 类即是构造函数

类只是构造函数的语法糖,所有类也是个构造函数(也是JS对象),可以直接在类上添加成员,这样成员就是静态。

class A{
	constructor(name){
		this.name = name;
		this.age = 20;
	}
	show(){
		console.log('show of instance of A');
	}
	static count(){
		console.log('count A: ' + this.age);
	}
}
A.Age = 1; // 定义静态成员变量
A.Say = function(){ // 定义静态成员方法
	console.log('My age is ' + A.Age);
}
A.Say(); // 调用静态方法

三、继承

3.1 语法基础

  1. 父类的构造方法,成员方法,静态成员方法,静态成员变量都会被子类继承,并且同名的会被子类覆盖。
class A{
	constructor(name, age){
		this.name = name;
		this.age = age;
	}
	show(){
		console.log('A instance: show: name=' + this.name + ', age=' + this.age);
	}
	static count(){
		console.log('A: count');		
	}
}
// 继承A
class B extends A {
	show() { // 覆盖A成员方法
		console.log('B instance: show: name=' + this.name + ', age=' + this.age);
	}
	say(){ // 新加的成员方法
		console.log('B instance say: ');
	}
	static count(){ // 覆盖A静态成员方法
		console.log('B: count: ');
	}
}

//B.count();
var b = new B('qyao_B', 24); // 继承A的构造方法
var a = new A('qyao_A', 23);
b.show();
b.say();
B.count();

a.show();
// a.say(); // 未定义
A.count();
  1. 子类的构造方法和成员方法可以通过super访问父类成员
  • constructor里直接调用super(arg...)
  • 成员方法(静态方法)里通过super只能访问父类的成员方法(静态方法),
    即成员方法不可以通过super访问父类静态方法,反之静态方法不可以通过super访问父类的成员方法;
  • super更像是个占位符,子类成员方法访问的任意父类成员方法(没有同名限制)。
    super的使用方式完全取决于子类在什么地方调用super
class A{
	constructor(name, age){
		this.name = name;
		this.age = age;
	}
	show(){
		console.log('A instance: show: name=' + this.name + ', age=' + this.age);
	}
	static count(){
		console.log('A: count: ');		
	}
}

class B extends A {
	constructor(name){
		super(name , 26); // 调用父类构造方法
	}
	show() {
		super.show(); // 调用父类继承show成员方法
		console.log('B instance: show: name=' + this.name + ', age=' + this.age);
	}
	say(){
                super.show(); // 也可以调用父类继承show成员方法
		console.log('B instance say: ');
	}
	static count(){
		super.count(); // 调用继承父类的count静态成员方法
		console.log('B: count: '); 
	}
}

//B.count();
var b = new B('qyao_B', 24);
b.show();
b.say();
B.count();
  1. 如果子类定义了构造方法,则必须在子类构造方法中调用父类的构造方法(即使父类没有显示的定义constructor方法),并且super语句必须在this引用之前。
    强制要求必须由父类初始化this变量
  • 这个应该是出于安全的考虑,否则在调用子类创建对象时就抛异常:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

从报错信息中还可以看到在访问this变量之前要先调用父类构造函数。

class A{
	show(){
	}
}
class B extends A {
	constructor(){
		super(); // 父类没有定义构造方法,也必须调用,否则在创建D实例时就抛异常
	}
}

class C extends A {
	constructor(){
		super(); 
               this.name = 'D'; // 一定先调用父类构造方法,再使用this变量。
	}
}
  • contructor方法没有这个限制
class A {	
	create() {
		console.log('class A.create')
	}
}

class B extends A {
	create() {
		console.log('class B.create')
		this.name = 12;
		super.create();
	}
}

var b = new B();
b.create()

3.2 原型关系

class P {}
class C extends P {}

var c = new C();

console.log(c instanceof C) // true
console.log(c instanceof P) // true

console.log(Object.getPrototypeOf(C) === P) // true
  1. 父类prototype也在子类实例对象的原型链上;
  2. 父类(构造函数)是子类(构造函数)的原型!?
    这样可以实现静态方法的继承

四、类 VS 构造函数

4.1 类既是构造函数

  1. class表达式值就是个构造函数,它本身只是构造函数的语法糖。
    使用class相当于JS自动帮开发创建了构造函数,正因为构造函数不是开发创建的,JS才能顺便做了一些规范限制(再也不用构造函数自己通过大写的名字告诉其他人:“hi, 看我的名字,请通过new方式调用”)
  2. 使用class定义基于原型的继承更方便些。
class Person{
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}
	say(){
		console.log('I am ' + this.name + ' and age is ' + this.age);
	}
}

// 等价于
function Person(name, age){
	this.name = name;
	this.age = age;
}
Person.prototype.say = function(){
	console.log('I am ' + this.name + ' and age is ' + this.age);
}

4.2 构造函数实现class继承

class继承逻辑还是比较复杂的:

  1. 继承;
  • 继承父类成员方法和属性;
  • 继承父类静态方法和属性;
  1. 父类方法引用;
  2. 其他一些特性:
  • 类构造函数必须new方式调用,否则抛异常;
  • 类成员方法和静态方法都无法作为构造函数;

大部分场景下可能不需要使用完整的功能,这里用构造函数简单模拟下。

  1. 只继承父类成员方法和属性
    EaselJS extend:
function extend(subclass, superclass) {
  "use strict";

  function o() { this.constructor = subclass; }
  o.prototype = superclass.prototype;
  // 定义一个空函数,切记不要直接`new superclass()`
  return (subclass.prototype = new o());
}

采用ES5的语法:

function extend(subclass, superclass) {
  "use strict";
  subclass.prototype = Object.create(superclass.prototype, {
        contructor: { value: subclass }
  })
  return subclass;
}
  1. 继承父类静态方法和属性
    也是利用原型解决问题,不过这里是调整子类构造函数本身的原型。
function extend(subclass, superclass) {
  "use strict";
  subclass.prototype = Object.create(superclass.prototype, {
        contructor: { value: subclass }
  })
  // 设置构造函数原型关系
  if(0 && Object.setPrototypeOf) {
    Object.setPrototypeOf(C, P);
  } else {
      C.__proto__ = P;
  }
  
  return subclass;
}
  1. 上面1,2实现中都是直接覆盖子类prototype属性的,正确的做法应是保留原子类prototype对象的属性
function extend(subclass, superclass) {
  "use strict";
  var subClassPrototype = subclass.prototype;
  subclass.prototype = Object.create(superclass.prototype, {
        contructor: { value: subclass }
  })

  // 补充子类原prototype对象的属性
  if(subClassPrototype === Object(subClassPrototype)) {
      var props = Object.getOwnPropertyDescriptors(subClassPrototype);
      Object.defineProperties(subclass.prototype, props);
  }

  // 设置构造函数原型关系
  if(0 && Object.setPrototypeOf) {
    Object.setPrototypeOf(C, P);
  } else {
      C.__proto__ = P;
  }  
  
  return subclass;
}
  1. new时调用父类构造函数
    new子类时,内部其实也调用了父类构造函数。
    这个就无法通过动态方式模拟了,因为调用子类调用父类构造函数的方式不固定的(比如传参个数,顺序等)。只能通过静态语法分析了(看看Babel)

babeljs怎么做的
完整的功能参考下babeljs

参考

  1. MDN Class
  2. understandinges6 - class
  3. ECMA
  4. Babel

python & itchat

Python Slogan

  1. simple syntax
  2. similarities to the English language
    以讲话的方式写代码?
  3. readability
  4. fewer code, more power
  5. 解释性,动态语言

python PK js

语法

  1. 换行作为语句结束
  2. 尾部冒号: + 缩进定义代码块。

作用域

  1. 模块作用域
    定义在模块里的全局变量
  2. 函数作用域
    定义在函数里的变量,函数
  3. 没有块作用域

操作符

  1. is
    判断两个变量是否指向同一个内存变量值
  2. 比较运算符
    注意逻辑运算符比较的是两个变量所指向变量值,而不是变量本身,
    判断两个变量指向的值是否相等
a = [1, 2]
b = [1, 2]
print(a == b)  #True

这个跟JS里不是一样的,JS中比较的是变量本身的值。估计是因为python只有引用类型的变量,所以比较运算符才这样搞。

引用类型/值类型

a = 1
b = a
print(a is b)
print(b is a)

说明Python只有引用类型,没有值类型。

控制语句

if elif else

  1. if elif else 简写格式(跟贴近语音):
print("A") if a > b else print("=") if a == b else print("B")

Lambda表达式

  1. 匿名函数,
    任意个参数(个数可以为0),函数体必须是个表达式
  2. 返回值?
  3. PK def
    • Lambda表达式是个表达式,该表达式的值是个函数。
    • def是函数定义语句
      因为return语言后面跟的是个表达式所以def函数不能直接写在return后面,而Lambda表达式却可以。

对象

一切都是对象?

函数

http://python.jobbole.com/81646/

函数定义的时候会把函数涉及的闭包变量,局部变量进行收集:

  1. 左值视为局部变量

Python的变量定义发生在赋值操作的时候。

闭包

代码有问题的原因?

def counter():
    count = 0
    def c():
        count += 1
        return count
    return c

c = counter()
c()

  1. 初始化方式
  2. 成员方法的self参数
    每个成员函数第一个参数是类实体对象。
  3. 创建对象不用new,

模块

模块的概念

A file containing a set of functions you want to include in your application.

  1. 即一个文件视为一个模块
  2. 文件名字作为模块的名字
  3. 通过import导入依赖的模块
  4. 通过模块名字引用定义在依赖模块中定义的函数。

分类

  1. 内置模块
  2. 自定义模块

模块的输出

定义在模块里全局变量和方法

模块的导入

  1. 全部导入
import moduleName as alisName
  1. 选择性导入
form moduleName import 模块输出列表

模块和作用域???
导入包查找方式???

包(package)& PIP

  1. 包就是一个目录,包含一个模块和该模块的所有依赖的模块。

异步编程

最新Python异步编程详解

issues

  1. 作用域,内存管理

参考

  1. w3schools Learn Python
  2. 廖雪峰 Python教程

Passive Event

从浏览器报错说起...

image
Treat Document Level Touch Event Listeners as Passive

AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..

If the value is explicitly provided in the AddEventListenerOptions it will continue having the value specified by the page.

This is behind a flag starting in Chrome 54, and enabled by default in Chrome 56. See https://developers.google.com/web/updates/2017/01/scrolling-intervention

一、什么是被动事件(Passive Event)

1.1 语法层面

addEventListener第三个参数option有个配置:

passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。

即passive为true时就是绑定个passive事件处理函数,并且该事件处理函数中不能调用preventDefault

1.2 有什么用 ?

Improving Scroll Performance with Passive Event Listeners

1.3 引入背景: scroll jank (block scrolling)

浏览器对一些事件都存在默认的行为,比如点击a标签,浏览器会打开a标签指定的链接,滚动鼠标会滚动页面。通过调用事件对象的preventDefault方法可以阻止浏览的默认行为。
但是对于页面滚动的事件会有些问题:

当你触摸滑动页面时,页面应该跟随手指一起滚动。假如此时也绑定了一个 touchstart 事件,这时浏览器就犯迷糊了:如果你在事件绑定函数中调用了 preventDefault,那么页面就不应该滚动,如果你没有调用 preventDefault,页面就需要滚动。但是你到底调用了还是没有调用,浏览器不知道。只能先执行你的事件处理函数执行完后,浏览器才知道,“哦,原来你没有阻止默认行为,好的,我马上滚”。此时,页面开始滚。

1.4 解决方案:向浏览器承诺处理函数内部不会调用preventDefault

在绑定事件的时候通过第三个参数配置向浏览器承诺回调函数不会调用preventDefault,这样浏览器就不等待回调函数执行后才发起重绘请求(注意:浏览器渲染还是等回调执行完之后,单线程的)。
但是如果你撒谎(还在调用了preventDefault)则浏览器会给报提示信息的:
image

1.5 touchstart, touchmove事件的passive配置默认是true

Chrome Treat Document Level Touch Event Listeners as Passive

With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..

虽然这样写,但是测试下来不只是document,其他元素的touchstart, touchmove事件(不包含touchend)都是默认为true。
Making touch scrolling fast by default作出了解释:

In the future we hope to make passive true the default for all touchstart and touchmove listeners,

Why?

Making touch scrolling fast by default作出了解释:

We looked at the percentage of cancelable touch events that were sent to a root target (window, document, or body) and determined that about 80% of these listeners are conceptually passive but were not registered as such. Given the scale of this problem we noticed a great opportunity to improve scrolling without any developer action by making these events automatically "passive".

鼠标滚动事件的passive配置默认也是true

WICG/EventListenerOptions 标准化了:

Passive event listeners are a new feature in the DOM spec that enable developers to opt-in to better scroll performance by eliminating the need for scrolling to block on touch and wheel event listeners

1.6 passive事件回调不能解决的问题

页面渲染和JS执行是同一个线程,页面滚动会造成重新页面的渲染。如果事件回调函数比较耗时同样会造成页面滚动卡顿,并且这是passive事件不能解决的。常见的解决方案:

  1. 节流:检查滚动事件回调函数的次数

1.7 其他

  1. 滚动事件(touchmove, mousewheel)的回调函数尽量节流操作。

参考

  1. 网站使用被动事件侦听器以提升滚动性能
  2. [移动端新特性] Passive Event Listeners
  3. 移动Web滚动性能优化: Passive event listeners
  4. Demo & 好的参考
  5. WICG/EventListenerOptions

UEditor

Study

UE全局变量是UEditor的最顶层命名空间

// 全局变量1: 默认配置对象,可以在其他文件里定义
UEDITOR_CONFIG = window.UEDITOR_CONFIG || {};

// 全局变量2:定义全局变量baidu
var baidu = window.baidu || {};
window.baidu = baidu;
// 全局变量3:UE
window.UE = baidu.editor = {
  plugins: {},
  commands: {},
  instants: {},
  I18N: {},
  _customizeUI: {},
  version: "1.5.0"
};
var dom = (UE.dom = {});
  1. UEditor实例对象表示具体一个富文本编辑器,每个UIEditor实例对象有自己的配置,UI,命名。
  2. UEditor上面菜单:看到的叫UI,执行的叫命令

添加新的UI

UI的属性

  1. name: 名字
  2. title: 提示,鼠标悬浮时提示
  3. cssRules: 样式字符串,可以用于控制显示的icon
  4. label: 显示的文案(可选的)在icon后面展示
  5. onClick
    用户点击触发回调函数
  6. disabled: 是否禁用
  7. checked: 是否被选择

调用方法UE.registerUI添加新的UI组件。

命令

UEditor所有对富文本编辑效果的操作都是通过命名完成的,已经内置了很多命令,源码的plugins目录都是命名定义文件。

命令的属性

命令是个对象,有个名称和对应的动能函数。

  1. name 命令名称,通过名字调用对应的命名
  2. execCommand 该命令真正要执行的函数。
  3. 状态,编辑器处于不同的状态可以执行的命名可以不一样,即命令是否可用。
1 => 代表当前命令在当前选区内已执行
0 => 代表当前命令在当前选区内未执行, 但处于可用状态
-1 => 代表当前命令在当前选区内处于不可用状态

queryCommandState方法用来查询命令的状态。一般toolBars上的UI组件都会监听selectionchange事件,然后查询其对应的命名的状态控制自己的状态(disabled, checked)。

执行命名

注册新命令

  1. editor.registerCommand
  2. UE.commands
    直接向UE.commands对象里添加新命名对象

参考

官网

JS-ES6-模板字符串

模板字符串本质就是字符串

任何可以使用字符串的地方都可以使用模块字符串代替。

Object.prototype.toString.call(`a`); // "[object String]"
console.log(`hello`[1]); // "e"

模板字符串相对于普通额外能做什么?

1. 多行

console.log(`
hello, world!
    I'm ok.
`)

image
多行时``之间的任何字符串都是字符串内容,包含不可见字符(换行,回车,tab等)。

2. 表达式嵌入

var msg = 'world';
console.log(`hello ${msg}`)

大括号内部可以放入任意的JavaScript表达式

3. 带标签的模板字符串(简称:标签模板)!!!

模板字符串也可以视为是JS表达式,该表达式返回的是字符串。并且可以通过自定义个函数(标签函数)来修改模板字符串默认的构建字符串的行为。

alert`123`
// 等同于
alert(123)
  1. 标签模板本质上是一种函数调用语法糖。
  2. 修改模板字符串返回值
function tag(s, v1, v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  console.log(v1);
  console.log(v2);

  return "OK";
}

var a = 5;
var b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
  1. 标签函数的第一个实参是个数组
// 参数1点取值等同于
var s = "Hello ${ a + b } world ${ a * b }".split(/\${.*?}/);
  1. 其他参数依次是模板字符串中变量嵌入的具体值了
  2. 应用场景:
  • html转义
  • i18n
  • 任意发挥

参考

  1. ES6:模板字符串
  2. MDN 模板字符串

E6:let_const

一、介绍

1.1 背景

ES5没块作用域的,见B_JS权威指南,这个问题在ES6中得以解决。ES6中let关键字声明的变量具有块作用域。

1.2 js块

js中一对花括号就构成了一个块。常见的块有:

  1. for,for-in,for-of
  2. while, do-while
  3. try-catch-finally
  4. if-else,switch
  5. 函数
  6. 一对花括号{}

二、let变量特性

2.1 let声明的变量具有块作用域

'use strict';

function show(){
	
	{
		var a = 1;
		let b = 1;
	}
	console.log(a);
	console.log(b); // 在块外部访问变量b,导致抛ReferenceError.
}

show();

2.2 for循环中每次循环都为let变量创建一个新的块作用域对象

先看下经典的循环闭包例子(显示结果也不出意外):

'use strict';

function show(){
	var arr = [];
	for(var i =0;i !== 4; ++i) { // var变量
		arr.push(function(){
			console.log('callback_' + i);
		})
	}
	return arr;
}

show().forEach(function(callback, index){
	callback();
});

image

如果把i变量该let变量问题就简单的解决了:

function show_Let(){
	var arr = [];
	for(let i =0;i !== 4; ++i) { // let变量,每次循环都创建个新的块作用域对象
		arr.push(function(){
			console.log('callback_' + i);
		})
	}
	return arr;
}

show_Let().forEach(function(callback, index){
	callback();
});

image
因为i是块作用域,并且每次循环都会创建新的块作用域对象,所以每次闭包的i变量都处于不同的块作用域对象中。上面的问题也可以这样解决:

'use strict';

function show(){
	var arr = [];
	for(var i =0;i !== 4; ++i) {
		let j = i; // 创建个let变量j
		arr.push(function(){
			console.log('callback_' + j); // 访问变量j
		})
	}
	return arr;
}

2.3 let变量必须先声明后使用

从侧面也说明了let变量不会发生声明提升(var变量的特性)。

let a;
console.log(a); // undefined
console.log(b); //  ReferenceError 
let b;

2.4 相同作用域中let变量不可用重新声明

let a; // SyntaxError
let a;
let b; // SyntaxError
var b;

2.5 全局的let变量不是全局对象的属性

浏览器中不在函数中声明的var变量是全局对象window的属性,但是对于全局的let变量是作为全局对象window的属性的。

三、const变量

3.1 介绍

ES6中除了引入let变量,还引入了const变量。const变量是一种特殊的let变量。除了3.2列出的特殊性外,其他的同let的用法。

3.2 const的特殊之处

3.2.1 定义必须初始话,初始化的右值可以是任意表达式,不一定非得是常量

const a = undefined; // 必须初始化,即使用undefined初始化
const b; // SyntaxError

const b = (function() { return 'hello'; })(); // 函数调用表达式

3.2.2 const变量值不可用修改

const变量是指不可用修改const变量本身的值:对于值类型的const变量表示const变量的值可以修改,对于引用类型的const变量表示不能修改变量的引用对象,但是可以修改被引用对象的属性。

const a = 12; //定义必须初始化
a = 1; // TypeError

const b = {
  name: 'john'
}
b.name = 'hi'; //OK 
b = {}; // TypeError

3.3 const in Nodejs

在参考https://nodejs.org/en/docs/es6/ 中,默认情况下Node.js是支持const块作用域的。但是测试发现非严格模式下const变量的行为并不具备块作用域,而是像不可变的var变量。

console.log(a)
{
	const a = 12;
}

console.log(a)

image

参考:

  1. 极客学院
  2. MDN
  3. ES6 in Nodejs

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.