GithubHelp home page GithubHelp logo

myblob's Issues

终于理解js的执行顺序:事件循环

主要记录异步代码顺序

WHATWG 规范:每一次事件循环,只处理一个 macrotask。待该 macrotask 完成后,所有的 microtask 会在同一次循环中处理。处理这些 microtask 时,还可以将更多的 microtask 入队,它们会一一执行,直到整个 microtask 队列处理完。

macrotask 与 microtask

任务队列又分为macrotask(宏任务)与microtask(微任务),二者均为队列

  • macrotask大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  • microtask大概包括:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

实例分析

下面用一段代码进行分析,猜猜输出顺序是什么?

console.log('script start') 

setTimeout(() => {  
  console.log('setTimeout 1')
  Promise.resolve().then(() => {
    console.log('promise 3')
  }).then(() => {
    console.log('promise 4')
  }).then(() => {
    setTimeout(() => {
      new Promise(resolve=>{
        console.log('promise 5');
        resolve();
      }).then(() => {
        console.log('promise 6')
      }).then(() => {
        setTimeout(()=>{console.log('setTimeout 3')},500);
      }).then(() => {
        console.log('promise 7')
      })
      console.log('setTimeout 2')
    }, 0)
  })
}, 0)

Promise.resolve().then(() => {  
  console.log('promise 1')
}).then(() => {
  console.log('promise 2')
})

代码多,是为了理解的更深刻,其实道理都是一样的,先放正确答案(在Chrome中得出)

script start
promise 1
promise 2
setTimeout 1
promise 3
promise 4
promise 5
setTimeout 2
promise 6
promise 7
setTimeout 3

事件循环每一轮称为一个tick,逐轮分析

tick 1:

先从macrotask队列的第一个元素开始执行,即运行整个script内的代码

console.log('script start') 是同步的,直接在主线程(执行线程)完成

继续执行,执行setTimeout(setTimeout1),将定时任务交给宿主环境(浏览器等)的工作线程执行,宿主环境的工作线程执行完毕后将回调函数加入到macrotask队列中

接下来执行Promise(Promise1),先同步执行resolve方法,决议后,将then内对应函数加入microtask队列

至此,同步代码已完成

开始检查microtask队列,存在一个元素(promise 1),开始执行,即promise 1,然后将promise 2加入到microtask队列中

再次检查队列,由于microtask队列存在元素,继续执行promise 2之后microtask为空 则调用下一个macrotask队列的元素 开始第二轮循环

tick 2:
... 同理 懒得打了

async/await 小试

在交流群有人贴出一段代码问怎么不对,正好昨天刚了解了async/await 所以尝试查看,参考着资料发现原因是他对await不够理解,当然我也不懂。

于是乎,借着anydoor的项目尝试了下前端使用async/await向后端发送请求,并获取数据

后端只需注意设置允许跨域请求就OK了,前端代码如下:
(目的:请求anydoor项目根目录下的app.js文件的内容)
`
function post(){
return new Promise((resolve)=>{
let xhr=new XMLHttpRequest();
xhr.open('GET','http://127.0.0.1:8898/app.js');
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
console.log(xhr.responseText);
resolve();
}
}
xhr.send();
})
}

async function get(){
try{
const res=await post();
console.log(res);
}catch(err){
console.log(err);
}
}
get();
`

await后面需要跟一个promise对象,所以对请求进行封装

对于await后代码以及catch块内代码的理解:

`
promise.then((resolve,reject)=>{
// do some
resolve(data); or reject(err);
}).then((data)=>{
console.log(data);
}).catch((err)=>{
console.log(err);
})

// 等价于
async function demo(){
try{
var data=await new Promise(// do some resolve(data); or reject(err););
console.log(data);
}catch(err){
console.log(err);
}
}
`

多次await等价于promise的链式调动

`
promise.then((resolve,reject)=>{
// do some
resolve(data);
}).then((data)=>{
// do some
return new Promise(// do some);
}).then((data)=>{
// do some
return new Promise(// do some);
}).then((data)=>{
// do some
return new Promise(// do some);
}).catch((err)=>{
console.log(err);
})

// 等价于
function test(){return new Promise(resolve=>{// do some})}

async function demo(){
try{
let data1=await test();
let data2=await test();
let data3=await test();
let data4=await test();
let data5=await test();
}catch(err){
console.log(err);
}
}
`

事件冒泡与事件捕获

事件冒泡与事件捕获是两种处理事件流的模型,分别由微软和网景公司提出

事件流

事件流是指HTML中从 document 到触发事件的元素 这一段代码(这一段DOM树结构)中事件触发的顺序

两种模型的概述

1. 事件捕获:
如图

假设 div 是触发事件的元素 事件流从 document 开始到 div逐级检查,凡是绑定事件的且设置为捕获模型的元素依序触发事件

举例之前,先说明一个问题,我们可通过 element.addEventListener('事件',回调函数,true or false) 来注册事件的响应函数,其中第三个参数表示采用的事件模型,如果为false 则为冒泡模型,为true 为捕获模型,不填为undefined 即冒泡模型

OK,现在开始举例说明(CSS代码不贴了)

html:

<div id="d1">
    爷爷
    <div id="d2">
        爸爸
        <div id="d3">儿子</div>
    </div>
</div>

js:

   var d1=document.getElementById("d1");
   var d2=document.getElementById("d2");
   var d3=document.getElementById("d3");
   d1.addEventListener("click",()=>{
       console.log("d1");
   },true);
   d2.addEventListener("click",()=>{
       console.log("d2");
   },true);
   d3.addEventListener("click",()=>{
       console.log("d3");
   },true);

分别点击
儿子(d3):d1 d2 d3
父亲(d2):d1 d2
爷爷(d1):d1

2. 事件冒泡:
如图

假设 div 是触发事件的元素 事件流从 div 开始到 document逐级检查,凡是绑定事件的且设置为冒泡模型的元素依序触发事件

举例来说:
html:

<div id="d1">
    爷爷
    <div id="d2">
        爸爸
        <div id="d3">儿子</div>
    </div>
</div>

js:

   var d1=document.getElementById("d1");
   var d2=document.getElementById("d2");
   var d3=document.getElementById("d3");
   d1.addEventListener("click",()=>{
       console.log("d1");
   },false);
   d2.addEventListener("click",()=>{
       console.log("d2");
   },false);
   d3.addEventListener("click",()=>{
       console.log("d3");
   },false);

分别点击
儿子(d3):d3 d2 d1
父亲(d2):d2 d1
爷爷(d1):d1

W3C标准

两家公司各自吹嘘自己的好用,争执不下,W3C采用中和的方法将二者结合。
如图所示:

即先进行事件捕获再进行事件冒泡

图片来自网络

举例说明:
只贴不同部分了
js:

   d1.addEventListener("click",()=>{
       console.log("d1");
   },false);
   d2.addEventListener("click",()=>{
       console.log("d2");
   },true);
   d3.addEventListener("click",()=>{
       console.log("d3");
   },false);

分别点击
儿子(d3):d2 d3 d1
父亲(d2):d2 d1
爷爷(d1):d1

当点击儿子(d3)时,先进行事件捕获 所以d2触发click事件 然后进行事件冒泡 所以d3 d1分别触发click事件

当然我们可以给任何一个元素设置两种事件模式 比如d2 可以设置一个事件冒泡模式的 同时也可设置一个事件捕获模式的

但值得注意的是,如果在拐点 (触发事件的那个元素,比如点击儿子(d3)时的儿子(d3)) 处设置两个模式,应以二则的注册先后顺序来决定触发顺序

举例来说:
js:

   d1.addEventListener("click",()=>{
       console.log("d1");
   },false);
   d2.addEventListener("click",()=>{
       console.log("d2");
   },true);
   d3.addEventListener("click",()=>{
       console.log("d3 冒泡");
   },false);
   d3.addEventListener("click",()=>{
    console.log("d3 捕获")
   },true);

当点击儿子(d3)时: d2 d3 冒泡 d3 捕获 d1

对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。

我知道的异步——初识

“现在”指的是主线程中代码开始执行时
"将来"指的是一些异步任务完成时

我们所研究的异步机制就是用来处理"现在"到"将来"这一过程

任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(如定时器、鼠标点击、Ajax响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序中引入了异步机制

事件循环:举例来说,如果javascript程序发出一个Ajax请求,要从服务器获取一些数据,我们需要在回调函数中设置好响应代码,然后javascript引擎会通知宿主环境:“嘿,现在我要暂停执行,你一旦完成网络请求,拿到了数据,就请调用这个函数”,然后浏览器就会设置某个线程监听来自网络的响应,拿到要给你的数据后,就会把回调函数插入到事件循环,等待javascript引擎的调用执行。

事件循环是一个先进先出的队列,循环每一轮称为一个 tick 。如果队列中有等待事件(宿主环境执行完某个异步任务后,就会把相应的回调函数添加到事件循环队列,所说的等待事件指的就是这些回调函数)

javascript引擎时刻准备着,在需要的时候执行相应的代码块(上述回调函数,假设主线程已执行完毕)

注意,并不是发起异步操作的那些代码将你的回调函数放入队列,比如 setTimeout() 并没有把你的回调函数挂在事件循环队列中,它所做的仅仅是设定(发起)一个定时器,当到指定时间后,环境会把你的回调函数放在事件循环队列中(那一时刻的队尾)。

补充一点,定时器执行时间总是比我们要求的多一点的原因是:因为在定会器的回调在队列的位置之前的位置很有可能有其他的回调,而且必须等待他们执行完毕后定时器的回调才能执行。

异步操作也会带来诸多问题
比如多个异步操作所操作的对象之间有某种关系、有一定的顺序要求,或者一个异步任务的发生与另一个异步任务的回调同时到达等等

比如:

var a,b;
function foo(x){
    a=x+2;
    baz();
}
function bar(y){
    b=y+2;
    baz();
}
functionbaz(x){
    console.log(a+b);
}
ajax(xxx,foo);
ajax(xxx,bar);

我们无法知道哪个异步的回调先返回到javascript引擎,所以结果不确定
虽然我们可以通过判断,a、b都有值时才调用baz,但显然是很麻烦的,(在之后Promise那会解决这些问题)

ES6从本质上改变了在哪里管理事件循环,它精确指定了事件循环的工作细节,这意味着在技术上将其纳入了javascript引擎的势力范围,而不是只由宿主环境来管理。这个改变主要原因是ES6中Promise的引入,因为这项技术要求对事件循环队列的调度运行能够直接进行精细控制。

任务队列:
建立于事件循环队列之上(Promise的异步特性基于此),是挂在事件循环队列的每个tick之后的一个队列,在事件循环的每个tick中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前tick的任务队列末尾添加一个项目(任务)

举个栗子,事件循环队列类似于一个游乐园游戏,玩过了一个游戏后,你需要重新到队尾排队才能再玩一次;而任务队列类似于玩过了游戏之后,插队接着继续玩。

再举栗

console.log('A');

setTimeout(function(){console.log('B')},0);

//任务队列中
shedule(function(){console.log('C')});
shedule(function(){console.log('D)});

结果:A C D B

因为首先会执行主线程中顺序执行(非异步)的代码,所以先打印A接着顺序执行,发起一个定时操作,宿主环境安排的负责监听定时的线程将回调放入处于事件循环队列中的任务队列的末尾(因为定时为0 所以立刻响应)然后将任务队列中的回调依次执行,所以打印C、D、B

this指向

this到底指向谁

首先判断函数的调用位置,如果关系复杂则可通过开发者调试工具调用栈查看,即该函数运行时调用栈的第二个元素。

然后在该调用位置上根据四条法则进行判断(有优先级 从优先级高的开始判断)

  • 函数被new调用,this绑定(指向)新创建的对象
  • 函数被call、apply调用,this绑定(指向)call或apply的第一个参数
  • 函数被某对象调用,this绑定(指向)该对象
  • 非以上三者(如正常调用函数),默认绑定,绑定(指向)全局对象window,严格模式下绑定undefined

注意出现绑定丢失现象,如

var fun1={};
fun1.foo=function(){console.log(this)};
var fun2=fun1.foo;
fun1.foo();     //this指向fun1
fun2();           //this指向window

同理setTimeout也是如此,调用实参函数的是形参,相当于上述丢失现象,所以this指向全局对象

箭头函数中的this
ES6中箭头函数并不会使用四条法则,而是根据当前词法作用域来决定this的指向。箭头函数会继承外层函数调用的this绑定,机制同软绑定(var that=this),且箭头函数的绑定无法被修改,new也不能修改箭头函数中的tihs指向。
例如

function foo(){
    return (a)=>{
	console.log(this.a);
};
}

var obj1={
a:2
};

var obj2={
a:3
};

var bar=foo.call(obj1);
bar.call(obj2);    //2

this继承箭头函数的外层函数foo的this

闭包

闭包:函数在定义时的词法作用域以外的地方被调用,该函数仍然可以继续访问定义时的词法作用域。

闭包举例:

function wai(){
var a=10;
function nei(){
	console.log(a);
}
   return nei;
 }
var a=11;
var fun=wai();
fun();    //10

因为fun引用了wai函数的执行结果(即wai函数的返回值nei函数)所以nei函数的作用域被保留,当fun函数执行时,按照正常的查询作用域即可。

无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

总结:无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

flex (flexbox)布局

flex 布局即弹性伸缩布局盒模型,为解决复杂的css布局问题而生。
Flexbox布局比较适合Web应用程序的一些小组件和小规模的布局,而Grid布局更适合用于一些大规模的布局

属性介绍:

  1. display(flex container 定义在包含块 即父元素)
    用来定义伸缩容器,是内联还是块,这个时候,他的所有子元素将变成flex文档流,称为伸缩项目(flex item)
    display: other values | flex | inline-flex;
  • flex:块
  • inline-flex:内联
  1. flex-direction(flex container)
    用来创建主轴,从而定义了伸缩项目放置在伸缩容器的方向
    flex-direction: row | row-reverse | column | column-reverse
  • row(默认值):从左向右排列
  • row-reverse:与row排列方向相反
  • column:类似 于row,不过是从上到下排列
  • column-reverse:类似于row-reverse,不过是从下到上排列
  1. flex-wrap(flex container)
    用来定义伸缩容器里是单行还是多行显示,侧轴的方向决定了新行堆放的方向
    flex-wrap: nowrap | wrap | wrap-reverse
  • nowrap(默认值):伸缩容器单行显示
  • wrap:伸缩容器多行显示,伸缩项目从左到右排列
  • wrap-reverse:伸缩容器多行显示,伸缩项目从右向左排列(和wrap相反)
  1. flex-flow(flex container)
    “flex-direction”和“flex-wrap”属性的缩写版本
    flex-flow: flex-direction flex-wrap
    某个属性不写则为该属性的默认值

  2. justify-content(flex container)
    用来定义伸缩项目沿着主轴线的对齐方式,当一行上的所有伸缩项目都不能伸缩或可伸缩但是已经达到其最大长度时,这一属性才会对多余的空间进行分配。当项目溢出某一行时,这一属性也会在项目的对齐上施加一些控制
    justify-content: flex-start | flex-end | center | space-between | space-around;

  • flex-start(默认值):伸缩项目向一行的起始位置靠齐
  • flex-end:伸缩项目向一行的结束位置靠齐
  • center:伸缩项目向一行的中间位置靠齐
  • space-between:伸缩项目会平均地分布在行里。第一个伸缩项目一行中的最开始位置,最后一个伸缩项目在一行中最终点位置
  • space-around:伸缩项目会平均地分布在行里,两端保留一半(均匀的一半)的空间
  1. align-content(flex container)
    用来调准伸缩行在伸缩容器里的对齐方式,类似于伸缩项目在主轴上使用“justify-content”一样,注意本属性在只有一行的伸缩容器上没有效果,操作单位是行
    align-content: flex-start | flex-end | center | space-between | space-around | stretch;
  • flex-start:各行向伸缩容器的起点位置堆叠
  • flex-end:各行向伸缩容器的结束位置堆叠
  • center:各行向伸缩容器的中间位置堆叠
  • space-between:各行在伸缩容器中平均分布
  • space-around:各行在伸缩容器中平均分布,在两边各有一半的空间
  • stretch(默认值):各行将会伸展以占用剩余的空间
  1. align-items(flex container)
    用来定义伸缩项目可以在伸缩容器的当前行(注意,是在每行内)的侧轴上对齐方式,操作单位是每行的item
    align-items: flex-start | flex-end | center | baseline | stretch;
  • flex-start:伸缩项目在侧轴起点边的外边距紧靠住该行在侧轴起始的边
  • flex-end:伸缩项目在侧轴终点边的外边距靠住该行在侧轴终点的边
  • center:伸缩项目的外边距盒在该行的侧轴上居中放置
  • baseline:伸缩项目根据他们的基线对齐
  • stretch(默认值):伸缩项目拉伸填充整个伸缩容器,此值会使项目的外边距盒的尺寸在遵照「min/max-width/height」属性的限制下尽可能接近所在行的尺寸
  1. align-self(flex items)
    在单独的伸缩项目上覆写默认的对齐方式,即在侧轴方向上搞特殊化
    align-self: auto | flex-start | flex-end | center | baseline | stretch;

  2. order(flex items)
    “order”属性可以控制伸缩项目在他们的伸缩容器(主轴方向上?)出现的顺序,值越大排序越靠后
    order: 一个数值;

  3. flex-grow(flex items)
    用来定义伸缩项目的扩展能力,它接受一个数值做为一个比例,主要用来决定伸缩容器剩余空间按比例应扩展多少空间
    flex-grow: 一个数值;
    例如这样一段代码

    效果如下

    i1的宽度为(300-200(已设定固定的宽度))/32
    i2的宽度为(300-200(已设定固定的宽度))/3
    1
    当然,因为代码的执行顺序,justify-content:space-around;代码的效果被覆盖掉了

  4. flex-basis(flex items)
    用来设置伸缩基准值,剩余的空间按比率进行伸缩
    flex-basis: <length> | auto; /* default auto */

  • 如果设置为“0”,不考虑剩余空白空间
  • 如果设置为自动,则按照flex-grow值分配剩余空白空间
  1. flex-shrink(flex items)
    根据需要用来定义伸缩项目收缩的能力 注意:负值同样生效
    flex-shrink: <number>; /* default 1 */

  2. flex(flex items)
    这是“flex-grow”、“flex-shrink”和“flex-basis”三个属性的缩写。其中第二个和第三个参数(flex-shrink、flex-basis)是可选参数
    flex: flex-grow flex-shrink flex-basis

JS模拟类的继承

定义父类

function Super(name,old){
    this.name=name;
    this.old=old;
}

在父类的prototype属性上(指向函数的原型对象) 定义方法

Super.prototype.eat=function(){
    console.log("eat.");
}
Super.prototype.sayMother=function(){
    console.log("Mom.");
}

定义子类

function Child(name,old,hobby){
    //通过call调用父类的构造函数
    Super.call(this,name,old);
    this.hobby=hobby;
}

将子类原型对象的原型指向父类的prototype属性,或者说是将子类与父类关联起来
Child.prototype.__proto__=Super.prototype;

在子类的prototype属性上定义方法

Child.prototype.sayFather=function(){
    console.log("Baba.");
}

创建一个子类的实例对象
var child=new Child("Lili",1,"play football");

Node环境下 采用回调、Promise、async/await实现读取文件

// 引入基本配置 无须在意

const http=require('http');
const path=require('path');
const fs=require('fs');
const chalk=require('chalk');
const promisify=require('util').promisify;
const conf=require('./config/defaultConfig');
const stat=promisify(fs.stat);
const readFile=promisify(fs.readFile);
const readDir=promisify(fs.readdir);

const server=http.createServer((req,res)=>{
const filePath=path.join(conf.root,req.url);

// anync版本

(async function(){
try{
let stats=await stat(filePath);
if(stats.isFile()){
res.statusCode=200;
res.setHeader('Content-Type','text/plain;charset=utf-8');
fs.createReadStream(filePath).pipe(res);
}
else if(stats.isDirectory()){
res.statusCode=200;
res.setHeader('Content-Type','text/plain;charset=utf-8');
let files=await readDir(filePath);
res.end(files.join('\n'));
}
}catch(err){
res.statusCode=404;
res.setHeader('Content-Type','text/plain;charset=utf-8');
res.end(${req.url.slice(1)} 被外星人带走啦~);
return;
}
})();

// promise版本

// stat(filePath).then((stats)=>{
//     if(stats.isFile()){
//         res.statusCode=200;
//         res.setHeader('Content-Type','text/plain;charset=utf-8');
//         fs.createReadStream(filePath).pipe(res);
//     }else if(stats.isDirectory()){
//         res.statusCode=200;
//         res.setHeader('Content-Type','text/plain;charset=utf-8');
//         readDir(filePath).then(files=>{
//             res.end(files.join('\n'));
//         })
//     }
// }).catch(err=>{
//     res.statusCode=404;
//     res.setHeader('Content-Type','text/html;charset=utf-8');
//     res.end(`<h1>${req.url.slice(1)} 被外星人带走啦!</h1>`);
//     return;   // 如果找不到 就中断这个回调 即终端这次链接避免执行下面代码
// })

// 普通回调版本

// fs.stat(filePath,(err,stat)=>{
// if(err){
// res.statusCode=404;
// res.setHeader('Content-Type','text/html;charset=utf-8');
// res.end(<h1>${req.url.slice(1)} 被外星人带走啦!</h1>);
// return; // 如果找不到 就中断这个回调 即终端这次链接避免执行下面代码
// }
// if(stat.isFile()){
// res.statusCode=200;
// res.setHeader('Content-Type','text/plain;charset=utf-8');

// // 组好采用流的形式
// fs.createReadStream(filePath).pipe(res);
// }
// else if(stat.isDirectory()){
// res.statusCode=200;
// res.setHeader('Content-Type','text/plain;charset=utf-8');
// fs.readdir(filePath,(err,files)=>{
// res.write(files.join('\n'));
// res.end();
// });
// }
// });

});

server.listen(conf.port,conf.hostname,()=>{
const addr=http://${conf.hostname}:${conf.port}
console.log(你的服务器运行在${chalk.green(addr)});
});

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.