对对象和数组解构的理解解构是ES6提供的一种新的数据抽取模式。价值。1)数组解构解构数组时,以元素的位置作为匹配条件,提取需要的数据:const[a,b,c]=[1,2,3]最后,a,b,c分别为数组的0、1、2索引位的值赋值:数组中0、1、2索引位的元素值准确映射到0、1、2左边2个变量,是数组解构的工作方式。也可以通过为左边的变量数组设置空位来准确提取数组中的某些元素:const[a,,c]=[1,2,3]通过将中间位留空,可以成功赋值将数组的首位和末位赋值给两个变量a和c:2)对象解构对象解构比数组结构稍微复杂一点,但也更强大。在解构对象时,以属性的名称作为匹配条件,提取出想要的数据。现在定义一个对象:conststu={name:'Bob',age:24}如果想解构它自身的两个属性,可以这样做:const{name,age}=stu这样,就可以得到name和age是和stu同级的变量:注意对象解构是严格按照属性名进行的,所以即使改变name和age的位置,结果也是一样的:const{age,name}=stu详解前端高级面试题答案代码输出结果functiona(){console.log(this);}a.call(null);打印结果:window对象根据ECMAScript262规范:如果作为第一个参数传入的对象调用者为null或undefined,则call方法会将全局对象(浏览器中的window对象)作为this的值。所以不管传入null还是undefined,它的this都是全局对象窗口。所以,在浏览器上答案是输出窗口对象。请注意,在严格模式下,null为null,undefined为未定义:'usestrict';functiona(){console.log(this);}a.call(null);//nulla.call(undefined);//undefined什么是文件准备?Webkit和Firefox都做了这个优化。当执行JavaScript脚本时,另一个线程解析剩余的文档并加载稍后需要通过网络加载的资源。这样可以并行加载资源,从而使整体速度更快。需要注意的是,预解析并没有改变DOM树,而是把这个工作交给了主解析过程,它只解析对外部资源的引用,比如外部脚本、样式表和图像。单击刷新按钮或按F5、按Ctrl+F5(强制刷新)和在地址栏按回车有什么区别?点击刷新按钮或者按F5:浏览器直接对本地缓存文件进行过期,但是会带上If-Modifed-Since,If-None-Match,表示服务器会检查文件的新鲜度,返回的结果可能是304,也可能是200。用户按下Ctrl+F5(强制刷新):浏览器不仅会使本地文件过期,而且不会带上If-Modifed-Since,If-None-Match,也就是说它之前没有请求过,返回结果为200在地址栏输入:浏览器发起请求,按照正常流程,在本地检查是否过期,然后服务器检查新鲜度,最后返回内容.重排和重绘的概念和触发条件(1)重排当渲染树中部分或全部元素的大小、结构或属性发生变化时,浏览器重新渲染部分或全部文档的过程称为重排。以下操作会引起回流:页面第一次渲染浏览器窗口大小改变元素内容改变元素大小或位置改变或者触发回流(重排)时删除可见DOM元素,因为浏览器基于流式布局渲染页面,所以当触发回流时,周围的DOM元素会重新排列,其影响范围有两种:全局范围:从根节点开始,重新布局整个渲染树局部范围:重新布局渲染树的某一部分或某个渲染对象(2)重绘当页面中某些元素的样式发生变化,但不影响其在文档流中的位置时,浏览器会重绘该元素,而这个过程称为重绘。以下操作会引起回流:颜色、背景相关属性:background-color、background-image其他轮廓相关属性:outline-color、outline-width、text-decorationborder-radius、visibility、box-shadow注意:回流时triggered,肯定会触发重绘,但重绘不一定会触发回流。事件总线(发布-订阅模式)类EventEmitter{constructor(){this.cache={}}on(name,fn){if(this.cache[name]){this.cache[name].push(fn)}else{this.cache[name]=[fn]}}off(name,fn){lettasks=this.cache[name]if(tasks){constindex=tasks.findIndex(f=>f===fn||f.callback===fn)if(index>=0){tasks.splice(index,1)}}}emit(name,once=false,...args){if(this.cache[name]){//创建副本,如果回调函数继续注册同一个事件,会造成死循环lettasks=this.cache[name].slice()for(letfnoftasks){fn(...args)}if(once){deletethis.cache[name]}}}}//测试leteventBus=newEventEmitter()letfn1=function(name,age){console.log(`${name}${age}`)}letfn2=function(name,age){console.log(`hello,${name}${age}`)}eventBus.on('aaa',fn1)eventBus.on('aaa',fn2)eventBus.emit('aaa',false,'Bran',12)//'Bran12'//'你好,Bran12'事件是什么?事件模型?事件是用户操作网页时发生的交互动作,例如点击/移动。除了用户触发的动作外,事件还可以是文档加载、窗口滚动和调整大小事件。所有相关信息(事件的属性)和可以对事件执行的操作(事件的方法)。事件是用户操作网页或对网页本身进行某些操作时发生的交互动作。现代浏览器有三种事件模型:DOM0级别的事件模型,不会传播,所以没有事件流的概念,但是现在有浏览器支持冒泡。可以直接在网页中定义监听功能,也可以通过js属性指定监听功能。所有浏览器都兼容这种方法。直接在dom对象上注册事件名,就是DOM0的写法。IE事件模型,在这个事件模型中,一个事件有两个过程,事件处理阶段和事件冒泡阶段。在事件处理阶段,首先执行绑定到目标元素的监听事件。然后是事件冒泡阶段。冒泡是指事件从目标元素冒泡到文档,依次检查传递的节点是否绑定了事件监听函数,如果是则执行。该模型通过attachEvent添加监听函数,可以添加多个监听函数,依次执行。DOM2级别的事件模型,在这个事件模型中,一个事件分为三个过程,第一个过程是事件捕获阶段。捕获是指事件从文档向下传播到目标元素,依次检查传递的节点是否绑定了事件监听函数,如果绑定则执行。后两个阶段与IE事件模型的两个阶段相同。在这个事件模型中,事件绑定函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。进程前的通信方式(1)管道通信管道是最基本的进程间通信机制。管道是操作系统在内核中创建的缓冲区。进程1可以将需要交互的数据拷贝到这个缓冲区,进程2可以读取。管道的特点:只能单向通信,只能通过与血液相关的进程进行通信,依赖于文件系统的生命周期。面向字节流的服务管道提供了管道内部的同步机制(2)消息队列通信。消息队列是消息列表。.用户可以在消息队列中添加消息、阅读消息等。消息队列提供了一种将数据块从一个进程发送到另一个进程的方法。每个数据块都被认为有一个类型,接收进程可以独立地接收包含不同类型的数据结构。通过发送消息可以避免命名管道的同步和阻塞问题。但是消息队列和命名管道一样,每个数据块都有最大长度限制。使用消息队列进行进程间通信可能会收到数据块最大长度的限制,这也是这种通信方式的缺点。如果频繁发生进程间通信,则进程需要频繁读取队列中的数据到内存中,相当于间接从一个进程复制到另一个进程,耗费时间。(3)信号量通信共享内存最大的问题就是多进程竞争内存的问题,就像线程安全问题一样。我们可以使用信号量来解决这个问题。信号量的本质是一个计数器,用来实现进程间的互斥和同步。比如信号量的初始值为1,然后当进程a访问内存1时,我们将信号量的值设置为0,然后当进程b也来访问内存1时,我们看到信号量为0可知已经有进程访问内存1,此时进程b将无法访问内存1。因此,信号量也是进程间通信的一种手段。(4)信号通信信号(Signals)是Unix系统中使用的最古老的进程间通信方法之一。操作系统通过信号通知进程系统中已经发生了预定的事件(一组事件中的一个),它也是用户进程之间进行通信和同步的一种原始机制。(5)共享内存通信共享内存就是映射一段内存,可以被其他进程访问。这个共享内存是由一个进程创建的,但是多个进程可以访问它(这样多个进程就可以访问同一个内存空间)。共享内存是最快的IPC方法,它专门设计用于在运行其他进程间通信方法的地方运行效率低下。它常与信号量等其他通信机制结合使用,以实现进程间的同步和通信。(6)Socket通信上面提到的共享内存、管道、信号量、消息队列等都是一台主机上多个进程之间的通信。相隔千里的两个进程能否通信?答案是必要的。这时候,Socket小伙就派上用场了。比如我们通常通过浏览器发起一个http请求,然后服务器返回相应的数据给你。这是使用Socket的通信方式。await在等什么?await在等什么?通常,await被认为是等待异步函数完成。但是按照语法,await等待一个表达式,这个表达式的结果是一个Promise对象或者其他值(也就是说没有特别的限制)。因为async函数返回的是一个Promise对象,所以可以使用await来等待一个async函数的返回值——这也可以说await是在等待async函数,但要明确一点,它实际上是在等待一个返回值.请注意,await不仅用于等待Promise对象,它还可以等待任何表达式的结果,因此,在await之后,它后面实际上可以进行普通函数调用或直接量。所以这个例子工作得非常好:constv2=awaittestAsync();console.log(v1,v2);}测试();await表达式的结果取决于它在等待什么。如果它不是Promise,则await表达式计算它正在等待的内容。如果它等待一个Promise对象,await将会很忙。它会阻塞下面的代码,等待Promise对象解析,然后获取解析后的值作为await表达式的结果。让我们看一个例子:functiontestAsy(x){returnnewPromise(resolve=>{setTimeout(()=>{resolve(x);},3000)})}asyncfunctiontestAwt(){letresult=awaittestAsy('你好世界');控制台日志(结果);//3秒后出现helloworldconsole.log('cuger')//3秒后出现cug}testAwt();console.log('cug')//立即输出cug这就是await必须在async中使用的原因功能。异步函数调用不会造成阻塞,所有内部阻塞都封装在一个Promise对象中异步执行。await暂停当前async的执行,所以先输出'cug'',3秒后helloworld'和'cuger'同时出现。寄生组合继承题目描述:实现一个你觉得不错的JS继承方式。实现代码如下:functionParent(name){this.name=name;this.say=()=>{console.log(111);};}Parent.prototype.play=()=>{console.log(222);};functionChildren(name){Parent.call(this);this.name=name;}Children.prototype=Object.create(Parent.prototype);Children.prototype.constructor=Children;//letchild=newChildren("111");////console.log(child.name);////child.say();////child.play();instanceof操作符的实现原理和实现instanceof操作符用于判断构造函数的prototype属性是否出现在对象的原型链中的任何地方。functionmyInstanceof(left,right){//获取对象的原型letproto=Object.getPrototypeOf(left)//获取构造函数的原型对象letprototype=right.prototype;//判断构造函数的原型对象是否在原型链上的对象中while(true){if(!proto)returnfalse;如果(原型===原型)返回真;//如果没有找到,则从其原型继续查找,Object.getPrototypeOf方法用于获取指定对象Prototype=Object.getPrototypeOf(proto);}}JS整数是怎么表示的?用Number类型表示,遵循IEEE754标准,一个数用64位表示,(1+11+52),最大安全数为Math.pow(2,53)-1,16位十进制。(符号位+指数位+小数部分有效位)Map和Object的区别MapObjectunexpectedkeyMap默认不包含任何key,只包含显式插入的key。对象有原型,原型链上的键名可能与对象上设置的键名冲突。键类型映射键可以是任何值,包括函数、对象或任何原始类型。对象键必须是字符串或符号。键顺序Map中的键是有序的。因此,在迭代时,Map对象按插入顺序返回键。Object的键是无序的。SizeMap的key-value对的个数可以很容易的通过size属性获取。Object的key-value对的个数只能手动计算。IterationMap是可迭代的,所以可以直接迭代。迭代一个对象需要在迭代之前以某种方式获取它的键。在频繁添加或删除键值对的场景下性能更好。没有针对频繁增删键值对的场景进行优化。margin和padding的使用场景需要在border外添加空白,空白处不需要背景(颜色),使用margin;当需要在边框内添加空白,并且空白需要背景(颜色)时,使用填充。new一个构造函数,如果函数返回return{}、returnnull、return1、returntrue会发生什么?如果函数返回一个对象,那么new函数调用返回这个函数的返回对象,否则返回new创建的新对象。说说类组件和函数组件的区别?1.语法上的区别:函数式组件是一个纯函数,需要接受props参数,返回一个React元素。类组件需要继承React.Component,类组件需要创建render并返回React元素,语法上比较复杂。2.调用方法函数式组件可以直接调用返回一个新的React元素;类组件在调用时需要创建一个实例,然后在实例中调用render方法返回一个React元素。3.状态管理功能组件没有状态管理,类组件有状态管理。4.使用场景组件没有特定要求。函数式组件一般用在大型项目中,用来划分大型组件(函数式组件不需要创建实例,效率更高)。一般可以使用函数式组件代替类组件来提高效率。写版本号排序的方法题目描述:有一组版本号如下['0.1.1','2.3.3','0.302.1','4.2','4.3.5','4.3.4.5']。现在需要排序,排序结果为['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']实现代码如下:arr.sort((a,b)=>{leti=0;constarr1=a.split(".");constarr2=b.split(".");while(true){consts1=arr1[i];consts2=arr2[i];i++;if(s1===undefined||s2===undefined){returnarr2.length-arr1.length;}if(s1===s2)继续;返回s2-s1;}});控制台日志(arr);如何判断一个对象是否属于某个类?第一种方式,使用instanceof运算符来判断构造函数的原型属性是否出现在对象原型链的任何地方。第二种方式是通过对象的constructor属性来判断。对象的constructor属性指向对象的构造函数,但是这种方法不是很安全,因为constructor属性可以被重写。第三种方式,如果需要判断一个内置的引用类型,可以使用Object.prototype.toString()方法打印对象的[[Class]]属性进行判断。
