JS类

promise.then里面有promise时的输出
new Promise((res) => {
    console.log('p1');
    res()
}).then(() => {
    console.log('p1-t1');
    new Promise((res) => {
        console.log('p2');
        res();
    }).then(() => {
        console.log('p2-t1');
    }).then(() => {
        console.log('p2-t2');
    });
}).then(() => {
    console.log('p1-t2')
});

// p1 -> p1-t1 -> p2 -> p2-t1 -> p1-t2 -> p2-t2
  • p1和p1-t1不难理解,整体代码块是一个macro-task,promise实例化过程中传入的函数会立马执行,故先输出p1,然后记录then里的回调,等待res被执行,排入micro-task队列里,故第二个输出的是p1-t1
  • 同理内部的promise立马执行,注册回调,调用res,先后输出p2和p2-t1
  • 但这里我们可能会有疑惑为什么p1-t2是在p2-t1之后触发的,最开始外部promise注册回调的时候不应该先把p1-t2记录下来,然后才轮到p2-t1?注册时的先后顺序是对的,但调用时机不同,由于promise then会返回一个promise,所以第二个then中执行回调的时机取决于第一个then返回的promise什么时候调用res
  • 而第一个promise then返回的promise,就是then里面的函数,相当于,then会将传入的函数用new promise包装一层,然后根据他的返回决定什么时候执行res,如果函数返回是一个promise的话,then包装的promise会将其res方法交给返回的promise的then处理,而如果返回的是非promise(没有写return,等同于返回undefined),则会在执行完回调的时候自动执行then包装的promise的res
  • 所以p2输出之后,执行了内部promise的res,p2-t1被推入micro-task队列,然后外部promise的第一个then执行完毕,没有返回,调用隐藏的res,执行第二个then回调,p1-t2推入micro-task队列
  • 所以先p2-t1,再p1-t2
  • 最后同理,内部promise的第二个then回调在第一个then函数执行完后执行,输入p2-t2
  • 那么换个形式将内部promise作为then的返回看输出
new Promise((res) => {
    console.log('p1');
    res()
}).then(() => {
    console.log('p1-t1');
    const p = new Promise((res) => {
        console.log('p2');
        res('inner');
    });
    p.then(() => {
        console.log('p2-t1');
    }).then(() => {
        console.log('p2-t2');
    });
    return p;
}).then((data) => {
    console.log('p1-t2', data)
});

// p1 -> p1-t1 -> p2 -> p2-t1 -> p2-t2 -> p1-t2 inner
  • p1 -> p1-t1 -> p2 -> p2-t1 这一段逻辑与前相同
  • 关键是最后两个的顺序,因为内部的promise是作为第一个then的返回的,所以第二个then的执行取决于第一个then被resolve的时机,而第一个then被resolve时,是在其内部返回的promise被resolve后的then里执行的
  • 也就是说内部promise被resolve后,首先内部promise的第一个then被推入micro-task,然后第二个被推入micro-task的是外部promise第一个then包装后的promise的res方法,接着第三个推入micro-task的是,内部promise的第二个then
  • 所以先输出p2-t1,然后resolve外部promise第一个then返回的promise,第二个then被推入micro-task,输出p2-t2,最后执行刚刚推入队列的p1-t2的输出,data来自内部promise的resolve值
  • 注意promise.then链式调用返回的是最后一个then返回的promise,即
new Promise((res) => {
    console.log('p1');
    res('innner')
}).then(() => {
    console.log('p1-t1');
    return new Promise((res) => {
        console.log('p2');
        res();
    }).then(() => {
        console.log('p2-t1');
    }).then(() => {
        console.log('p2-t2');
    });
}).then((data) => {
    console.log('p1-t2', data)
});

// p1 -> p1-t1 -> p2 -> p2-t1 -> p2-t2 -> p1-t2 undefined
  • 因为第一个then返回的是内部promise最后一个then生成的promise,他没有写返回,所以是undefined
setTimeout和setImmediate哪个先执行
  • setImmediate一般在nodejs环境下运行,大部分主流浏览器不支持
  • 由于nodejs在执行setTimeout(fn, 0)的时候会把0改成1,即setTimeout(fn, 1),setImmediate与setTimout的执行顺序取决于主线程的运行时间,若主线程运行超过1ms,则先执行setTimeout,后执行setImmediate
nodejs的事件循环
  • 与浏览器不同,分为:
- timers阶段:这个阶段执行已经到期的timer(setTimeout,setInterval)回调
- I/O callbacks阶段:执行I/O(例如文件、网络)的回调
- idle, prepare 阶段:node内部使用
- poll阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
- check阶段:执行setImmediate回调
- close callbacks阶段:执行close事件回调,比如TCP断开连接
  • 每个阶段中间会执行micro task,process.nexttick会插入执行micro task
  • 由于这几个阶段的存在,针对上面的settimeout和setimmediate,假如是下面的代码
setTimeout(() => {
    setTimout(() => console.log(1), 0);
    setImmediate(() => console.log(2));
}, 0)
  • 由于setTimeout里的回调在timmer阶段执行,新的setTimeout回调会在下一个循环的timmer里执行,但setImmediate会在本次循环的check阶段执行,所以setImmediate一定会比setTimeout先执行,即使node会默认把setTimeout的0改成1
toFixed的处理
  • toFixed并不是四舍五入,而是一种叫银行家舍入法,规则如下
  • 小于5舍去,大于5进一
  • 等于5看后续,后续非0进一,后续为0看前一位
  • 前一位是偶数舍去,前一位是奇数进一

箭头函数与普通函数区别

  • 箭头函数的this是定义时的上下文,普通函数的this是调用它的对象
  • 箭头函数没有自己的arguments,但可以访问到外层的arguments
  • 箭头函数可以用apply和call,但无法修改this的指向,apply和call的第一个参数会被忽略
  • 箭头函数没有原型
  • 箭头函数无法作为构造函数,因为new的过程要利用apply或call修改this的指向,以及需要用到构造函数的原型进行关联,这些箭头函数都没有,所以无法作为构造函数

CSS

flex:1
  • flex: 1是flex-basis: 0, flex-shrink: 1, flex-grow: 0, 三者的缩写
  • flex-basis表示元素宽度,flex-shrink表示视图宽度小于元素宽度时的压缩比例,flex-grow表示视图宽度大于元素宽度时,剩余宽度的分配比例
position
  • static:默认值,无定位,忽略left、top、z-index等属性
  • relative:相对定位,基本同static,但left、top、z-index起作用
  • absolute:绝对定位,相当于第一个static以外的父元素的定位,且使用该定位的元素脱离文档流,不会影响后续元素的布局
  • fixed:固定定位,相对于窗口的定位
  • inherit:从父组件继承
  • sticky:css3新增,粘性布局,元素在屏幕内,按reletive布局,当父组件发生滚动,元素即将被移出屏幕外时,按fixed布局

webpack

  • webpack自带tree-shaking,但并不一定能把无用依赖删除
  • tree-shaking依赖的是es6的export和import,在静态编译过程就对代码进行删除,这要求export与import位于文档的最上方
  • 而比如类,因为类通常会使用babel转义,class会被转义成function,并在文件下方进行export,这个时候就会产生副作用,webpack无法知道export之前,这段js代码做了什么,假设他在window上绑定了某个属性,或修改了某个对象的原型链,即使外部文件只是import了这个文件,但实际没有使用,因此对其tree-shaking的话会导致某些操作丢失,所以webpack不会对其删除
  • 解决方法,在package.json中增加sideEffects属性,可以是一个数组,存放路径,指明哪些模块是有副作用的,或者在webpack.config的rules属性中也可以增加sideEffects

浏览器

script/async/defer

  • 单纯一个script,遇到立即下载,下载完立即执行,下载与执行都会阻塞dom的渲染
  • 有async的script,遇到立即下载,下载完立即执行,但下载不会阻塞dom的渲染,下载是并行的,但下载完的执行会阻塞dom渲染
  • 有defer的script,遇到立即下载,等dom渲染完才会执行,具体是在DOMContentLoaded之后,此时dom已经渲染完了,另外下载阶段也是异步的,不会阻塞dom的渲染
  • 总结一下就是js的执行一定会阻塞dom渲染,但因为defer的执行是在渲染之后,所以不存在渲染的阻塞

DOMContentLoaded和onload

  • DOMContentLoaded,dom构造完成,不包括图片表的加载
  • onload,图片也加载完成后触发

zengrc
28 声望0 粉丝