前端二面面试题(附答案)
Proxy 代理
proxy 在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截
new Proxy()
表示生成一个 Proxy 实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为
targetWithLog
读取属性的值时,实际上执行的是logHandler.get
:在控制台输出信息,并且读取被代理对象target
的属性。在
targetWithLog
设置属性值时,实际上执行的是logHandler.set
:在控制台输出信息,并且设置被代理对象target
的属性的值
Proxy 实例也可以作为其他对象的原型对象
proxy
对象是obj
对象的原型,obj
对象本身并没有time
属性,所以根据原型链,会在proxy
对象上读取该属性,导致被拦截
Proxy 的作用
对于代理模式
Proxy
的作用主要体现在三个方面
拦截和监视外部对对象的访问
降低函数或类的复杂度
在复杂操作前对操作进行校验或对所需资源进行管理
Proxy 所能代理的范围--handler
实际上 handler 本身就是 ES6 所新设计的一个对象.它的作用就是用来 自定义代理对象的各种可代理操作 。它本身一共有 13 中方法,每种方法都可以代理一种操作.其 13 种方法如下
为何 Proxy 不能被 Polyfill
如 class 可以用
function
模拟;promise
可以用callback
模拟但是 proxy 不能用
Object.defineProperty
模拟
目前谷歌的 polyfill 只能实现部分的功能,如 get、set https://github.com/GoogleChrome/proxy-polyfill
了解 this 嘛,bind,call,apply 具体指什么
它们都是函数的方法
call: Array.prototype.call(this, args1, args2])
apply: Array.prototype.apply(this, [args1, args2])
:ES6 之前用来展开数组调用, foo.appy(null, [])
,ES6 之后使用 ... 操作符
New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
如果需要使用 bind 的柯里化和 apply 的数组解构,绑定到 null,尽可能使用 Object.create(null) 创建一个 DMZ 对象
四条规则:
默认绑定,没有其他修饰(bind、apply、call),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined
隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最后一层在调用位置中起作用
显示绑定:通过在函数上运行 call 和 apply ,来显示的绑定 this
显示绑定之硬绑定
New 绑定,new 调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的 this。
New 绑定时,如果是 new 一个硬绑定函数,那么会用 new 新建的对象替换这个硬绑定 this,
compose
题目描述:实现一个 compose 函数
实现代码如下:
说一下 web worker
在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。 并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。
如何创建 web worker:
检测浏览器对于 web worker 的支持性
创建 web worker 文件(js,回传函数等)
创建 web worker 对象
事件是什么?事件模型?
事件是用户操作网页时发生的交互动作,比如 click/move, 事件除了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型:
DOM0 级事件模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。所有浏览器都兼容这种方式。直接在 dom 对象上注册事件名称,就是 DOM0 写法。
IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
页面有多张图片,HTTP 是怎样的加载表现?
在
HTTP 1
下,浏览器对一个域名下最大 TCP 连接数为 6,所以会请求多次。可以用多域名部署解决。这样可以提高同时请求的数目,加快页面图片的获取速度。在
HTTP 2
下,可以一瞬间加载出来很多资源,因为,HTTP2 支持多路复用,可以在一个 TCP 连接中发送多个 HTTP 请求。
对 WebSocket 的理解
WebSocket 是 HTML5 提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于 TCP 传输协议,并复用 HTTP 的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。
WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。
WebSocket 原理:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者 ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有 ID 在接收者 ID 序列中的客户端才会处理这个事件。
WebSocket 特点的如下:
支持双向通信,实时性更强
可以发送文本,也可以发送二进制数据‘’
建立在 TCP 协议之上,服务端的实现比较容易
数据格式比较轻量,性能开销小,通信高效
没有同源限制,客户端可以与任意服务器通信
协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL
与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
Websocket 的使用方法如下:
在客户端中:
柯里化
题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
实现代码如下:
图片懒加载
实现:getBoundClientRect
的实现方式,监听 scroll
事件(建议给监听事件添加节流),图片加载完会从 img
标签组成的 DOM 列表中删除,最后所有的图片加载完毕后需要解绑监听事件。
ES6 之前使用 prototype 实现继承
Object.create() 会创建一个 “新” 对象,然后将此对象内部的 [[Prototype]] 关联到你指定的对象(Foo.prototype)。Object.create(null) 创建一个空 [[Prototype]] 链接的对象,这个对象无法进行委托。
对 keep-alive 的理解
HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是短连接。当使用 Keep-Alive 模式时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接,这就是长连接。其使用方法如下:
HTTP1.0 版本是默认没有 Keep-alive 的(也就是默认会发送 keep-alive),所以要想连接得到保持,必须手动配置发送
Connection: keep-alive
字段。若想断开 keep-alive 连接,需发送Connection:close
字段;HTTP1.1 规定了默认保持长连接,数据传输完成了保持 TCP 连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送
Connection:close
首部字段。
Keep-Alive 的建立过程:
客户端向服务器在发送请求报文同时在首部添加发送 Connection 字段
服务器收到请求并处理 Connection 字段
服务器回送 Connection:Keep-Alive 字段给客户端
客户端接收到 Connection 字段
Keep-Alive 连接建立成功
服务端自动断开过程(也就是没有 keep-alive):
客户端向服务器只是发送内容报文(不包含 Connection 字段)
服务器收到请求并处理
服务器返回客户端请求的资源并关闭连接
客户端接收资源,发现没有 Connection 字段,断开连接
客户端请求断开连接过程:
客户端向服务器发送 Connection:close 字段
服务器收到请求并处理 connection 字段
服务器回送响应资源并断开连接
客户端接收资源并断开连接
开启 Keep-Alive 的优点:
较少的 CPU 和内存的使⽤(由于同时打开的连接的减少了);
允许请求和应答的 HTTP 管线化;
降低拥塞控制 (TCP 连接减少了);
减少了后续请求的延迟(⽆需再进⾏握⼿);
报告错误⽆需关闭 TCP 连;
开启 Keep-Alive 的缺点:
长时间的 Tcp 连接容易导致系统资源无效占用,浪费系统资源。
说一下 slice splice split 的区别?
说一下原型链和原型链的继承吧
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其包含了 JavaScript 中许多通用的功能
为什么能创建 “类”,借助一种特殊的属性:所有的函数默认都会拥有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型
在发生 new 构造函数调用时,会将创建的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承
方法定义在原型上,属性定义在构造函数上
首先要说一下 JS 原型和实例的关系:每个构造函数 (constructor)都有一个原型对象(prototype),这个原型对象包含一个指向此构造函数的指针属性,通过 new 进行构造函数调用生成的实例,此实例包含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象
然后说一下 JS 中属性的查找:当我们试图引用实例对象的某个属性时,是按照这样的方式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去构造这个实例对象的构造函数的 prototype 所指向的对象上去查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象上去查找
什么是原型链:这样逐级查找形似一个链条,且通过 [[Prototype]] 属性链接,所以被称为原型链
什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。
** 标准答案更正确的解释**
什么是原型链?
当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找停止。这种通过 通过原型链接的逐级向上的查找链被称为原型链
什么是原型继承?
一个对象可以使用另外一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另外一个对象,相当于一个对象可以使用另外一个对象的属性和方法了。
对 line-height 的理解及其赋值方式
(1)line-height 的概念:
line-height 指一行文本的高度,包含了字间距,实际上是下一行基线到上一行基线距离;
如果一个标签没有定义 height 属性,那么其最终表现的高度由 line-height 决定;
一个容器没有设置高度,那么撑开容器高度的是 line-height,而不是容器内的文本内容;
把 line-height 值设置为 height 一样大小的值可以实现单行文字的垂直居中;
line-height 和 height 都能撑开一个高度;
(2)line-height 的赋值方式:
带单位:px 是固定值,而 em 会参考父元素 font-size 值计算自身的行高
纯数字:会把比例传递给后代。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px
百分比:将计算后的值传递给后代
HTTP3
Google 在推 SPDY 的时候就已经意识到了这些问题,于是就另起炉灶搞了一个基于 UDP 协议的“QUIC”协议,让 HTTP 跑在 QUIC 上而不是 TCP 上。主要特性如下:
实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性
实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据。
集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
多路复用,彻底解决 TCP 中队头阻塞的问题。
DOM 节点操作
(1)创建新节点
(2)添加、移除、替换、插入
(3)查找
(4)属性操作
即时通讯的实现:短轮询、长轮询、SSE 和 WebSocket 间的区别?
短轮询和长轮询的目的都是用于实现客户端和服务器端的一个即时通讯。
短轮询的基本思路: 浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。这种方式的优点是比较简单,易于理解。缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。
长轮询的基本思路: 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。
SSE 的基本思想: 服务器使用流信息向服务器推送信息。严格地说,http 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 http 协议,目前除了 IE/Edge,其他浏览器都支持。它相对于前面两种方式来说,不需要建立过多的 http 请求,相比之下节约了资源。
WebSocket 是 HTML5 定义的一个新协议议,与传统的 http 协议不同,该协议允许由服务器主动的向客户端推送信息。使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息,而 SSE 的方式是单向通信的,只能由服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个 http 请求了。
上面的四个通信协议,前三个都是基于 HTTP 协议的。
对于这四种即使通信协议,从性能的角度来看: WebSocket > 长连接(SEE) > 长轮询 > 短轮询 但是,我们如果考虑浏览器的兼容性问题,顺序就恰恰相反了: 短轮询 > 长轮询 > 长连接(SEE) > WebSocket 所以,还是要根据具体的使用场景来判断使用哪种方式。
Generator
Generator
是ES6
中新增的语法,和Promise
一样,都可以用来异步编程。Generator 函数可以说是 Iterator 接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。
function*
用来声明一个函数是生成器函数,它比普通的函数声明多了一个*
,*
的位置比较随意可以挨着function
关键字,也可以挨着函数名yield
产出的意思,这个关键字只能出现在生成器函数体内,但是生成器中也可以没有yield
关键字,函数遇到yield
的时候会暂停,并把yield
后面的表达式结果抛出去next
作用是将代码的控制权交还给生成器函数
上面这个示例就是一个 Generator 函数,我们来分析其执行过程:
首先 Generator 函数调用时它会返回一个迭代器
当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42
yield
实际就是暂缓执行的标示,每执行一次next()
,相当于指针移动到下一个yield
位置
总结一下 ,
Generator
函数是ES6
提供的一种异步编程解决方案。通过yield
标识位和next()
方法调用,实现函数的分段执行
遍历器对象生成函数,最大的特点是可以交出函数的执行权
function
关键字与函数名之间有一个星号;函数体内部使用
yield
表达式,定义不同的内部状态;next
指针移向下一个状态
这里你可以说说
Generator
的异步编程,以及它的语法糖async
和awiat
,传统的异步编程。ES6
之前,异步编程大致如下
回调函数
事件监听
发布/订阅
传统异步编程方案之一:协程,多个线程互相协作,完成异步任务。
从以上代码可以发现,加上
*
的函数执行后拥有了next
函数,也就是说函数执行后返回了一个对象。每次调用next
函数可以继续执行被暂停的代码。以下是Generator
函数的简单实现
absolute 与 fixed 共同点与不同点
共同点:
改变行内元素的呈现方式,将 display 置为 inline-block
使元素脱离普通文档流,不再占据文档物理空间
覆盖非定位文档元素
不同点:
abuselute 与 fixed 的根元素不同,abuselute 的根元素可以设置,fixed 根元素是浏览器。
在有滚动条的页面中,absolute 会跟着父元素进行移动,fixed 固定在页面的具体位置。
懒加载的特点
减少无用资源的加载:使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。
提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而使用懒加载就能大大的提高用户体验。
防止加载过多图片而影响其他资源文件的加载 :会影响网站应用的正常使用。
评论