关于前端:高级前端一面面试题合集

39次阅读

共计 8442 个字符,预计需要花费 22 分钟才能阅读完成。

陈说输出 URL 回车后的过程

1. 读取缓存:搜寻本身的 DNS 缓存。(如果 DNS 缓存中找到 IP 地址就跳过了接下来查找 IP 地址步骤,间接拜访该 IP 地址。)
2.DNS 解析: 将域名解析成 IP 地址
3.TCP 连贯:TCP 三次握手,繁难形容三次握手
           客户端:服务端你在么?服务端:客户端我在,你要连贯我么?客户端:是的服务端,我要链接。连贯买通,能够开始申请来
4. 发送 HTTP 申请
5. 服务器解决申请并返回 HTTP 报文
6. 浏览器解析渲染页面
7. 断开连接:TCP 四次挥手

对于第六步浏览器解析渲染页面又能够聊聊如果返回的是 html 页面
依据 HTML 解析出 DOM 树
依据 CSS 解析生成 CSS 规定树
联合 DOM 树和 CSS 规定树,生成渲染树
依据渲染树计算每一个节点的信息
依据计算好的信息绘制页面

为什么 udp 不会粘包?

  • TCP 协定是⾯向流的协定,UDP 是⾯向音讯的协定。UDP 段都是⼀条音讯,应⽤程序必须以音讯为单位提取数据,不能⼀次提取任意字节的数据
  • UDP 具备爱护音讯边界,在每个 UDP 包中就有了音讯头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏辨别解决了。传输协定把数据当作⼀条独⽴的音讯在⽹上传输,接收端只能接管独⽴的音讯。接收端⼀次只能接管发送端收回的⼀个数据包, 如果⼀次承受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会失落⼀局部数据,即便失落,承受端也不会分两次去接管。

网络劫持有哪几种,如何防备?

⽹络劫持分为两种:

(1)DNS 劫持: (输⼊京东被强制跳转到淘宝这就属于 dns 劫持)

  • DNS 强制解析: 通过批改运营商的本地 DNS 记录,来疏导⽤户流量到缓存服务器
  • 302 跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是能够进⾏劫持解决的, 再对劫持的内存发动 302 跳转的回复,疏导⽤户获取内容

(2)HTTP 劫持: (拜访⾕歌然而⼀直有贪玩蓝⽉的⼴告), 因为 http 明⽂传输, 运营商会批改你的 http 响应内容(即加⼴告)

DNS 劫持因为涉嫌守法,曾经被监管起来,当初很少会有 DNS 劫持,⽽ http 劫持仍然⾮常盛⾏,最无效的方法就是全站 HTTPS,将 HTTP 加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

JavaScript 为什么要进行变量晋升,它导致了什么问题?

变量晋升的体现是,无论在函数中何处地位申明的变量,如同都被晋升到了函数的首部,能够在变量申明前拜访到而不会报错。

造成变量申明晋升的 实质起因 是 js 引擎在代码执行前有一个解析的过程,创立了执行上下文,初始化了一些代码执行时须要用到的对象。当拜访一个变量时,会到以后执行上下文中的作用域链中去查找,而作用域链的首端指向的是以后执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它蕴含了函数的形参、所有的函数和变量申明,这个对象的是在代码解析的时候创立的。

首先要晓得,JS 在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。

  • 在解析阶段,JS 会查看语法,并对函数进行预编译。解析的时候会先创立一个全局执行上下文环境,先把代码中行将执行的变量、函数申明都拿进去,变量先赋值为 undefined,函数先申明好可应用。在一个函数执行之前,也会创立一个函数执行上下文环境,跟全局执行上下文相似,不过函数执行上下文会多出 this、arguments 和函数的参数。

    • 全局上下文:变量定义,函数申明
    • 函数上下文:变量定义,函数申明,this,arguments
  • 在执行阶段,就是依照代码的程序顺次执行。

那为什么会进行变量晋升呢?次要有以下两个起因:

  • 进步性能
  • 容错性更好

(1)进步性能 在 JS 代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了进步性能,如果没有这一步,那么每次执行代码前都必须从新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会扭转,解析一遍就够了。

在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计申明了哪些变量、创立了哪些函数,并对函数的代码进行压缩,去除正文、不必要的空白等。这样做的益处就是每次执行函数时都能够间接为该函数调配栈空间(不须要再解析一遍去获取代码中申明了哪些变量,创立了哪些函数),并且因为代码压缩的起因,代码执行也更快了。

(2)容错性更好

变量晋升能够在肯定水平上进步 JS 的容错性,看上面的代码:

a = 1;var a;console.log(a);

如果没有变量晋升,这两行代码就会报错,然而因为有了变量晋升,这段代码就能够失常执行。

尽管,在能够开发过程中,能够完全避免这样写,然而有时代码很简单的时候。可能因为忽略而先应用后定义了,这样也不会影响失常应用。因为变量晋升的存在,而会失常运行。

总结:

  • 解析和预编译过程中的申明晋升能够进步性能,让函数能够在执行时事后为变量调配栈空间
  • 申明晋升还能够进步 JS 代码的容错性,使一些不标准的代码也能够失常执行

变量晋升尽管有一些长处,然而他也会造成肯定的问题,在 ES6 中提出了 let、const 来定义变量,它们就没有变量晋升的机制。上面看一下变量晋升可能会导致的问题:

var tmp = new Date();

function fn(){console.log(tmp);
    if(false){var tmp = 'hello world';}
}

fn();  // undefined

在这个函数中,本来是要打印出外层的 tmp 变量,然而因为变量晋升的问题,内层定义的 tmp 被提到函数外部的最顶部,相当于笼罩了外层的 tmp,所以打印后果为 undefined。

var tmp = 'hello world';

for (var i = 0; i < tmp.length; i++) {console.log(tmp[i]);
}

console.log(i); // 11

因为遍历时定义的 i 会变量晋升成为一个全局变量,在函数完结之后不会被销毁,所以打印进去 11。

什么是闭包

闭包是一种非凡的对象,它由两局部组成:执行上下文(代号 A),以及在该执行上下文中创立的函数(代号 B),当 B 执行时,如果拜访了 A 中变量对象的值,那么闭包就会产生,且在 Chrome 中应用这个执行上下文 A 的函数名代指闭包。

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要 思考浏览器兼容问题。
.parent {position: relative;} .child {position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
  • 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于 盒子有宽高 的状况:
.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于 盒子宽高已知 的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
  • 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 思考兼容的问题,该办法在挪动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}

闭包是什么?

闭包是指有权拜访另外一个函数作用域中的变量的函数

JavaScript 代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器实现,将代码翻译成可执行代码,这个阶段作用域规定会确定。执行阶段由引擎实现,次要工作是执行可执行代码,执行上下文在这个阶段创立。

异步编程的实现形式?

JavaScript 中的异步机制能够分为以下几种:

  • 回调函数 的形式,应用回调函数的形式有一个毛病是,多个回调函数嵌套的时候会造成回调函数天堂,高低两层的回调函数间的代码耦合度太高,不利于代码的可保护。
  • Promise 的形式,应用 Promise 的形式能够将嵌套的回调函数作为链式调用。然而应用这种办法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
  • generator 的形式,它能够在函数的执行过程中,将函数的执行权转移进来,在函数内部还能够将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移进来,当异步函数执行结束时再将执行权给转移回来。因而在 generator 外部对于异步操作的形式,能够以同步的程序来书写。应用这种形式须要思考的问题是何时将函数的控制权转移回来,因而须要有一个主动执行 generator 的机制,比如说 co 模块等形式来实现 generator 的主动执行。
  • async 函数 的形式,async 函数是 generator 和 promise 实现的一个主动执行的语法糖,它外部自带执行器,当函数外部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会期待 promise 对象的状态变为 resolve 后再持续向下执行。因而能够将异步逻辑,转化为同步的程序来书写,并且这个函数能够主动执行。

map 和 foreach 有什么区别

foreach()办法会针对每一个元素执行提供得函数, 该办法没有返回值, 是否会扭转原数组取决与数组元素的类型是根本类型还是援用类型
map()办法不会扭转原数组的值, 返回一个新数组, 新数组中的值为原数组调用函数解决之后的值:

如何取得对象非原型链上的属性?

应用后 hasOwnProperty() 办法来判断属性是否属于原型链的属性:

function iterate(obj){var res=[];
   for(var key in obj){if(obj.hasOwnProperty(key))
           res.push(key+':'+obj[key]);
   }
   return res;
} 

object.assign 和扩大运算法是深拷贝还是浅拷贝,两者区别

扩大运算符:

let outObj = {inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

Object.assign():

let outObj = {inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

能够看到,两者都是浅拷贝。

  • Object.assign()办法接管的第一个参数作为指标对象,前面的所有参数作为源对象。而后把所有的源对象合并到指标对象中。它会批改了一个对象,因而会触发 ES6 setter。
  • 扩大操作符(…)应用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,然而它会复制 ES6 的 symbols 属性。

CDN 的应用场景

  • 应用第三方的 CDN 服务:如果想要开源一些我的项目,能够应用第三方的 CDN 服务
  • 应用 CDN 进行动态资源的缓存:将本人网站的动态资源放在 CDN 上,比方 js、css、图片等。能够将整个我的项目放在 CDN 上,实现一键部署。
  • 直播传送:直播实质上是应用流媒体进行传送,CDN 也是反对流媒体传送的,所以直播齐全能够应用 CDN 来进步访问速度。CDN 在解决流媒体的时候与解决一般动态文件有所不同,一般文件如果在边缘节点没有找到的话,就会去上一层接着寻找,然而流媒体自身数据量就十分大,如果应用回源的形式,必然会带来性能问题,所以流媒体个别采纳的都是被动推送的形式来进行。

代码输入后果

var a = 10
var obj = {
  a: 20,
  say: () => {console.log(this.a)
  }
}
obj.say() 

var anotherObj = {a: 30} 
obj.say.apply(anotherObj) 

输入后果:10 10

我么晓得,箭头函数时不绑定 this 的,它的 this 来自原其父级所处的上下文,所以首先会打印全局中的 a 的值 10。前面尽管让 say 办法指向了另外一个对象,然而仍不能扭转箭头函数的个性,它的 this 依然是指向全局的,所以依旧会输入 10。

然而,如果是一般函数,那么就会有齐全不一样的后果:

var a = 10  
var obj = {  
  a: 20,  
  say(){console.log(this.a)  
  }  
}  
obj.say()   
var anotherObj={a:30}   
obj.say.apply(anotherObj)

输入后果:20 30

这时,say 办法中的 this 就会指向他所在的对象,输入其中的 a 的值。

代码输入后果

var obj = {say: function() {var f1 = () =>  {console.log("1111", this);
     }
     f1();},
   pro: {getPro:() =>  {console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

输入后果:

1111 window 对象
1111 obj 对象
window 对象

解析:

  1. o(),o 是在全局执行的,而 f1 是箭头函数,它是没有绑定 this 的,它的 this 指向其父级的 this,其父级 say 办法的 this 指向的是全局作用域,所以会打印出 window;
  2. obj.say(),谁调用 say,say 的 this 就指向谁,所以此时 this 指向的是 obj 对象;
  3. obj.pro.getPro(),咱们晓得,箭头函数时不绑定 this 的,getPro 处于 pro 中,而对象不形成独自的作用域,所以箭头的函数的 this 就指向了全局作用域 window。

事件是如何实现的?

基于公布订阅模式,就是在浏览器加载的时候会读取事件相干的代码,然而只有理论等到具体的事件触发的时候才会执行。

比方点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。

在 Web 端,咱们常见的就是 DOM 事件:

  • DOM0 级事件,间接在 html 元素上绑定 on-event,比方 onclick,勾销的话,dom.onclick = null,同一个事件只能有一个处理程序,前面的会笼罩后面的。
  • DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件能够有多个事件处理程序,按程序执行,捕捉事件和冒泡事件
  • DOM3 级事件,减少了事件类型,比方 UI 事件,焦点事件,鼠标事件

对象创立的形式有哪些?

个别应用字面量的模式间接创建对象,然而这种创立形式对于创立大量类似对象的时候,会产生大量的反复代码。但 js 和个别的面向对象的语言不同,在 ES6 之前它没有类的概念。然而能够应用函数来进行模仿,从而产生出可复用的对象创立形式,常见的有以下几种:

(1)第一种是工厂模式,工厂模式的次要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目标。然而它有一个很大的问题就是创立进去的对象无奈和某个类型分割起来,它只是简略的封装了复用代码,而没有建设起对象和类型间的关系。

(2)第二种是构造函数模式。js 中每一个函数都能够作为构造函数,只有一个函数是通过 new 来调用的,那么就能够把它称为构造函数。执行构造函数首先会创立一个对象,而后将对象的原型指向构造函数的 prototype 属性,而后将执行上下文中的 this 指向这个对象,最初再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因而能够应用 this 给对象赋值。构造函数模式绝对于工厂模式的长处是,所创立的对象和构造函数建设起了分割,因而能够通过原型来辨认对象的类型。然而构造函数存在一个毛病就是,造成了不必要的函数对象的创立,因为在 js 中函数也是一个对象,因而如果对象属性中如果蕴含函数的话,那么每次都会新建一个函数对象,节约了不必要的内存空间,因为函数是所有的实例都能够通用的。

(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它蕴含了通过构造函数创立的所有实例都能共享的属性和办法。因而能够应用原型对象来增加专用属性和办法,从而实现代码的复用。这种形式绝对于构造函数模式来说,解决了函数对象的复用问题。然而这种模式也存在一些问题,一个是没有方法通过传入参数来初始化值,另一个是如果存在一个援用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对援用类型值的扭转会影响所有的实例。

(4)第四种模式是组合应用构造函数模式和原型模式,这是创立自定义类型的最常见形式。因为构造函数模式和原型模式离开应用都存在一些问题,因而能够组合应用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数办法的复用。这种办法很好的解决了两种模式独自应用时的毛病,然而有一点有余的就是,因为应用了两种不同的模式,所以对于代码的封装性不够好。

(5)第五种模式是动静原型模式,这一种模式将原型办法赋值的创立过程挪动到了构造函数的外部,通过对属性是否存在的判断,能够实现仅在第一次调用函数时对原型对象赋值一次的成果。这一种形式很好地对下面的混合模式进行了封装。

(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的了解是,它次要是基于一个已有的类型,在实例化时对实例化的对象进行扩大。这样既不必批改原来的构造函数,也达到了扩大对象的目标。它的一个毛病和工厂模式一样,无奈实现对象的辨认。

forEach 和 map 办法有什么区别

这办法都是用来遍历数组的,两者区别如下:

  • forEach()办法会针对每一个元素执行提供的函数,对数据的操作会扭转原数组,该办法没有返回值;
  • map()办法不会扭转原数组的值,返回一个新数组,新数组中的值为原数组调用函数解决之后的值;

浏览器资源缓存的地位有哪些?

资源缓存的地位一共有 3 种,按优先级从高到低别离是:

  1. Service Worker:Service Worker 运行在 JavaScript 主线程之外,尽管因为脱离了浏览器窗体无奈间接拜访 DOM,然而它能够实现离线缓存、音讯推送、网络代理等性能。它能够让咱们 自在管制 缓存哪些文件、如何匹配缓存、如何读取缓存,并且 缓存是持续性的 。当 Service Worker 没有命中缓存的时候,须要去调用 fetch 函数获取 数据。也就是说,如果没有在 Service Worker 命中缓存,会依据缓存查找优先级去查找数据。 然而不论是从 Memory Cache 中还是从网络申请中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。
  2. Memory Cache: Memory Cache 就是内存缓存,它的效率最快,然而内存缓存尽管读取高效,可是缓存持续性很短,会随着过程的开释而开释。一旦咱们敞开 Tab 页面,内存中的缓存也就被开释了。
  3. Disk Cache: Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,然而什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中,Disk Cache 覆盖面根本是最大的。它会依据 HTTP Herder 中的字段判断哪些资源须要缓存,哪些资源能够不申请间接应用,哪些资源曾经过期须要从新申请。并且即便在跨站点的状况下,雷同地址的资源一旦被硬盘缓存下来,就不会再次去申请数据。

Disk Cache: Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被应用。并且缓存工夫也很短暂,只在会话(Session)中存在,一旦会话完结就被开释。其具备以下特点:

  • 所有的资源都能被推送,然而 Edge 和 Safari 浏览器兼容性不怎么好
  • 能够推送 no-cacheno-store 的资源
  • 一旦连贯被敞开,Push Cache 就被开释
  • 多个页面能够应用雷同的 HTTP/2 连贯,也就是说能应用同样的缓存
  • Push Cache 中的缓存只能被应用一次
  • 浏览器能够拒绝接受曾经存在的资源推送
  • 能够给其余域名推送资源

正文完
 0