前端八股文

179 阅读21分钟

HTML+CSS

1、css盒模型,普通盒模型和怪异盒模型

盒模型:css把每个元素视作一个盒子,包括内容content,内边距padding,边框border,外边距margin四个部分,盒模型是css布局的基本单元

普通盒模型:w3c标准盒模型,规定width和height属性只包含content部分

怪异盒模型:IE盒模型,规定width和height属性包括content,margin和padding三部分,设置方式boxsizing:border-box

2、块级元素,行内元素,行内块元素

:默认宽度和父盒子一样宽,高度由内容撑开,独占一行,可以直接设置width,height,任意的margin,可以容纳其他行内元素或者块级元素。常见的有div,h1-h6,ul和li,p标签等

行内元素:可以水平排列,宽高均由内容撑开,不可以直接设置宽高,只能容纳其他行内或者行内块元素,不能设置上下的margin和padding。常见的有span,a标签等

行内快:一行可以放置多个,也可以设置宽高,常见的有input,img标签等

3、伪元素选择器和伪类选择器的区别

伪元素:以::为结尾,用于创建一些文档树中不存在的元素,注意::bfore和::after必须设置content属性,能够保证结构清晰

伪类选择器

1、结构伪类 :nth-child(n),:nth-of-type(n) 两者区别在于前者直接匹配数量,后者先匹配类型,再匹配序号

2、链接伪类 :hover鼠标悬浮 :visited访问过的 :active激活

4、css常见的选择器

1、普通的:元素{}、类class、id#

2、结构类:子代>、后代空格

3、伪元素和伪类

4、交,并(例如div.box)

5、属性选择[]

6、通配符*

选择器的权重

1、!important无穷大

2、内嵌式style 1,0,0,0

3、id 0,1,0,0

4、类/伪类/属性 0,0,1,0

5、标签/伪元素 0,0,0,1

6、通配符 0,0,0,0

7、继承的样式没有权重

5、常见的长度单位

px:像素,相对于显示器的分辨率而言的,也叫逻辑像素。移动端根据物理相比/逻辑像素之比使用多倍图

em/rem:em相对于父元素的font-size,rem是相对于根元素(html)的font-size

rpx:小程序特有的,规定屏幕的宽度为750rpx

vw/vh:相对于浏览器视口的宽高

6、垂直居中的几种方式

单行文字的垂直居中

1、行高line-height===高度height

2、vertical-align:middle(只对行内和行内快有效) image.png

通用的垂直居中

1、position+tranform:

image.png

2、position+calc动态计算

top:calc(50%-width*50%)

3、flex布局

align-item:center

7、flex弹性盒模型

1、display:flex(宽度默认和父亲一样宽)/inline-flex(内容撑开)

2、父盒子属性

flex-direction(主轴方向):row/row-reverse,colomn/colomn-reverse

flex-wrap(是否自动换行):wrap/nowrap

flex-flow(上面两个属性的合并写法)

justify-content(主轴的排列方式):flex-start/end, center, space-between/around/evenly

align-item(单行的辅轴排列方式):stretch,flex-start/end,center

align-content(多行的辅轴排列方式):flex-start/end, center, space-between/around/evenly

3、子盒子属性

flex:flex-grow(伸长系数),flex-shrink(收缩系数),flex-basis(基础长度,优先级比width高,但兼容性不好)三个属性合成

align-self: 覆盖自身的align-item

order:默认为0,越小越靠前

8、浮动,浮动塌陷,清除浮动的问题

浮动:1、脱离文档流,2、不覆盖文字,3、只影响后续的盒子,不影响之前的盒子。脱标的盒子:默认内容撑开,可以设置宽高,不再独占一行

浮动塌陷:浮动脱标,后续的盒子会覆盖浮动的盒子,因为内容撑不开浮动的盒子

清除浮动塌陷

1、额外标签/伪元素设置clear:both,注意标签需要是块盒子

2、设置over-flow:hidden

3、开启BFC环境

9、块级盒子外边距塌陷

原因:

1、相邻的两个块盒子,上方设置margin-bottom,下方设置margin-top,会以大的生效,不会叠加

2、父子塌陷,设置子盒子的margin-top,会带着父盒子一起走,而不是和父盒子有距离

解决办法:

1、设置不了margin就用padding

2、子盒子转成行内快,行内快不存在塌陷

3、开启BFC

10、BFC和IFC

BFC(块级格式化环境):开启后,形成一个独立的区域,布局不会被外部影响,可以用于解决浮动塌陷和外边距塌陷问题

IFC:内敛格式化环境

11、隐藏元素的三种方式

1、display:none

元素消失,不占页面空间,会导致回流和重绘,不能响应交互类的事件

2、visible:hidden

元素占据空间不变,会导致重绘,不能响应交互类的事件

3、opcity:0

元素占据空间不变,不会回流重绘,可以响应交互类的事件

12、回流和重绘

回流:回流一定会导致重绘,当布局或者元素的几何属性发生变化后会导致重绘,例如:页面的初始渲染,浏览器窗口尺寸变化,元素位置和尺寸变化,添加或删除dom

重绘:重绘不一定会回流,不改变布局的样式变化会导致重绘

13、H5新增内容

1、语义化标签:Aside,main,nav,footer,section,article等

2、表单标签:date,email等

3、绘图标签:canvas,视频音频标签:video,audio

4、本地存储:localStorage和sessionStorage

5、window的history对象,history路由的原理

14、href和src的区别

作用结果:href是建立引用,sec是替换当前标签

浏览器解析方式:碰到href时会并行下载资源,碰到src会暂停其他资源的下载和处理,直到src的文件加载,编译,执行完毕

15、link和import的区别

1、link为标签,除了可以引入css,还可以设置一些属性,import是css的新语法,只可以用于导入样式

2、link会和html同时加载,import引入的css需要在页面加载完毕后加载,尽量使用link

16、css画三角形

image.png

17、三栏布局

1、margin+position

image.png

2、flex

image.png

3、grid

image.png

优先渲染中间main区域

4、圣杯布局,也可以用flex实现圣杯,设置order就可以了

image.png

5、双飞翼

image.png

18、实现0.5px的边框

JS部分

1、js异步

js是一门单线程的语言,它运行在浏览器的渲染进程中的渲染主线程,而渲染主线程只有一个,它承担着诸多的工作,例如页面的渲染,一秒钟要绘制页面60次,执行js等,如果使用同步的方式,就很有可能导致主线程阻塞,从而导致任务队列中的任务无法及时得到执行,一方面白白浪费时间,另一方面页面也无法及时得到渲染,用户体验差

所以,浏览器采用异步的方式避免,具体做法是当某些任务发生时,比如开启定时器,发起网络请求,事件监听等,主线程会将任务交由其他线程操作,当其它线程完成之后,会将事先传递的回调函数包装成任务,加入到消息队列的末尾,等待主线程的调度执行,在这种模式下,最大程度的保证了单线程的流畅运行

2、事件循环Event Loop

事件循环也叫做消息循环,是浏览器渲染主线程的工作方式,在chrome源码中定义为messageLoop,他会开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务进行执行,其他线程只需要在合适的时候将任务加到消息队列的队尾,过去把消息队列分为宏任务队列和微任务队列,微任务主要有promise的回调函数,MutationWatcher等。宏任务有定时器,延时器,文件的IO等,js主执行栈会从微任务队列中依次拿去任务放到主执行栈中执行,微任务队列没有任务之后会执行一个宏任务,继而继续执行微任务队列

不过随着浏览器复杂度的提升,目前w3c已经摒弃了宏任务和微任务这种说法,两种任务队列满足不了复杂的环境,比如常用的有延时队列,交互队列,由浏览器自行决定调用哪一个任务队列,但是浏览器仍然必须要有一个微任务队列,拥有最高的优先级,必须优先调度执行

3、对原型链的理解

1)原型对象:每一个构造函数在创建时都会被赋予一个prototype属性,指向构造函数的原型对象,这个对象可以包含所有实例共享的属性和方法

2)原型链:每一个实例对象都具有一个—__proto__属性,指向构造函数的原型对象,同样的,构造函数的原型对象上也存在一个__proto__属性,指向上一级构造函数的原型对象,就这样层层向上,直到某个原型对象为null。原型链终止 3)原型链上的所有节点都是对象,不能是字符串,数字等原始的数据类型,规范要求原型链必须是有限长度的,由于无法访问null的属性,所以null起到了终止原型链的作用

4、js有哪些数据类型

1)基本数据类型:String Number Boolean null undefined

  1. 引用数据类型 Array Object

es6新增数据类型:Symbol,表示一个独一无二的值,由Symbol函数生成,一般作为对象的属性使用,使用symbol函数生成的属性不会被for in、Object.key、Json.parse(Json.stringfy)访问到,因此可以把一些不对外开放的属性用symbol函数生成。如果想要访问symbol函数生成的属性,可用Object.getOwnPropertySymbols(obj)或者Reflect.ownKeys(obj)访问

5、Null和undefined的区别

1)null:空,但是是被定义过的。undefined:声明未赋值的变量或者是不存在的对象属性

2)null可以作为原型链的终点,undefined的情况一般有以下几种

情况1: 变量声明未赋值

情况2: 函数需要的参数没有传递,但是函数内部使用了

情况3: 对象不存在的属性

情况4: 函数没有返回值,默认返回undefined

3)Number(null)为0,但是Number(undefined)是NaN不是数字

6、js判断数据类型的方法

1)typeof:结果为字符串形式,特殊情况:null,[],{}都会判断为'object'

  1. instanceof:结果为Boolean,一般用于判断是否是某个构造函数的实例,特殊情况,对于[] instanceof Object/Array 结果均为true

3)Object.prototype.toString.call()结果为'[object type]'格式的字符串,所有格式都可以判断

7、js判断一个变量是否为数组

1)instanceof: [] instanceof Array === true

  1. Array.isArray: Array.isArray([]) === true

3)Object.prototype.toString.call: Object.prototype.toString.call([]) === '[object Array]'

4)利用原型链判断:例如[].proto === Array.prototype

8、js继承的方式以及区别

1)原型链继承

Son.prototype = new Father():子构造函数的原型指向父构造函数的实例

优点:简单,通过Father.prototype添加的属性和方法以及在构造函数内用this挂载(实例自身)的方法都可以继承

缺点:不能传参。对于引用类型的属性,如果修改这个属性的某项(不是整体修改),那么会影响到其他的子类实例,因为这样是修改的是原型上继承来的,共用一份,但是如果是基本的数据类型或者引用类型整体覆盖是不会影响其他实例的,因为这样是在子类自身又单独挂了一份,原型上的才是共享的,下图就可以看出来绿色是共享的,红色是独有的一份,访问优先访问自身

image.png

2)构造函数继承

function Son(){ Father.call(this) }

优点:能够传参,也解决了引用类型共享(因为都是单独设置在自身上的而不是原型)的问题

缺点:通过Father.prototype添加的方法是继承不到的,也可以这么理解,就是把父类中this的代码又拷贝了一份到子类中,原型上的自然继承不到(根本父类的原型根本没有出现在子类的原型链上)

3)组合式继承

上述两种都用,缺点就是冗余,因为实例自身挂载了一份,原型上也挂载了一份

4)寄生组合继承

在组合式继承中,使用一个中间的构造函数Fn.prototype = Father.prototype,可以免掉Son中挂载在实例上的重复的属性和方法,这样能够想到,通过call继承在自身的属性还在,但是原型链继承的实例上的一份就没了(中间这个构造函数的实例是空的),但是又能拿到父类原型上的方法(再上一层的原型),其实自己写一下就可以明白。

image.png

5)ES6继承

使用extends和super关键字继承,如果想要设置自身独有的constructor,需要用super关键字继承

ES5继承和ES6继承的区别

es5继承:实质上是先创建子类的实例,再将父构造函数的属性和方法添加到子类的this上(Father.call(this))

es6继承:通过class定义类,extends和super关键字实现继承,实质上是先创建父类的实例,再调用子构造函数修改this

9、call,apply,bind的区别(手搓)

call和apply是立即调用改变this的函数,bind是返回一个改变了this指向的函数便于稍后调用

call和bind是正常传参,bind是传递一个数组,将参数写入数组中

image.png

9、防抖和节流的区别,应用(手搓)

防抖:事件触发后等待n秒之后再执行回调,如果在等待的这n秒内再次触发了该事件,重新计算等待时间,简单说就是只触发最后一次事件的回调函数

应用:例如远程搜索联想

image.png

节流:事件触发后进行判断有没有该事件正在等待执行的回调函数,如果有则本次事件不生效,简单来说就是n秒内只触发第一次事件的回调函数

应用:下拉加载更多,或者防止用户疯狂点击产生大量的http请求

image.png

相同点:都是减少http请求,进行性能优化的方法

10、深拷贝和浅拷贝以及实现方法

浅拷贝:对原始对象的属性进行一份精确拷贝,如果是基本数据类型,拷贝的是值,如果是引用类型,拷贝的是地址。只拷贝对象的一层

实现方法:

方法1:let b = Object.assign({},a)

方法2:let b = [...a]

方法3:concat方法或者slice方法:let b = a.concat()或者a.slice()

方法4:手写浅拷贝

image.png

深拷贝:

方法1:Json.parse(Json.stringfy(b))

缺点:会忽略undefined,无法序列化函数,解决不了循环引用问题

方法2:循环浅拷贝(解决循环引用自身的问题,递归+哈希表)

image.png

方法3:利用MessageChannel构造函数(管道通信,也可用于webworked传值)

image.png

11、对比var,const和let

1)重复声明:var定义的变量可以重复声明,let不可以

2)变量提升:其实是都存在变量提升的,我理解的是var定义的变量只需要声明就可以完成初始化的过程,但是let定义的必须要声明+赋值(所谓的暂时性死区),可以看下图的代码:如果没有let a = 2这个行代码,找不到a自然会向外层的作用域去寻找,打印1,但是加上let a = 2之后会报错,如果let没有变量提升,那应该向外寻找打印1而不是报错。

image.png

3)作用域:var是函数级的作用域,如果没有声明直接使用的变量,默认为全局作用域,挂在windows下,而let声明的是单独的块级作用域,以{}为一个块,可以看下图的例子:var声明的最终会打印出三个3,let声明的会是1,2,3

image.png

题外话:其实上图的let声明,想要打印出1,2,3还可以用闭包的方式,利用立即执行函数形成闭包,保存变量

image.png

12、ES6新特性

1)新增let const关键字

2)箭头函数 ()=>{}

3)模版字符串 ${变量}

  1. 解构赋值 const {a,b} = obj

  2. 函数的默认参数 function(a=1){}

  3. 对象的字面量简写 {a}等价于 {a:a}

  4. for of 遍历方法(map,set,Array,String格式内部有迭代器)

8)迭代器iterator和Generator执行函数

  1. 新增Map和Set类型

10)新增数据类型symbol

11)新增类Class

12)Promise

13)es6的模块化

14)二进制(0b)和八进制(0o)的字面量

13、普通函数和箭头函数的区别

1、箭头函数不能用作构造函数(因为内部没有执行函数constructor),不能使用new关键字(会报错),没有原型对象prototype

2、箭头函数没有自身的this,内部的this为函数定义时所在作用域的this,并且call,apply和bind也无法改变this

3、函数内不存在arguments,拿到的是外层函数的arguments,取而代之的是使用剩余参数的写法...rest

14、new的时候发生了什么

在内存中创建一个空对象------让this指向这个新对象------指向构造函数里面的属性和代码,给这个对象添加属性和方法------最后返回这个对象(这就是为什么构造函数中不需要return)

15、this指向问题

1、箭头函数的this为定义箭头函数时所在作用域的this,箭头函数自身没有this

2、全局下的this指向window

3、普通函数的this指向调用者:1)全局下:window。2)对象方法中:对象本身

4、事件绑定中的this:事件源(绑定事件的元素),事件对象event才是触发事件的元素

5、构造函数的this:实例化的对象

15、对闭包的理解,有哪些应用,有什么缺点,如何避免

闭包:一个函数内使用到了另一个函数的局部变量,这样的变量不会被垃圾回收机制回收,能够持久保存

应用:防抖,节流,函数的柯里化,元素循环绑定事件

缺点:比普通函数更占用内存,滥用闭包会造成内存泄露

解决办法:在退出函数之前,手动将闭包变量设置为null

image.png

16、js作用域

什么是作用域:作用域是可访问变量,对象,函数的集合,分为全局作用域、局部作用域和块级作用域(es6)

全局作用域:任何地方都能够访问到的变量:1)在最外侧函数定义的变量和最外层函数 2)未声明直接使用的变量

作用域链:当前作用域未声明但使用到的变量,会不断向外层函数寻找

17、函数柯里化,有什么用,如何实现

1、是什么:把一个多参数的函数转化成单参数函数的方法

2、作用:延时计算,动态生成函数,复用参数

3、实现:

image.png

4、面试题:实现add(1)(2)(3)===add(1,2,3)===add(1,2)(3)

18、commonjs和es6Module的区别

1、ES6是编译时导出接口(),Commonjs是运行时导出对象

// index.js,可以看出,lib中拿不到name,所以打印undefined,如果换成es6的写法,由于编译时导出接口,已经能够拿到name,虽然没有值,后续赋值就能打印出index
const {log} = require('./lib.js');
module.exports = {
    name: 'index'
};
log();

// lib.js
const {name} = require('./index.js');
module.exports = {
    log: function () {
        console.log(name);
    }
};

2、ES6导入是值的引用,Commonjs导入的是一份拷贝

如果换成es6模块化,打印的就是bob
const {name} = require('./lib.js'); // 等价于const lib = require('./lib'); const {name} = lib;
setTimeout(() => {
    console.log(name); // 'Sam'
}, 1000);

// lib.js
const lib = {
    name: 'Sam'
};
module.exports = lib;
setTimeout(() => {
    lib.name = 'Bob';
}, 500);

3、ES6语法是静态的,Commonjs语法是动态的:commonjs可以根据条件导入,导入的模块参数可以是变量: require(./${template}/index.js) es6必须在顶层,且参数只能是字符串,因此支持treeShaking

4、ES6导入的模块是只读的,Commonjs导入的是可修改的变量

5、ES6支持异步加载,Commonjs不支持

19、js垃圾回收机制,v8引擎的垃圾回收机制

js内存分为栈空间和堆空间,栈用于存储静态的数据,例如基本数据类型和引用数据类型的地址,由于这些静态数据的大小是固定的,js引擎知道大小不会改变,于是给它们分配固定大小的内存,栈的垃圾回收方式非常简便,当函数执行完成后就会进行释放,堆是用来存储对象,函数的,与栈不同的是,js引擎不会为这些数据分配固定大小的空间

v8引擎的垃圾回收机制,v8引擎将堆内存分为新生代和老生代两个区域,新生代存放小的,变化快,存活时间短的对象,老生代存放大的,变化慢,存活时间久的对象。因此新生代只支持1-8M的容量,而老生代支持的更大,针对这两个区域,V8引擎分别使用

image.png

主垃圾回收器:负责老生代的垃圾回收(标记-清除-整理)

副垃圾回收器:负责新生代的垃圾回收(Scavenge算法)

所谓的Scavenge算法:将新生区域分为From空间和To空间,新的对象首先会被分配到From空间,当From空间快要写满时,将From空间中存活的对象赋值一份到To空间,然后清除From空间,最后将两个空间进行角色互换,这样能够保证这两块空间无限重复使用。

晋升策略:如果新生代的对象在经过两次变换后仍然存在,就会晋升到老生代区域

标记-清除-整理:先标记要回收的对象,清除不存活的对象,由于此时内存是不连续的,要将内存整理到一边,变成连续的内存

增量标记:老生代的对象比较大,使用标记-清除-整体会比较快,但仍然会卡顿,因为js是单线程的,因此,V8将标记过程分为一个个子标记过程,和js应用逻辑交替进行,直到标记完成,称之为增量标记

20、判断元素是否在视口

1、0<元素距离页面顶端的高度-页面被卷去的头部<页面可视区域的高度,代码如下:注意的是window.body.clientWidth比较特殊,会计算滚动条的宽度

image.png

21、for in和for of的区别

1、for in 遍历的是对象的key

2、for of 遍历的是value,但是只能用于Array,String Map,Set这样具有Iterator迭代器对象的数据结构,不能用于对象

22、事件冒泡和事件委托

Dom事件流存在三个阶段,捕获---目标阶段---冒泡

捕获是从外向内触发,冒泡是从内向外触发,事件委托就是利用事件冒泡,在父元素上绑定事件,那么每个子元素都可以触发

23、{},new Object 和 Object.create的区别

1、前两种创建的是Object的实例,能够继承原型上的属性和方法

2、Object.create(null)是纯粹的对象,没有Object原型上的属性和方法,第一个参数为必填,继承的目标。

24、Iterator迭代器

为什么要有这个迭代器:简单说就是不同的数据结构内部实现方式肯定是不同的,那么遍历的话,不同的数据类型有自己的遍历方法,很明显使用起来就需要去了解每种结构是怎么遍历的,就比较复杂

优点:Iterator迭代器就是一种统一的接口机制,为不同的数据结构提供一种统一的访问机制,js里面的表现就是for of循环,当使用for of遍历某种数据时,就会自动的寻找Iterator接口,只要部署了这种接口,就可以完成遍历操作

本质:迭代器对象本质上就是一个指针对象,通过指针对象的next方法移动指针

原生具备:js中原生具备Iterator接口的数据结构有:Array,String,Map,Set,函数的arguments对象,NodeList对象。

使用:在控制台打印一下Array.prototype可以看到,存在一个Symbol.iterator方法,如下图所示

image.png

通过调用该方法就会生成一个迭代器Iterator对象,而这个对象上存在一个next方法,每次调用此方法,都会生成一个对象,包含当前的值和遍历的状态,代码和结果如下图所示,可以看到当对象的done属性为ture时,表示已经没有可以访问的值了

image.png

实现:

image.png

25、Promise(手搓)

function Promise(consrtuctor) {
  this.PromiseResult = null;
  this.PromiseState = 'pending';
  this.callbacks = [];
  const resolve = (data) => {
    // 由于状态只能改变一次(只能由pending到rejected或者pending到resolved)
    if (this.PromiseState === 'pending') {
      if (data instanceof Promise) {
        data.then(
          (dat) => {
            this.PromiseResult = data;
            this.PromiseState = 'fulfilled';
            this.callbacks.forEach((item) => {
              item.onResolved(data);
            });
          },
          (error) => {
            this.PromiseResult = error;
            this.PromiseState = 'rejected';
            this.callbacks.forEach((item) => {
              item.onRejected(error);
            });
          }
        );
      } else {
        this.PromiseResult = data;
        this.PromiseState = 'fulfilled';
        this.callbacks.forEach((item) => {
          item.onResolved(data);
        });
      }
    }
  };
  const reject = (error) => {
    if (this.PromiseState === 'pending') {
      this.PromiseResult = error;
      this.PromiseState = 'rejected';
      this.callbacks.forEach((item) => {
        item.onRejected(error);
      });
    }
  };
  try {
    consrtuctor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

Promise.prototype.then = function (onResolved, onRejected) {
  return new Promise((resolve, reject) => {
    // resolve还是reject由回调函数的结果决定
    // 如果当前状态是fulfilled,那么执行onResolved, rejected则执行onRejected
    // 由于箭头函数没有自己的this,这里的this就是外层的this,即then方法中的this,外层promise实例
    if (typeof onResolved !== 'function') {
      onResolved = (data) => {
        return data;
      };
    }
    if (typeof onRejected !== 'function') {
      onRejected = (err) => {
        throw err;
      };
    }
    // 将复用的函数封装
    const callback = (type) => {
      const result = type(this.PromiseResult);
      if (result instanceof Promise) {
        result.then(
          (data) => {
            resolve(data);
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        resolve(result);
      }
    };
    if (this.PromiseState === 'fulfilled') {
      // 说明resolve了,执行onResolved函数,并通过该函数的返回值确定resolve还是reject
      callback(onResolved);
    }
    if (this.PromiseState === 'rejected') {
      callback(onRejected);
    }
    if (this.PromiseState === 'pending') {
      // 由于是异步的resolve,此时还是pending状态,要将onResolved函数和onRejected函数存起来
      // 存起来之后等待resolve或者reject之后再调用,由于then方法可以多次调用,所以将回掉存入数组
      this.callbacks.push({
        onResolved: () => {
          callback(onResolved);
        },
        onRejected: () => {
          callback(onRejected);
        }
      });
    }
  });
};

Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected);
};

Promise.resolve = function (data) {
  return new Promise((resolve, reject) => {
    if (data instanceof Promise) {
      data.then(
        (data) => {
          resolve(data);
        },
        (err) => {
          reject(err);
        }
      );
    } else {
      resolve(data);
    }
  });
};

Promise.reject = function (err) {
  return new Promise((resolve, reject) => {
    reject(err);
  });
};

Promise.all = function (iteArr) {
  return new Promise((resolve, reject) => {
    let result = [];
    let count = 0;
    for (let i = 0; i < iteArr.length; i++) {
      if (iteArr[i] instanceof Promise) {
        iteArr[i].then(
          (data) => {
            result[i] = data;
            count++;
            if (count === iteArr.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        result[i] = iteArr[i];
        count++;
      }
    }
  });
};

Promise.race = function (iteArr) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < iteArr.length; i++) {
      iteArr[i].then(
        (data) => {
          resolve(data);
        },
        (err) => {
          reject(err);
        }
      );
    }
  });
};

26、Generator执行函数(这个放在Iterator和Promise之后主要是你要比较深刻的理解Promise,才容易理解async)

Generator执行函数是为了解决异步问题的,普通函数在调用时会立即执行,直到return或者执行完毕,Generator函数是可以暂停的,不一定要把函数体中的代码都执行完毕

其实async await的实现就是利用了generator,是generator的语法糖 await相当于替换了yiled。async替换了*

简单的模拟一个async函数

// 生成promise
function promiseFn(data){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(data)
    },1500)
  })
}
// generator函数
function* genfn(){
 const result1 =  yield promiseFn(1)
 console.log(result1);
 const result2 =  yield promiseFn(2)
 console.log(result2);
 const result3 =  yield promiseFn(3)
 console.log(result3);
 return 22
}
// 简单模拟的async
function initAsync(fn){
  const ite = fn()
  function fn1(data){
    const {value,done} = ite.next(data)     
    if(done){
      return value
    }
    value.then(data=>{
      fn1(data)
    })
  }
  fn1()
}
initAsync(genfn)

27、10进制转其他

Number.prototype.transformer = function(n){
  let num = this
  let result = []
  let yushu
  const arr = ['A','B','C','D','E','F']
  while(num !== 0){
    yushu = num % n
    num = Math.floor(num / n)
    result.unshift(yushu>9?arr[yushu-10]:yushu)
  }
  return result.join('')
}
const a = 15
console.log(a.transformer(16)); // F

28、发布订阅模式和观察者模式

构建工具Webpack

1、webpack的工作流程

(1)初始化过程

输入webpack命令之后,webpack会将cli参数(命令携带的参数)、webpack配置文件,默认配置(比如默认的入口)进行合并,生成一个最终的配置对象,这个过程依赖于名为yargs的第三方库完成的

(2)编译构建阶段

生成chunk:根据入口模块创建一个chunk,chunk可能是多个,每个入口都是创建一个chunk,默认是一个,并且每个chunk都至少有两个属性:name(默认为main),id(开发环境和name相同,生产环境是一个数字,从0开始)

构建依赖(生成chunk模块列表):

31681013298_.pic.jpg 在chunkList(右侧表格)中检查模块是否加载(已经加载则结束)------读取文件内容------将读取的内容转化成抽象语法树(这一步主要是进行语法分析,分析当前模块依赖了哪些模块)------记录依赖后将记录的依赖保存到dependencies中,例如右侧的模块id------替换依赖函数(这部分主要是讲读取后保存的文件内容以字符串保存,替换字符串中的依赖函数,原文件是没有变化的,例如吧require变为webpack_require)------保存转换后的模块代码(右侧表格:模块id和转换后的代码)------循环dependencies递归加载模块(最终形成一个完整的chunk模块记录,右侧表格

生成资源列表:第二步之后会生成一个chunk模块列表,根据模块列表生成文件列表(文件名和文件内容,通常情况是一个文件,资源列表中每一项都是一个bundle,但是配置后也可以是多个),文件列表还会包含一个叫做chunk hash的东西,他是根据文件列表生成的,只要文件内容不变,hash就不变

合并chunk:每个chunk都会生成资源列表,有自己的hash值,将每个资源列表合并生成总的资源列表,同样有一个总的hash

(3)输出过程

将构建阶段生成的文件列表输出,生成真正的文件

总结:

41681014916_.pic.jpg

计算机网络

1、强缓存和协商缓存

框架Vue

1、vue响应式的原理(数据双向绑定v-model的原理)

2、vue3相比vue2是如何实现性能提升的

1)静态提升(例如静态节点和静态变量的提升)

21681227254_.pic.jpg

例如一个h1标签内的文本为固定的字符串‘helloword’,如上图所示,vue2编译时会将模版生成render函数,每个节点,属性 内容都会放在render中,那么当数据变化时又会重新执行render,然而一些静态的节点,比如这里的h1,也可能是标签变化但是属性不变(静态属性)就会重复执行,因此在vue3中,根据上图可以看到,讲这些静态节点或者是静态属性从render中抽离出来,不需要每次render都会执行,这就是静态提升。