# 前端爱好者学习周刊:第4期

2019-11-30

html
css
js

# 【JavaScript】:

# 1. WebSocket 原理浅析与实现简单聊天

WebSocket 原理浅析与实现简单聊天 (opens new window)

http1.x 只能客户端发起,不能服务器主动推送。

  • 轮询实现推送

# 2. (?.运算符)@babel/plugin-proposal-optional-chaining

JS操作小技巧,工作简单了 (opens new window)

?.操作符之前说是目前在试验阶段,看到这篇文章,通过加这个babel插件可用,立马想尝鲜;在原有项目上安装时,报错,说要求babel-core要是7.0.0版本的,(此时为6.26.3),按百度装了 babel-core@^7.0.0-bridge.0 立马就报出新的问题,babel以及几个插件的版本有6点几的 还有7点几的,版本不兼容。问题又出来了;。。。。鉴于时间问题,改天接着研究————

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

const baz = obj?.foo?.bar?.baz; // 42

const safe = obj?.qux?.baz; // undefined

1
2
3
4
5
6
7
8
9
10
11
12

{
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}
1
2
3
4


?.运算符

超哥的框架配置中,居然已经配置了这个@babel/plugin-proposal-optional-chaining,babel所有的都升级到7.x了,竟然之前还没发现,😄,可以直接使用

babel/plugin-proposal-optional-chaining使用?. (opens new window)

const customer={
    address:{
        city:'beijing',
        cityArr:['beijing','shanghai','nanjing'],
        street:'12'
    }
}
//对象
const cityValOld= customer && customer.address && customer.address.city
const cityValNew= customer?.address?.city 
const zipcode= customer?.address?.zipcode

// 数组
const cityArrOld=data && customer && customer.address 
&& customer.address.cityArr&& customer.address.cityArr[0]  

const cityArr0New=customer?.address?.cityArr?.[0]   
const cityArr5New=customer?.address?.cityArr?.[5]

const arrs = [1,2,3]
const newArrs = arrs?.map(item=>{
    return item
})

const {getValue}=this.props
getValue && getValue() 
getValue?.() 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

不过在使用的时候,vscode不认识这个?. 所以一直报红,看的人好难受。

image.png

那么可以采用下面的方法——————我采取在当前工作区去掉了javascript的校验。不怕,因为咱们有超哥配置的eslint检验。

image.png

得,提问还是有好处的,第二天就收到了一个更好的方法的回答:使用?. 好像是vscode一直报红,应该配置什么? (opens new window)

给vscode安装JavaScript and TypeScript Nightly插件即可

# 3. js小技巧

JS操作小技巧,工作简单了 (opens new window)

见[js小技巧]

# 4.https

漫画:什么是 HTTPS 协议? (opens new window)这篇文章讲的很好,后面还是需要好好消化一下;

评论区一大神点评:

  • 1.0版本:信息在网络中裸奔,劫持人想怎么搞就怎么搞。
  • 2.0版本:劫持人有点难受了,必须在刚开始建立会话时就监听,获取到对称钥匙过后才能开始瞎搞。
  • 3.0版本:只是在会话建立时单纯的监听行不通了,还需要偷梁换柱。把自己的公钥发给小灰,小灰拿到这个密钥就哼哧哼哧的加密对称密钥了,然后发出去又被劫持,劫持人用自己的私钥解开,拿到对称密钥,再用小红的公钥加密对称密钥发给小红,嘿嘿这样子就拿到他俩通讯用的对称密钥了,还没有被发现
  • 4.0版本:凉凉,中间人没办法偷梁换柱了,小灰从第三方去拿公钥,想个办法把第三方劫持了?

# 5.js事件循环机制

js事件循环机制 (opens new window)

JS是单线程(如果一个线程删DOM,一个线程增DOM,浏览器傻逼了~所以只能单着了),虽然有webworker酱紫的多线程出现,但也是在主线程的控制下。webworker仅仅能进行计算任务,不能操作DOM,所以本质上还是单线程。

单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,串行执行任务;异步执行就是cpu跳过等待,先处理后续的任务(CPU与网络模块、timer等并行进行任务)。由此产生了任务队列与事件循环,来协调主线程与异步模块之间的工作。    image.png

任务队列: 任务队列存在两种类型,一种为microtask queue,另一种为macrotask queue。

图中所列出的任务队列均为macrotask queue,而ES6 的 promise[ECMAScript标准]产生的任务队列为microtask queue。

  • microtask queue:唯一,整个事件循环当中,仅存在一个;执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;

  • macrotask queue:不唯一,存在一定的优先级(用户I/O部分优先级更高);异步执行,同一事件循环中,只执行一个。

console.log('1, time = ' + new Date().toString())
setTimeout(macroCallback, 0);
new Promise(function(resolve, reject) {
    console.log('2, time = ' + new Date().toString())
    resolve();
    console.log('3, time = ' + new Date().toString())
}).then(microCallback);

function macroCallback() {
    console.log('4, time = ' + new Date().toString())
} 

function microCallback() {
    console.log('5, time = ' + new Date().toString())
}     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

同步环境:1 -> 2 -> 3

事件循环1(microCallback):5

事件循环2(macroCallback):4

# 6. 闭包

# 定义

  • 1.能访问外部作用域的变量的函数
  • 2.能访问外部作用域的变量,即使外部作用域已经返回(return)[return之后本应该被垃圾回收机制回收了的,可是仍然被其内的函数引用变量,所以变量没有被回收]
  • 3.闭包可以更新外部变量的值

# 闭包中的变量

深入javascript——作用域和闭包 (opens new window)

在使用闭包时,由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值

# 闭包的作用

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」


# 「每日一题」JS 中的闭包是什么? (opens new window)

这篇文章的讲解通俗易懂,挺好,推荐。

作者如是说:

「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包

闭包通常函数套函数,为什么呢?----是因为需要局部变量,所以才把 local 放在一个函数里,如果不把 local 放在一个函数里,local 就是一个全局变量了,达不到使用闭包的目的——隐藏变量;为什么要 return bar 呢?因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。所以 return bar 只是为了 bar 能被使用,也跟闭包无关

闭包会造成内存泄露?错。说这话的人根本不知道什么是内存泄露。内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。闭包里面的变量明明就是我们需要的变量(lives),凭什么说是内存泄露?这个谣言是如何来的?因为 IE。IE 有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量


# 从作用域链谈闭包 (opens new window)

这篇文章从作用域的方向梳理了闭包。每段代码的执行存在一个作用域链,每次执行的时候,都会顺着作用域链去查找变量,一直向上。每个执行环境都有一个与之关联的变量对象,里面存着当前作用域的变量和函数,这个对象就像执行环境的一个存储管理器,用的时候只管去这里拿。

作者如是说:

Javascript中的作用域链:Javascript中有一个执行环境(execution context)的概念,它定义了变量或函数有权访问的其它数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。你可以把它当做Javascript的一个普通对象,但是你只能修改它的属性,却不能引用它。

变量对象也是有父作用域的。当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不再存在父作用域了,这就是作用域链。

作用域链和原型继承有点类似,但又有点小区别:如果去查找一个普通对象的属性时,在当前对象和其原型中都找不到时,会返回undefined;但查找的属性在作用域链中不存在的话就会抛出ReferenceError。

作用域链的顶端是全局对象。对于全局环境中的代码,作用域链只包含一个元素:全局对象。所以,在全局环境中定义变量的时候,它们就会被定义到全局对象中。当函数被调用的时候,作用域链就会包含多个作用域对象。

在这篇文章中,作者通过画执行作用域图,展示了闭包;


# JavaScript 中 闭包 的详解 (opens new window)

变量生存周期---闭包,导致了 a 的生命周期延续;

var func = function(){
    var a = 'linxin';
    var func1 = function(){
        a += ' a';
        console.log(a);
    }
    return func1;
}
var func2 = func();
func2();                    // linxin a
func2();                    // linxin a a
func2();                    // linxin a a a
1
2
3
4
5
6
7
8
9
10
11
12

# 闭包题目经常见到的代码段

  • 最简单的一种说法,函数中可以访问外部作用域的变量
var str = 'local'
function getStr() {
	console.log(str)
}
1
2
3
4
  • 函数嵌套
function foo(){
  var local = 1
  function bar(){
    local++
    return local
  }
  return bar
}

var func = foo()
func() // 2
func() // 3
func() // 4
1
2
3
4
5
6
7
8
9
10
11
12
13

调用foo函数,执行,本来return 函数内部的变量就要被垃圾回收机制回收了,但是local仍然被bar引用,无法回收。所以local变量一直为活跃的。

所以也就会出现,调用多次func(),每次值都会增加1;

  • 依然函数嵌套

"use strict";
function createCounter(initial) {
  var counter = initial;
  function increment(value) {
    counter += value;
  }
  function get() {
    return counter;
  }
  return {
    increment: increment,
    get: get
  };
}
var myCounter = createCounter(100);
console.log(myCounter.get());   // 返回 100
myCounter.increment(5);
console.log(myCounter.get());   // 返回 105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 带有循环的内部函数
function createClosure(){
    var result = [];
    for (var i = 0; i < 5; i++) {
        result[i] = function(){
            return i;
        }
    }
    return result;
}

createClosure()[0]()  // 5
createClosure()[1]()  // 5
createClosure()[2]()  // 5
createClosure()[3]()  // 5
createClosure()[4]()  // 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

因为每个函数的作用域链中都保存着对外部函数(createClosure)的活跃对象,因此,他们都引用着同一变量i,当外部函数返回时,此时的i值为5,所以内部的每个函数i的值也为5。

想要输出0,1,2,3,4:----换成立即执行函数

function createClosure() {
    var result = [];
    for (var i = 0; i < 5; i++) {
        result[i] = function(num) {
            return function() {
                console.log(num);
            }
        }(i);
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
  • 带有循环的异步函数
for (var i = 0; i < 4; i++) {
    setTimeout(function () {
        console.log(i)
    }, 0)
}
// 结果4个4
1
2
3
4
5
6

setTimeout 函数时异步的,等到函数执行时,for循环已经结束了,此时的 i 的值为 4,所以 function() { console.log(i) } 去找变量 i,只能拿到 4

解决这个的方法:

for (var i = 0; i < 4; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i)
        }, 0)
    })(i)
}
// 0 1 2 3 4
1
2
3
4
5
6
7
8
  • 循环引用----关于闭包的内存泄漏的一种说法:
function bindEvent(){
    var target = document.getElementById("elem");
    target.onclick = function(){
        console.log(target.name);
    }
}
1
2
3
4
5
6

匿名函数对外部对象target产生一个引用,只要是匿名函数存在,这个引用就不会消失,外部函数的target对象也不会被销毁,这就产生了一个循环引用

JavaScript深入之4类常见内存泄漏及如何避免 (opens new window)这个作者提出了,现在使用的更先进的标记清除垃圾回收机制,不再这样内存泄漏

解决办法:

function bindEvent(){
    var target = document.getElementById("elem");
    var name = target.name;
    target.onclick = function(){
        console.log(name);
    }
    target = null;
 }
1
2
3
4
5
6
7
8
  • 简单带一笔闭包中的this

后面好好研究一下this

var scope = "global";
var object = {
    scope:"local",
    getScope:function(){
        return function(){
            return this.scope;
        }
    }
}
1
2
3
4
5
6
7
8
9

调用object.getScope()()返回值为global;

var scope = "global";
var object = {
    scope:"local",
    getScope:function(){
        var that = this;
        return function(){
            return that.scope;
        }
    }
}
1
2
3
4
5
6
7
8
9
10

调用结果为global


闭包,执行机制等:

Excuse me?这个前端面试在搞事! (opens new window)

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000 * i);
}
1
2
3
4
5

setTimeout 会延迟执行,那么执行到 console.log 的时候,其实 i 已经变成 5 了;每隔一秒再输出一个 5,一共 5 个 5

加个闭包,实现输出0到4:

for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
1
2
3
4
5
6
7
for (var i = 0; i < 5; i++) {
  (function() {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
1
2
3
4
5
6
7

这样子的话,内部其实没有对 i 保持引用,其实会变成输出 5

for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), i * 1000);
}
1
2
3
4
5

这里给 setTimeout 传递了一个立即执行函数。额,setTimeout 可以接受函数或者字符串作为参数,那么这里立即执行函数是个啥呢,应该是个 undefined ,也就是说等价于:

setTimeout(undefined, ...); 而立即执行函数会立即执行,那么应该是立马输出的。立马输出 0 到 4

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);
1
2
3
4
5
6
7
8
9
10
11
12
13

这道题考察 JavaScript 的运行机制的---2 3 5 4 1

# 7.async

深入浅出ES6教程『async函数』 (opens new window)

async函数的特点

  • 语义化强
  • 里面的await只能在async函数中使用
  • await后面的语句可以是promise对象、数字、字符串等
  • async函数返回的是一个Promsie对象
  • await语句后的Promise对象变成reject状态时,整个async函数会中断,后面的程序不会继续执行
  • await 语句后的Promise没有只要没有resolve,后面的就不会再继续执行了

# 8.babel-plugin-transform-class-properties

这个插件加上,并在.babelrc中添加此插件后,方可直接使用箭头函数写react中的方法