前端-JS篇

988 阅读38分钟

前言: JS篇可以说是前端的重点,本人准备找实习了,做一些总结,对js重新进行缕一遍,也希望有理解错误的够被指出

JS基础篇

1、列举typeof返回的数据类型?

numberstringbooleanundefinedbigint(ES6新增)、symbol(ES6新增)、Object(包含null)、function

2、列举基本数据类型和引用数据类型?

基本数据类型:  numberstringbooleanundefinedbigint(ES6新增)、symbol(ES6新增)、 null;
引用数据类型: regExp、date、error、object、array、function...

3、为什么0.1 + 0.2不等于0.3? 怎么才能判断是相等的?

js采用64位双精度二进制数表示数字,第一位表示符号、后11位表示指数偏移部分、后52位表示尾数部分
所以最大安全数位 2^53-1,当超过最大安全数时会截取52位,造成精度丢失.
对整数部分转换为二进制是除二取余、逆序输出,对小数部分是乘二取整、正序输出,0.1和0.2在转换时会产生计算循环,然后被截取52位之后,再进行对阶,就有精度丢失,最后得出的树就不是0.3.
解决方法: Math.abs(0.1+0.2-0.3) < Number.EPSILON
        (0.1*10 + 0.2*10)/10 == 0.3

4、谈谈包装类?

比如: '1'.length
实际上它进行了以下的操作: let str = new String('1');
                          return str.length;
                          delete str;
包装类将基本类型包装为一个对象,从而可使用其上面的方法(包装类值只能读、不能写)
let str = '1'; 
str.color = 'clolor' 
console.log(str.color) = undefined(因为包装类在使用完之后就被销毁了)

5、简述原型链、实例对象、原型对象和constructor的关系?

1、通过构造函数new出来的对象成为实例对象
2、每一个实例对象上都拥有__proto__(null和undeifned除外) === 构造函数.prototype,这个属性也就是实例对象的原型.
3、__proto__.constructor指向对象的构造函数本身
4、__proto__这个属性指向的对象可以是另一个实例对象,而当查找某一个对象时,js会顺着这个__proto__属性进行链式查找,这也是为什么F.prototype = new F0()就形成了继承,最终必定会指向object(这也是js中万物皆对象的原因),这种链式指向形成的链表称为原型链
5、当访问某个实例的变量或者方法时,会顺着该实例的原型链查找值,未找到则返回undefined,这也就实现了继承
6hasOwnProperty()可以不检测原型链只检测自身是否用有变量, in则会检测原型链上

6、获取数组类型的方法?

Array.isArray()、
xxx instanceof Array(基于原型链查找,可以被伪造)、
xxx.__proto__.constrcutor.name === 'Array'(基于原型链的第一层)
Object.prototype.toString.call(xxx) === '[object Array]':区别于Object.toString()等,js对NumberArray等数据类型上的toString进行了重写,当调用toString时,原型链首先找到重写的函数所以不会调用在原型链上位于链表尾部的Object的原型方法,而Object上的toString则是具有获取数据类型的功能的.

7、Number和parseInt、parseFloat的区别?

1、相同点: 都是将传入的数据转换为number
         当传入整数字符时、返回的数据时相等的
2、不同点: parse...转换的数据中一定是包含数字的,而Number则可以将nullboolean类型数据转换
          parse...当遇到非数字时就会停止parse并且返回,而Number遇到非数字时回直接NaN
          parseInt表示对数据取整、parseFloat可以取小数和表示浮点数的e,同时parseInt还可以传入第二个参数,表示多少进制的数转换为十进制
         与它相对的还有toString,它传入的参数表示十进制转换为多少进制(这俩常用来做算法)
         parseInt内是小数,并且小数点后有连续6个及以上0时,会用科学计数法表示,选择接下来第一个不为0的数返回

8、会改变数组本身的方法?

reverse(),splice(),sort(),pop(),push(),shift(),unshift()
flat():(传入Infinity实现扁平化),
fill(val,begin,end):(数组内必须有内容,默认会改变数组内所有的数据为val)
copyWithin(target,begin,end);选中[begin,end)的复制到以target为起点的位置

9、==和===的问题?

===表示全等,如果是引用类型是地址相等,如果是基本类型则是数值相等并且不会作任何的类型转换
==问题比较多:
1、在和numberboolean类型进行比较时,其他类型会被转换为number类型,然后进行比较(null除外)
2null == undefined,null == null, undefined == undefined
3NaN和任何进行比较时都是false,包括自己
4、在进行==时默认会按序调用[Symbol.toPrimitive()](),valueof(),toString().

10、谈谈sort函数?

1、sort函数内部默认使用了ascii码顺序进行从小到大排序,0 = 0x30, a = 0x41, A = 0x61
2、sort内部可以传入函数,函数默认传入两个数组数据,第一个参数在第二个参数后,如果返回数值 >= 0不改变位置,否则交换顺序,并且把交换顺序的值与前面的值比较

image.png

11、谈谈Math.floor(),Math.ceil(),Math.round(),Math.trunc()的区别?

Math.floor()为向下取整,当出现小数是会往轴的左边寻找第一个整数
Math.ceil()为向上取整,和floor相反
Math.round()为四舍五入并且会忽略符号
Math.trunc()(ES6新增)去掉小数点后的数字,区别于parseInt?见7的最后一条

12、谈谈闭包?

闭包指的是在当前作用域下访问其他函数作用域的函数
闭包产生的原因?
    js通过执行栈来对控制函数的执行,每一个函数会根据自己的词法作用域(函数在全局的中的位置)通过outer指向当前所在的函数作用域或者全局作用域,并且拥有对它们进行访问的权利,而当函数执行完成后,函数从执行栈中退出,变量被释放,但是如果子函数访问了其中的变量,那么这些变量不会被释放,通过Closure的方式携带在函数体中,会保存在浏览器内存中.
闭包的使用场景?
    利用闭包的特性可以使用自执行函数,由于闭包的缘故,外部环境无法访问自执行函数内的变量,从而达到了封装的效果
    防抖和节流也利用了闭包的特性
    vue2.x中的data通过函数的形式return也是使用了闭包的特性,使得不会造成全局污染.

13、谈谈作用域和作用域链和执行上下文?

作用域有块级作用域、函数作用域、全局作用域.
作用域是作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。
作用域内的变量需要等到函数被执行时,根据传入的参数,this的指向等来创建执行上下文,从而进行访问
作用域链指的是函数会根据当前的词法作用域(函数的被创建时的环境)指向当前环境下的父作用域,最终指向全局作用域形成的链表
指向上下文栈控制了函数执行的状态,每当进行一个函数的作用域时,就会根据传入的变量创建相应的执行上下文(变量环境和词法环境构成),将它推入执行栈中,并且会携带作用域链的信息,通过作用域链的out顺着查找变量.
执行上下文只有函数级执行上下文和全局执行上下文,这些执行上下文内拥有词法环境,存放了letconst等块级变量,就像内部维护了一个小的栈用来push块级作用域.

14、构造函数时的返回问题?

在使用new获取实例时,函数内部会创建一个Object
并且函数内部this指向被创建的object(在函数内对this赋值,就是给object赋值)
1、当返回值为基本类型,实例对象为创建的Object
2、当返回值为引用类型,实例对象为返回值

15、在js中this的指向的问题?

1、在全局脚本下,this指向默认为window(node端指向global)
2、在直接调用函数的情况下,this也指向window(默认为window.fn()执行)
3、在构造函数时,使用new时,在函数内部设置的this指向被创建的object.(参照14)
4、使用箭头函数内部的this指向当前父作用域下的this,如果为undefined,则会不断往上查找,知道指向window
5、使用object调用函数时,this的指向为object对象(array也如此)
6、使用call函数可以改变this指向,第一个参数为this指向,后面的参数会作为实参执行
7、使用apply函数和call基本一致,不过后面参数需要以数组的形式传入
8、使用obj.settimeout函数,函数内的this指向全局,(strict指向undefined)

16、谈谈伪数组?

1arguments、使用queryelectAll的nodeList、使用getElementsByClass的htmlCollection都是伪数组
2、伪数组拥有下标和length,但是不能使用数组的方法(nodeList拥有迭代器,可以使用forEach方法)

17、伪数组转换为数组的方法?

伪数组为a
    1Array.from(a);
    2、[...a]
    3Array.prototype.concat.apply([],a)
    4Array.prototype.slice.call(a)
    5Array.prototype.map.call(a,item => item)
    6Array.prototype.reduce.call(a,(pre,item) => pre.concat(item),[])

18、实现单例模式(一个构造函数只有一个实例)

1function proxy(func) {
  let instance;
  return (args) => {
    return instance || (instance = new func(args));
  };
}
2、使用proxy代理拦截new操作符(参考三元大佬)
 function proxy(func) {
  let instance;
  let handler = {
    construct(target,args) {
      return instance || (instance = Reflect.construct(func,args));
    }
  }
  return new Proxy(func,handler);
 }

19、Date的API?

1、获取时间 a = new Date();
2、clone时间 b = new Date(a);
3、获取秒: a.getSeconds();
4、获取分钟: a.getMinutes();
5、获取小时: a.getHours();
6、获取星期: a.getDay();
7、获取日: a.getDate();
8、获取月: a.getMonth()+1(默认是0-11);
9、获取年: a.getFullYear();
10、格式化时间格式
    a.toLocaleDateString() (精确到日) a.toLocaleString() (精确到秒)

20、正则表达式?

1、new RegExp()或者/表达式/ 创建正则表达式(在new中的表达式使用通配符时需要使用\\d转义)
2、通配符:
    \d(0-9) 
    \w(字母数字下划线)
    \s(空格)
    \b(边界)
    .(匹配非回车的任意数字)
    ^a(表示匹配需要以a开头)
    b$(表示匹配需要以b结尾)
3、量词:
    + 出现1次及以上
    ? 出现0次或1次
    * 出现0次及以上
    {n} 出现n次
    {n,} 出现n次及以上
    {n,m} 出现n次到m次
5、捕获: 在串中使用小括号时,小括号内被匹配到的串为被捕获的串,可以在后面的匹配中使用\(n)或者RegExp.$(n) (n表示被捕获的次数),如图1
   分组:
    如图,$&表示最后一次匹配中的串,$'表示除去匹配串的剩余串,$_表示用于match的字符串
6、断言: 用于结合被匹配串头和尾部分进行匹配
   let a = '123456';
   a.match(/(?<=123)456/); (?<=表示前向断言,表示被匹配串456前面为123)
   a.match(/123(?=456)/);  (?=表示后向断言)
   a.match(/(?!=123)456/); (?!=表示负向断言,456前面不能为123)
   a.match(/123(?!456)/); (?!表示123后面不能为456)
7、string.match(RegExp),获取匹配的串,返回数组,如图2
8Reg.test()返回bool确定是否匹配成功
9String.search(Reg)
10、string.replace(Reg,(被匹配的,捕获的?,beginIndex,string))
11Reg.exec(string) 当Reg有/g时,匹配的到的string修改Reg.lastIndex,下一次匹配的时候会从lastIndex以后开始匹配,所以多次匹配时需要将Reg的lastIndex置0

图一 image.png
图二 image.png

DOM和BOM篇

1、谈谈dom?

  dom指文档对象模型,html dom会被结构化为dom Tree,开发者可以通过document来对html结构进行增删改查的操作.

2、dom中的增删改查?

  增: 1、document.createElement(标签) 
      2、document.cloneElement(ele)
        document.appendChild(单个ele) || document.append(多个ele)
      3、document.createDocumentFragment()
          创建一个文档碎片,并且可以往其中添加dom节点,其可以被当作dom节点添加到其他element中.
          结构化的实现了dom的批量操作,减少重绘次数,提高性能
      4、innerHTML和innerText;

      增加属性: 1、ele.setAttribute(name,val);
               2、let node = ele.createAttribute(name);
                  node.value = xxx;
                  ele.setAttrubuteNode(node);
  删: 1、移除自己 childeNode.remove();
      2、移除子元素 parentNode.removeChild(childNode);
     
     删除属性: 1removeAttribute(移除属性);
              2removeAttributeNode(移除节点);
  查: 1queryselect(选择器)(返回查到的第一个).
        queryselectAll(选择器)(以数组形式返回全部)
        document.getElementBy...
      2、查找类: ele.className(以字符串空格分割形式返回class)(只读)
                ele.classList(以数组形式返回,DOMTokenList)
      3、查找属性: ele.getAttribute
                 ele.getAttributeNode
                 ele.attributes

3、element的原型链?

    htmlDivElement->htmlElement->element->node->eventTarget

4、描述node,element和node的关系?

    1、element是node的子集
    2、如图
    3、ele.childrens和ele.childNode的区别也就是...node会加上注释和text等,而children则只有ele

image.png

5、获取如下html结构中的所有element,elementCount,text,textCount?

    这里根据传入的nodeType来获取相应的数据
    注意 text也包括换行空格等,并且是遇到下一个nodeName !== text之前的都属于一段text

image.png image.png image.png

6、阻止冒泡的方法?

    e.stopPropagation()(W3C标准,在IE中无法使用) || 
    e.cancelBabel = true(在IE中使用,chrome等目前也支持);

7、阻止默认事件的方法?

    e.preventDefault() ||
    e.returnValue = false;
    return false;

8、on + type和addEventListener(type,func)的区别?

    on + typed的事件在触发type时,只会触发一个事件,并且后定义的事件会覆盖先定义的事件.
    addEventListener可以多次定义,并且先定义的会先触发
    addEventListener设置第二个参数具有捕获的功能(默认为false)
    并且当一个ele同时设置了冒泡和捕获时,会先由外向内触发捕获,再由内向外触发冒泡
    触发优先级: 捕获 > addEventlistener冒泡 > onclick冒泡

9、drag and drop API ?

    触发drag事件需要现在ele上添加draggble=true
    一、应用于被拖拽元素的事件
    -   ondrag 应用于拖拽元素,整个拖拽过程都会调用
    -   ondragstart 应用于拖拽元素,当拖拽开始时调用
    -   ondragleave 应用于拖拽元素,当鼠标离开拖拽元素是调用
    -   ondragend 应用于拖拽元素,当拖拽结束时调用

    二、应用于目标元素的事件
    -   ondragenter 应用于目标元素,当拖拽元素进入时调用
    -   ondragover 应用于目标元素,当停留在目标元素上时调用
    -   ondrop 应用于目标元素,当在目标元素上松开鼠标时调用
    -   ondragleave 应用于目标元素,当鼠标离开目标元素时调用

    浏览器默认会阻止ondrop事件:我们必须在ondrapover中阻止默认行为
    
    可以在dragSatrt时利用e.Transfer.setData(name,value),向目标元素的drop事件中,传递数据
    

10补充、mouseEvent的区别?

mousemove 表示鼠标在目标元素上移动的事件.
mouseDown 表示鼠标在目标元素上按下
mouseup 表示鼠标在按下后抬起
mouseEnter 表示鼠标进入,不会被冒泡触发.
mouseLeave 表示鼠标移出,不会被冒泡触发.
mouseOver 表示鼠标进入本元素或子元素,会冒泡
mouseOut 表示鼠标移出本元素或子元素,会冒泡

10、window.onLoad和window.addEventListener('DOMContextLoaded)的区别?

   DOMContextLoaded是在dom Tree被parse完成后触发的回调(浏览器parse过程中如果有js脚本,则会先加载脚本,如果脚本前有css文件,则会先加载css文件).但是图片等文件是不会阻塞回调的
   onLoad是在也全部加载完成后才触发的.
       10.1、为什么通常需要把js文件放在底部,css文件放在头部?
           首先浏览在parse过成中需要等html文档(包含script)都加载完成才出发domContextLoaded
           所以不会改变domContextLoaded的触发事件.
           但是放在底部时,dom Tree已经构建完毕,此时可以获取dom Tree上的所有节点.
           这里发现很多文章说到dom的阻塞问题,但是我实践后发现,dom的渲染是在整个dom Tree构建完毕后才进行的,所以script放在哪里并不会影响首屏的展示.

11、history API?

    history API允许在不刷新页面的情况下,根据请求来的数据,局部改变页面的显示.
    history.go(val): 通过输入的val前进或者后退页面.
    history.back() === history.go(-1)
    history.forward() === history.go(1)
    history.pushState(val,title,url)(val表示向栈中传入,当前历史记录的参数,再下次回到页面是可以通过history.state访问,title没啥用,url表示下次进入条记录时,访问的url,url有同源限制,不指定默认为当前URI)
    可以通过window.onpopstate来监听history的变化,从而发起请求.

12、location API?

    1、loacation.href表示当前所在页面的URI,并且可以通过设置来改变当前URI
    2、locaton.assgin表示跳转到指定页面,但是可以back回来
    3、location.replace是替换当前页面,不会back
    4、location.hash指的是改变URL中的锚点位置,可以通过监听hashChange的改变来发送对应参数的请求,从而实现不刷新页面就更改网页内容的功能

13、 screen API?

image.png

14、谈谈offset、client、scroll?

    1、offsetWidth和offsetHeight为获取元素的宽度和高度(width + padding + border)(border-box则为width)
    2、offsetTop表示当前元素的上边框(border外边框)距离视窗顶部的距离
    3、clientHeight表示height + padding
    4、clientTop表示border的大小
    5、scrollHeight在当前元素上实现了scroll时,显示子元素的offsetHeight
        如果只是普通的元素则显示clientHeight
    6、scrollTop表示当前元素视窗顶部距离 - 滚动总页面的顶部的距离
    7、获取页面超出浏览器顶部的大小:var top = document.documentElement.scrollTop(有声明文档类型) || document.body.scrollTop(没有声明文档类型);(兼容写法)

ES6篇及以上

1、let和const?

  letconst的出现是为了解决var导致的变量提升问题,如下
  var a = 123;
  function f() {
      console.log(a); undefined
      if(0) {
          var a = 456;
      }
      console.log(a); undefined
  }
  由于变量提升的的问题,在f中的a覆盖了在外部作用域下的a变量.这是由于js没有块级作用域引起的,这也是constlet出现的原因
  在执行上下文中,var声明的变量和函数都被放在变量环境中,而用letconst声明的变量被放在词法环境中,每一个块级作用域会作为一个整体被放入词法环境中,并且在括号结束后,在词法环境中的块级作用域会出栈,词法环境就类似一个小型的栈.会按照如图的顺序去查找变量.
  在块级作用域下letconst的创建会被提升,但是初始化(为undefined)并不会提升,这时如果访问就会造成暂时性死区,报错
  var创建的变量创建和初始化都会被提升.
  function创建的变量创建和初始化和赋值都会被提升(如果在一个块级作用域中,则赋值不会被提升)
  let修饰的词指向可变.
  const修饰的值基本类型不能改变,引用类型地址不能改变.
  

image.png

2、解构赋值?

解构赋值的使用方式?
    1、常见的解构赋值、let {a} = {a: 123}
    2、解构赋值的默认值: let {a = 3} = {b: 123}  a = 3;
    3、混合解构赋值: {a: }
        let {
          b: {name: {age}},
          c: [val,[val1]]
        } = {
          b: {
            name: {
              age: [123,235,[123,21]]
            }
          },
          c: [123,[123,45]]
        }
        console.log(age); // [123,235,[123,21]]
        console.log(val,val1); // 123 123
  2.1: 交换两个变量并且不使用第三个参数?
      let a = 1;
      let b = 2;
      [a,b] = [b,a];  a: 2, b: 1

3、字符串的扩展?

  string.includes(str) 判断是否包含str
  string.statrtWith(str) 判断开头是否包含str
  string.endWith(str) 判断结尾是否包含str
  string.padStart(number,string) 返回扩展当字符串长度到number,如果长度不到就在头部用stringstring.padEnd()...
  string.repeat(n)返回重复n次的字符串
  模版字符串: 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能,比字符串并且更简洁.

4、数值的扩展?

   开立方根: Number.cbrt(val), Math.pow(val,1/3) val ** (1/3)
   直角三角形斜边: Number.hypot(val,val1)

5、箭头函数?

   使用() => {return val}形式,更加简洁,并且当只有一个返回值时可以使用() => val(对象要加({})上下文相关文法)
   箭头函数中不会初始化this,调用的this找到作用域链中最近的this不为undeifned的值,如果没有则为undeifned.
   箭头函数不存在arguments,要以数组的形式拿到实参可以使用(...args)

6、数组的扩展?

   1、array.from(arr,func)和array.map(func),都可以用遍历数组,arr.from可以实现伪数组向类数组的转换.
   2、array.of(val,val...)用于获取一个数据为参数内的数组.
     它解决了new Array中传入一个参数时返回长度为n的空数组.
                     传入两个及以上参数时返回以参数为item的数组
   3、array.fill(val,begin?,end?)表示填充val进数组,只传val,则全部为val,否则填充指定长度
   3Array中出现的高阶函数: 
      forEach((item,index,Arr)=>{},thisArg) 没有返回值,无法用return退出,只能throw
      map((item,index,Arr)=>{},thisArg) 有返回值,遍历数组时的return代表返回数组的item
      filter((item,index,Arr)=>{},thisArg),创建一个空的item 当returntrue时,返回被访问的item
      every((item,index,Arr)=>{},thisArg),当返回值有false时直接false,当返回值都为true则返回true
      some((item,index,Arr)=>{},thisArg),当有一个returntrue时则返回true,否则返回false
      reduce((prev,item,index,Arr) => {},initialValue?),归纳函数.initialValue不设定时,prev默认为第一个item,且index从1开始执行,当设定时,index从0执行,并且将返回的return下一个循环中的prev,直到结束返回prev.
      findIndex((item,index,arr) => {},thisArg),返回returntrue的indedx
      find((item,inex,arr) => {},this.Arg),返回returntrue的item
   4、迭代器: entries(), keys(),values()
       entries()用于遍历,以 for( [index,value] of arr.entries()) 的形式返回,
       keys和values分别以返回index和value
   5、includes 对比indexOf 语义话,可以检测NaN,不可以检测+0-0;

数组扁平化的方式?

let a = [[[123],[123,123,456],[21]],[12,[212]],[212212]];
1a.flat(Infinity);
2while(arr.some(Array.isArray) arr = [].concat(...arr);
3a.toString().split(',').map(item => item-0)
4、JSON.stringify(a).replace(/\[|\]/g,'').split(',').map(item => item-0)
5、function f(arr) {
     return !Array.isArray(arr) ? arr : arr.reduce((pre,item) =>
       pre.concat(Array.isArray(item) ? f(item) : item),[])
   }
6、let ar = [];
    function f(arr) {
      for(let i = 0;i < arr.length;i++)
      {
        Array.isArray(arr[i]) ? f(arr[i]) : ar.push(arr[i]);
      }
    }

7、对象的扩展?

   1Object.is(val,val1),判断两个值是否是全等的.
       IndexOf()不可以检测+0-0, NaN
       includes()不可以检测+0-0;
       (NaN和NaN应该是相等的,+0和-0应该是不相等的(一位存储符号))
   2Object.assign(obj,obj1,obj2),拼接对象,将obj1拼接到obj,(被拷贝的对象中的属性是浅拷贝),不会拷贝原型链上的属性
   3、迭代器: Object.entries(),Object.values(),Object.keys() (如果使用Arr作为参数时,属性名会变为string类型,因为对象的属性名是string类型)

8、Set?

 新增数据结构、可用于去重复,类似Array的升级,
 arr->set
     new Set(arr);
 set->arr
     [...set],
     Array.from(set)
 方法: add() delete() size() clear() has(),forEach(),entries()..

9、Map?

 以健值对的形式存储,但是key值可以是任意类型的(对象的key一定时string)
 二维数组->map
     let a = [['1',1],['2',2]]
     let map = new Map(a)
 方法: set(key,value) has() delete() clear() size(),forEach(),entries()..

10、weakMap和weakSet?

 基本和map和set一致,但是会被gc垃圾回收忽略,当只有weakMap中有引用时,垃圾回收会忽略该引用,防止内存泄漏问题.

11 ES6新增内容?

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const
  • symbol

12、 ES7新增内容?

  • Array.prototype.includes;
  • ** 幂运算符号

13、 ES8新增内容?

  • async,await
  • Object.entries(),Object.keys()..
  • String.prototype.paddingStart...
  • Object.getOwnPropertyDescriptors(obj) (返回对于该对象内所有属性的描述信息)

14、 ES9新增内容?

1、异步迭代 
 async function process(array) { for await (let i of array) { doSomething(i); } }
2、Promise.finally()
3、展开运算符
    let a = [1,2,3]
    let [d,...b] = a (ES6的扩展运算符,合并)
    let c = [...a,1] (ES9的展开运算符,展开)
4、正则表达式的分组 (?<word>123) 通过match.group.word获得
5、正则表达式的前向断言(?<=) (?<!)

15、 ES10新增内容?

1Array.prototype.flat(n) flat函数根据n对数组进行深度遍历,使遍历到的元素和子元素合并,n设置为Infinity时,可以实现数组的扁平化.根据该特性,还可以实现数组去空值
2Array.prototype.flatMap() 在map函数的基础上增加一层flat,每个item映射为一个新的数组,最后这些新的数组会被打扁平一次.
 let a = ['abc sd dd dasdasd asd a','','abd b'];
 console.log(a.map(item => item.split(' '))); 
 // [["abc", "sd", "dd", "dasdasd", "asd", "a"],[‘‘],["abd", "b"]]
 console.log(a.flatMap(item => item.split(' ')));
 // ["abc", "sd", "dd", "dasdasd", "asd", "a", "", "abd", "b"]
 flatmap就是在map()的之后执行了一次flat()

 var arr = [1, 2, 3, 4];
 arr.flatMap(x => [x, x * 2]);
 // is equivalent to
 // 下面的方式是低效的,每次都需要创建一个将被gc的数组,并且还需要将累加器数组添加到新数组
 arr.reduce((acc, x) => acc.concat([x, x * 2]), []);
 // [1, 2, 2, 4, 3, 6, 4, 8]
3、新增了trimStart()和trimEnd(),去除首尾空格
4Object.fromEntries()与Object.entries()作用相反,传入一个map或者二维数组,最终获取一个对象
 let a = new Map([[undefined,5],[3,5]])
 let b = [['1',2],['2',3]]
 console.log(Object.fromEntries(a)); //{3: 5, undefined: 5}
 console.log(Object.fromEntries(b)); //{1: 2, 2: 3}
5Symbol.prototype.description表示获取Symbol的描述,以前获取描述只能通过转换为字符串然后比较 let a = Symbol('12')
        a.toString() === "Symbol('12')"
        a.description === '12'
6Function.prototype.toString,可以精确获得换行符和注释.
7、修改catch绑定: try{} catch(e) {} => try{} catch{} // 简化了有时不需要获得异常.
8、新增基本类型bigInt: 可以表示number超过2^53-1的数,使用BigInt(n)或者valn表示,并且bigInt无法和number类型进行运算,但可以和同类型的进行运算,可以使用boolean转换等.

16、谈谈class?

class为ES6新增内容,它更好的解决了在es5上使用函数和创建实例混乱的问题,在实例化对象时,会调用constructor函数.
class必须先定义后时候,不会进行提升(区别function)
class上定义的函数默认的propery设置了enumable为false,不可被枚举
class可以实现extend继承.super(arguments)执行继承的类.
this必须在super()之后调用,super()中初始化了this.
super()在读的时候指向继承类,在写的时候指向当前类.

17、页面加载的整个过程?(考察深度思考能力,面试加分)

1、首先当输入地址时,浏览器会根据关键字提供访问历史记录.
2、当跳转时,浏览器会根据输入的url的合法性,选择以输入为关键字进行搜索引擎搜索或者发送请求.
3、构造请求行. 请求类型,HTTP版本,地址构成.
4、浏览器为了安全性问题,对渲染进程有沙箱机制,渲染进程没有内存和发送请求的权限,会通过进程间通信(IPC)通知浏览器主进程,浏览器主进程通知网络进程发送请求.
5、网络进程检查本地缓存.
  本地缓存类型: sevice worker(运行在浏览器后台的线程技术,使用请求拦截技术,需要HTTPS协议),内存缓存(memory cache 速度快,存储时间短),硬盘缓存(disk cache 速度慢,存储时间长),push缓存(HTTP2,只用于回话,回话结束被释放.)
6、DNS解析,浏览器会根据输入的域名,进行IP转换.通过本地域名服务器->权威域名服务器->根域名服务器的方式迭代查询.IP会拼接端口(默认为80或者443).
7、和服务器建立tcp连接(三次握手)或者复用长连接(https建立TLS连接,交换密钥和数字签名)
8、构建请求头和请求体
9、当本地缓存不存在时,请求发送,如果存在代理服务器,则会判断代理服务器是否过期,不过期,就用代理服务器的缓存.
10、过期,发送给原服务器,原服务器拿到请求的if-midified和midifiy-since(对应响应发送的etag和last-midified-since)最后修改时间和文件的hash值,服务器检查强缓存,是否文件真的过期,如果没有过期,发送304使用本地缓存,过期则发送请求,并更新文件.
11、网络进程拿到响应时,会交给浏览器主进程数据,根据响应的content-type做出不同的处理,如果content-type为text/html类型,主进程通知渲染进程,要干活了,渲染进程和网络进程进行管道连接.发送commit给浏览器进程,确认提交,表示准备好接受文件.
12、渲染进程拿到html文件,首先parseHTML文件,
    1、html标记化以及建立domTree.首先建立根为document的树.然后parsehtml标记,每个标记会对应一个结束标记,当遇到开始标记时,接下来的标记会被注入开始标记内,当遇到结束标记时,该标记闭合,接下来的标记会被迭代的建立到新生成的子树上.并且html标记是上下文相关文法,具有一定的容错机制.
    2、对css文件进行标准化和格式化.
        2.1、(css文件包括行内样式表,内部样式表和外部样式表.)
        2.2、对css文件内的单位格式化,em->px等.
        2.3、生成cssSheetLists.
    3、利用css层叠和继承的特性将css样式表结合到dom Tree中.
    4、建立布局树,(head子标签和dispay为none的标记不会参与布局),根据定位和布局格式对元素进行布局.(一个小位置变化就会造成重新布局、回流reflow)
    5、建立图层树、根据z-index、opaicty、rgba,filter等显式合成层.
        5.1(使用3D transform、canvas、vedio、will change或生成合成层、合成层会提升为单独的一层,并且可以利用GPU加速渲染,但是如果提升的层拥有上层时,为了保证层的位置关系,上层的层也会被提升为合成层.如果层数过多会造成层爆炸(可以使用z-index让合成层为最高层解决))
        5.2生成绘制列表,用于绘制(在f12+layer中查看)
   6、绘制操作由渲染进程的合成线程完成.在合成线程中,会对图层进行分块的操作,这样做是为了减少首屏加载速度.
     6.1、由于图块进入GPU内存,考虑到即使部分部分图块进行加载也会拖慢显示时间,所以浏览器才用了首先显示低分辨率图片的策略,当绘制完成后进行替换.
     6.2、在合成线程中维护了一个栅格化线程池,用于将图块转换为位图.(生成图块实际是利用了合成线程加速的原理)
   7、显示器显示: 栅格化完成后,合成线程生成DrawQuad的指令给浏览器主进程.
               浏览器进程的viz组件在接收到这个指令后,把页面内容发到内存中再发送给显卡.
               显示器一般都是60HZ(1s60张照片)的刷新频率,而图片是通过前缓冲区和后缓冲区中获取的,前缓冲取区用来显示图片,后缓冲区则是获取浏览器发送的图像.不断交替

18、回流和重绘和合成?

1、回流是当页面的元素位置或者大小发生变化或者元素的变化时,会使domTree结构发生变化,从而使后续操作重走一遍.
    引起回流的操作: 1、对元素的大小或者定位进行改变.
                  2、对节点进行增加或者删除.
                  3、使用document.computedStyle
                  4、读写offset client scrool等内容时,浏览器为了获取这些值,需要回流
2、重绘是当元素的样式发生变化,几何位置未发生改变时,会造成重绘,由于布局未发生变化,从而只需要重新计算样式和生成绘制列表.
    引起重绘的操作: 1、改变了元素的样式,但是没有改变元素的几何位置的操作.
回流一定触发重绘,重绘不一定触发回流
3、合成指的是被单独提升为合成层的元素发生变化时,不会触发上面,而是直接合成,利用GPU加速
    在合成的情况下,会直接跳过布局和绘制流程,直接交给合成线程.从而利用GPU加速生成.
提高性能的方式: 1、对样式的读写分离.
              2、避免频繁使用style,多使用class
              3、对resize/rescroll进行防抖/节流的处理
              4、使用createDocumentFlagment对节点批量添加
              5、使用will-change: tranform 使浏览器为其生成为合成层,当发生变化时,可以利用GPU加速
        

19、消息队列和宏任务与微任务?

  • 首先单线程使渲染主线程会有条不紊的执行代码逻辑,但是如果需要处理中途发生的事情(比如事件点击)时,直接的单线程就无法操作了.

  • 所以在渲染主线程中,维护了一个消息队列,当其他线程发送消息时,会首先由渲染进程中的IO线程处理消息,再把对应的消息放入消息队列中.

  • 并且队列遵循先进先出的策略,先进入的任务会先被取出然后交给渲染主进程执行,而中途如果有其他线程提交信息时,就会被放入消息队列中.

  • 消息队列中的任务类型有: 输入事件(鼠标滚动,点击等),微任务,setTimeout,文件IO,webSocket,还有执行js、parseHTML,执行动画等.

  • 如何安全退出? chrome采用了当要退出页面时,会在渲染主线程上设置一个退出哨兵,每次执行完事件时,检测是否有该哨兵,如果有就中断任务,退出.

  • 宏任务就是消息队列中的任务,每个宏任务上又维护了一个微任务队列,这些任务会在本次宏任务执行完成之后,下一次宏任务执行开始之前调用.

  • 这里再说一下setTimeout,它是设置了指定的时间然后触发的,由于它的特殊性,它实际上是存在于延时队列中的. 首先它可以想象为会被创建一个为一个对象,在对象中有任务开始时间和到期时间以及回调函数. 每次调用时都会创建对象并且被放入延时队列中,在每次执行完任务时会检查延时队列中的对象是否到期,到期就执行. 也可以使用clearTimeout()在还没执行时,对定时器执行清除操作,实际就是通过定时器的id查找并且删除. 所以如果单个宏任务执行时间过久就会导致settimeout延时的问题 所以在执行动画时,由于对实时性要求高,最好使用requestAnimationFrame(rAF),它由系统决定调用的时间,在每次页面刷新时调用,如果页面未激活,rAf也会停止执行,节省开销.在回调函数中会传入当前时刻的参数,可以通同对参数的判断递归调用该函数. V8引擎会维护一个微任务队列.在微任务被创建时被放入,并且宏任务结束之前会把当前微任务队列清空. 微任务: promise.then,MutationObserver.observe(是Mutation Event的升级版,异步触发,可以不用每次变化都同步触发,而是用异步的方式,在主函数之外执行,这样就解决了性能问题,又为了保证实时性,使用了微任务的方式执行)

20、回调函数?

回调函数就是通过参数传递的函数.
同步回调: 在主函数内返回之前执行的函数称为同步回调.
异步回调: 在主函数外部执行的函数称为异步回调
每个任务都有自己的调用栈
同步回调就是在上下文中执行函数.
异步回调可能是在自身的微任务队列中.
       也可能是被加入到了消息队列

21、谈谈promise?

首先浏览器是单线程工作的(v8和渲染引擎都在渲染进程主线程),这也是异步编程出现的原因,如果在发起请求或者进行延时任务时采用同步的策略就会导致页面卡顿,而浏览器是由事件循环驱动进的,这时为了避免发生卡顿,就采取了异步的策略(具体在后面展开讨论).
promise的出现解决了回调地狱的问题
在promise没有出现前,处理需要回调的判断时会出现两种情况,再对这两种情况判断时,又会出现两种,之后以2^n增长,产生代码量巨大,不易维护的问题.而prmise则很好的解决了问题.它采用了三种方式.
1、回调函数延时绑定.
    在回调地狱中,维护困难很大一部分原因是由于对回调函数的嵌套处理以及所有的嵌套操作都放在了回调内部.而promise则将问题的处理放在then方法中.
2、返回值穿透.
    2.1、promise可以通过resolve和reject对需要处理的值穿透出去,作为回调函数的参数调用.
    2.2、在处理回调函数时,如果还存在判断处理,需要进行下一步操作,就可以将返回值继续穿透.链式调用then方法进行处理.
3、错误冒泡,当遇到reject的情况时会抛出错误,而错误可以被catch捕获,然后对返回值统一判断处理,从而不用在中间过程中处理错误的情况.简化了代码
通过这些方式就很好的处理了嵌套层深,不易维护.代码量复杂的问题.
promise的status有: resolved,rejected,pending分别对应执行resolve,执行reject,都还未执行(可能放在定时器中等)
promise.then: 
  then中内置两个回调函数,分别为fulfilled和rejected,分别对应resolve和reject的处理方式,并且函数内参数也为Promise的resolve和reject传入的参数.fulfilled默认为value =>value, rejected默认为err => throw err;
  then内部函数的回调都被在微任务中执行,如果promise的状态为pending,那么promise的两个回调函数都会被放在promise内部,当状态发生变化时,遍历resolvedArr和rejectedArr执行.

22、 async和await?

22.1、在使用promise的过程发现,虽然实现了线性调用以及错误的冒泡,当前当嵌套过深的时候,会造成then调用过深,最好的实现的方式是采用同步的书写格式,实现异步不阻塞主线程,但是阻塞函数之后代码执行.
22.2、而generator生成器就实现了这种功能,genatator函数(ES6语法)有funtion*和yield组成.它拥有中断函数的权限(yield),而父协程(后面会说)拥有继续执行函数的权限.
这种技术就被称为协程.
再说一些线程和进程吧,进程是程序运行和资源分配的基本单位,线程是任务调度和执行的基本单位,线程时运行在进程之上的,并且进程资源的,一个进程上可以拥有多个线程,进程是有线程来管理和启动的.进程中任意一个线程中执行出错都会造成整个进程发生崩溃,当一个进程关闭时,操作系统会回收进程资源.进程之间的内容相互隔离,进程之间的通信依赖IPC机制
那么线程实际上就是存在于操作系统之上的,是属于内核态的,而协程则是在运行在线程中的,可以看做线程中的一个个任务,但是一个线程中只能有一个任务内运行,如果A协程在运行,而B协程要运行就需要A协程交出线程控制权,而对B协程发起执行指令的称为父协程.
这时就会出现一个问题? 一个协程有自己的调用栈(表示一个任务,上面有许多子任务),在切换的时候是如何切换呢?
    1、协程之间不是并发执行的,是通过暂停启动执行的.
    2、在调用协程,并通过yield进行权限交接时,会保存当前函数的调用栈信息,当下次再次调用时next(),会恢复当前协程,当协程中遇到return时就会结束当前的协程.
在function*中,yiled会中断函数,并将yield后面的内容以对象的形式返回出去
    {value: 内容,done: 函数是否执行完成}.
    并且在父协程中调用协程时,可以使用next(val),其中的val会作为返回值用于赋值
        function* gen() {
           let a = yield 100; // 3 遇到yield 退出并返回 100
           console.log(a); // 0。a被next的返回值赋值
        }
        let gen0 = gen(),获取迭代器对象. 1
        gen0.next(); // 执行函数 2
        gen0.next(0); // 继续执行迭代器,并将0作为yield的返回值返回.
    再看一个:
      function* genarator() {
          let value = yield fetch('http://localhost:8080');
          console.log(value); // response 在执行的时候,父协程已经将拿到了异步获取的数据.
          实现了异步阻塞当前函数,但是不阻塞主线程.
        }
        let gen = genarator(); // 1 获取迭代器对象.
        // **
        function f(res) {
          return gen.next(res).value // 用于继续协程执行
        }
        f().then((res) => { // 控制继续执行协程的时刻
          f(res); // 当拿到yield的promise对象,并且使用then监听改变时,再继续执行.
        })
        ** //
        或者
        // **
        function run(f) {
          const next = (f) => {
            let nt = f.next();
            if(nt.done) return;
            nt.value.then((res) => {
              f.next(res);
            })
          }
          next(f);
        }
        run(gen);
        ** //
        或者使用co库.

22.3、我们发现协程已经让我们代码的简略性和可读性大大的提高了,但是还有个问题,就是对于协程的再执行的操作,需要父协程配合.ES8推出了async和await就很好的解决了这个问题(切入正题)

asyncawait是一种结合promjse和generator的技术
async的定义: 表示该函数时一个通过异步执行并且隐式返回一个promise对象的函数.
await就是比较类似yield的作用了,只是在yield的基础上包装了一层,在async函数上,遇到await的时候,会使用Promise.resolve(await后面的内容),并且会中断当前函数的执行,交出线程执行权,而在父协程中会使用promise.then对返回的promise的状态进行监听(放入微任务队列.)
当当前宏任务执行完成后,发现微任务队列中还有一个臭小子,就会拿出执行then(then函数的fulfilled回调默认为value => value)所以promise的返回值就会作为参数传入async fn().next()中,然后继续函数的执行,如果又遇到await,则又被放入微任务队列(宏任务执行完成之前,微任务队列会被清空,所以会被线性的执行)
async function() {
    let val = await fetch('http://localhost:8080')
    console.log(val) // response
}

到这里async await已经结束了,讲了这么多,不知道你懂了没,反正我是懂了,看自己懂了真的有点开心的的,😁这个内容我骑马看了5遍,我现在已经彻底的懂了,希望你结合资料不断的精读.奥利给💪

23、V8引擎工作原理?

首先、js是一种动态语言(我们把在执行之前就确认变量类型的原因称为静态语言,反之则为动态),并且时一种弱类型语言(无法通过隐式类型转换的语言被称为弱类型语言,反之为强类型语言).
原始类型的数据是被存放在栈中的,而引用类型的数据被存放在堆中,而栈中的引用类型的数据指向的是堆空间的地址.
分出栈空间和堆空间的原因就是如果栈空间内的数据量较大,那么就会让切换执行上下文变得缓慢,同时,
但是闭包对象如果是基本类型在栈执行结束后就会被销毁吗?
    闭包对象会以clousion的形式存放在堆内存中,而引用的闭包则指向堆内存的地址.
js引擎还通过移动ESP指针的方式来控制对执行上下文的释放,当函数退出时,ESP指针会向下移动,此时退出函数的执行上下文在下一个函数进入时将会被覆盖.
接下来就说一下js中的垃圾回收机制?
    在c和c++中,垃圾回收需要手动free,但是js、java等语言拥有自己的垃圾回收器,它们会自动化的进行垃圾回收,接下来就说一下V8的GC
    V8的垃圾回收遵循代际假说和分代收集两种原则.
        代际假说: 1、一般存储在堆内存中的数据的存储时间都比较短
                2、不死的对象存活的时间更久
    根据这种规则,V8引擎的堆内存被分为了新生代和老生代,新生代的数据存活时间短,老生代的数据存活时间长.
    也由于这种特性,使得新生代内存的大小只有1-8M,而老生代的内存就大的多了.
    垃圾回收器的工作流程是:
        1、对非存活对象和存活对象进行标记.
        2、对非存活对象进行清除
        3、整理内存碎片,在频繁的进行垃圾回收时,就会造成内存不连续的问题,使得内存空间的浪费.这时就需要整理碎片,减少内存浪费.
    新生代的GC: 使用scanvange算法,将堆内存分为对象区域和空闲区域,在对象区域中,对于非存活对象进行标记,然后将没被标记的放入空闲区域中,将标记的清除,然后交换两者的身份.这种方式也使得数据变得连续.
    老生代的GC: 如果老生代的内存就不能像新生代一样了,这会浪费内存空间,而是采用标记清楚的方式,将不再被引用的堆内存数据标记,然后清除,而解决内存不连续的方法就是将不连续的内存往一个方向移动.
    全停顿: 由于浏览器的单线程原因,在执行垃圾回收时,会占用线程,等到回收完成后再执行,这中行为称为全停顿. 而如果一次性进行垃圾回收那会非常的耗时.
    增量标记: 解决垃圾回收的耗时问题,将垃圾回收的任务分为多个小任务,与应用逻辑交替进行执行.
 在说一下编译器和解释器?
     编译型语言在程序执行之前,需要经过编译器的编译,生成二进制文件,而且编译器会保留二进制文件,在下次执行时,可以直接执行该二进制文件,而不需要重写编译.
     解释性语言则是在程序执行的时候,动态的对程序进行解释和执行.
     编译器: 代码(经过词法分析和语法分析)->AST抽象语法树(词义分析)->中间代码(代码优化)->二进制文件->执行
     解释器: 代码(经过词法分析和语法分析)->AST抽象语法树(词义分析)->字节码->解释执行.
     
     V8引擎首先会进行分词操作(词法分析),将每一行的代码生成一个个不可再分的token.
          然后进行解释(语法分析),将生成的token转换为AST树,如果转换不符合语法规则就会抛出异常.
          这就是AST的生成过程,编译器和解释器的执行都依赖于AST,而不是源代码,Babel就是实现了将ES6的AST树转换为ES5的AST树,从而解决ES6的兼容问题.
          在生成完AST树之后,就可以根据AST树来创建执行上下文了.
          然后就是生成字节码的过程了,解释器对AST树内容生成特定的字节码,然后解释执行,而字节码是介于AST和二进制码之间的一种码,需要通过解释器转换为机器码然后执行,那为啥不直接生成二进制码呢? 原因是二进制码的太大了,导致了内存消耗严重的问题,
          接下来就是执行代码了,解释器会根据字节码逐行解释执行,并且对于重复出现的代码通过编译器生成热点代码(机器码),用于代码优化,再遇到重复代码时,不用再解释,这种编译器和解释器一起使用的技术也被称为即时编译(JIT)
 启发: 避免大的内联脚本,因为在解释html时,编译和解释代码会占用主线程.
      减少js的文件容量,增快传输速度,减少内存消耗.

24、AJAX篇

AJAX是一种异步请求数据,并且不刷新页面,从而大大提升用户体验的与后台交互的技术.同时,请求的是数据,而不需要重复的请求html页面,所以也减少了网络带宽.
XHRHttpRequest:
    function GetWebData(URL){
      // 1:新建XMLHttpRequest请求对象
      let xhr = new XMLHttpRequest()
      // 2:注册相关事件回调处理函数 
      xhr.onreadystatechange = function () {
          switch(xhr.readyState){
            case 0: //请求未初始化
              console.log("请求未初始化")
              break;
            case 1://OPENED 请求被创建
              console.log("OPENED")
              break;
            case 2://HEADERS_RECEIVED 收到请求头
              console.log("HEADERS_RECEIVED")
              break;
            case 3://LOADING  // 加载中
              console.log("LOADING")
              break;
            case 4://DONE // 加载完成
              if(this.status == 200||this.status == 304){
                  console.log(this.responseText);
                  }
              console.log("DONE")
              break;
          }
      }
      xhr.ontimeout = function(e) { console.log('ontimeout') }
      xhr.onerror = function(e) { console.log('onerror') }

      // 3:打开请求
      xhr.open('Get', URL, true);//创建一个Get请求,采用异步


      // 4:配置参数
      xhr.timeout = 3000 //设置xhr请求的超时时间
      xhr.responseType = "text" //设置响应返回的数据格式
      xhr.setRequestHeader("X_TEST","time.geekbang")

      // 5:发送请求
      xhr.send();
  }
  
 fetch API: 浏览器内置,支持promise.     
     fetch('http://localhost:8080').then((res) => {
      if(res.status === 200) return res.json();
    }).then((res) => {
      console.log(res);
    })

25、浅拷贝的几种方式?

浅拷贝指的是将数组或者对象的第一层进行拷贝,它与深拷贝的区别在于它对于对象的属性使用引用类型是无法拷贝的.
let a = [{val:1},1]
let b = [...a];
b[1] = 2; b[0].val = 2;
// a = [{val: 2},1]; // 引用类型的地址被拷贝过去了,所以改变了地址内的值
// b = [{val: 2},2]; 
浅拷贝的方式:
     1、[...items];
     2、Array.from(items);
     3、... (伪数组转换的方式都是适用于浅拷贝的)
     4、Object.assign({},items) 可以用于对象的转换
     5、{...items} 对象的结构
     
这是我做手写篇的时候发现有点漏掉的.深拷贝在会在手写篇内出现.