社招前端必会面试题

OSI七层模型

ISO为了更好的使网络应用更为普及,推出了OSI参考模型。

(1)应用层

OSI参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTPHTTPSFTPPOP3SMTP等。

  • 在客户端与服务器中经常会有数据的请求,这个时候就是会用到http(hyper text transfer protocol)(超文本传输协议)或者https.在后端设计数据接口时,我们常常使用到这个协议。
  • FTP是文件传输协议,在开发过程中,个人并没有涉及到,但是我想,在一些资源网站,比如百度网盘`迅雷`应该是基于此协议的。
  • SMTPsimple mail transfer protocol(简单邮件传输协议)。在一个项目中,在用户邮箱验证码登录的功能时,使用到了这个协议。

(2)表示层

表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。

在项目开发中,为了方便数据传输,可以使用base64对数据进行编解码。如果按功能来划分,base64应该是工作在表示层。

(3)会话层

会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。

(4)传输层

传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。

(5)网络层

本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。我们可以这样理解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。

(6)数据链路层

将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。
网络层与数据链路层的对比,通过上面的描述,我们或许可以这样理解,网络层是规划了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还增加了差错控制的功能。

(7)物理层

实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。

OSI七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。

----问题知识点分割线----

position的属性有哪些,区别是什么

position有以下属性值:

属性值 概述
absolute 生成绝对定位的元素,相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。
relative 生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。
fixed 生成绝对定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。
static 默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。
inherit 规定从父元素继承position属性的值

前面三者的定位方式如下:

  • relative: 元素的定位永远是相对于元素自身位置的,和其他元素没关系,也不会影响其他元素。
  • fixed: 元素的定位是相对于 window (或者 iframe)边界的,和其他元素没有关系。但是它具有破坏性,会导致其他元素位置的变化。
  • absolute: 元素的定位相对于前两者要复杂许多。如果为 absolute 设置了 top、left,浏览器会根据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了position:relative/absolute/fixed的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。如下两个图所示:

----问题知识点分割线----

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

⽹络劫持分为两种:

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

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

(2)HTTP劫持: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)

DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

----问题知识点分割线----

掌握页面的加载过程

网页加载流程

  • 当我们打开网址的时候,浏览器会从服务器中获取到 HTML 内容
  • 浏览器获取到 HTML 内容后,就开始从上到下解析 HTML 的元素
  • 元素内容会先被解析,此时浏览器还没开始渲染页面

    • 我们看到元素里有用于描述页面元数据的元素,还有一些元素涉及外部资源(如图片、CSS 样式等),此时浏览器会去获取这些外部资源。除此之外,我们还能看到元素中还包含着不少的
      • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
      • DOM 离线后修改,比如:先把 DOMdisplay:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
      • 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
      for(let i = 0; i < 1000; i++) {
          // 获取 offsetTop 会导致回流,因为需要去获取正确的值
          console.log(document.querySelector('.test').style.offsetTop)
      }
      • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
      • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
      • CSS选择符从右往左匹配查找,避免 DOM深度过深
      • 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video标签,浏览器会自动将该节点变为图层。

      社招前端必会面试题_第6张图片

      社招前端必会面试题_第7张图片

      ----问题知识点分割线----

      代码输出结果

      function foo() {
        console.log( this.a );
      }
      
      function doFoo() {
        foo();
      }
      
      var obj = {
        a: 1,
        doFoo: doFoo
      };
      
      var a = 2; 
      obj.doFoo()
      

      输出结果:2

      在Javascript中,this指向函数执行时的当前对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出2。

      ----问题知识点分割线----

      深拷贝浅拷贝

      浅拷贝:浅拷贝通过ES6新特性Object.assign()或者通过扩展运算法...来达到浅拷贝的目的,浅拷贝修改
      副本,不会影响原数据,但缺点是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有引用型数据,修改
      副本会影响原数据。
      
      深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目的,但利用JSON拷贝也是有缺点的,
      当要拷贝的数据中含有undefined/function/symbol类型是无法进行拷贝的,当然我们想项目开发中需要
      深拷贝的数据一般不会含有以上三种类型,如有需要可以自己在封装一个函数来实现。
      

      ----问题知识点分割线----

      前端储存的⽅式有哪些?

      • cookies: 在HTML5标准前本地储存的主要⽅式,优点是兼容性好,请求头⾃带cookie⽅便,缺点是⼤⼩只有4k,⾃动请求头加⼊cookie浪费流量,每个domain限制20个cookie,使⽤起来麻烦,需要⾃⾏封装;
      • localStorage:HTML5加⼊的以键值对(Key-Value)为标准的⽅式,优点是操作⽅便,永久性储存(除⾮⼿动删除),⼤⼩为5M,兼容IE8+ ;
      • sessionStorage:与localStorage基本类似,区别是sessionStorage当⻚⾯关闭后会被清理,⽽且与cookie、localStorage不同,他不能在所有同源窗⼝中共享,是会话级别的储存⽅式;
      • Web SQL:2010年被W3C废弃的本地数据库数据存储⽅案,但是主流浏览器(⽕狐除外)都已经有了相关的实现,web sql类似于SQLite,是真正意义上的关系型数据库,⽤sql进⾏操作,当我们⽤JavaScript时要进⾏转换,较为繁琐;
      • IndexedDB: 是被正式纳⼊HTML5标准的数据库储存⽅案,它是NoSQL数据库,⽤键值对进⾏储存,可以进⾏快速读取操作,⾮常适合web场景,同时⽤JavaScript进⾏操作会⾮常便。

      ----问题知识点分割线----

      寄生组合继承

      题目描述:实现一个你认为不错的 js 继承方式

      实现代码如下:

      function Parent(name) {
        this.name = name;
        this.say = () => {
          console.log(111);
        };
      }
      Parent.prototype.play = () => {
        console.log(222);
      };
      function Children(name) {
        Parent.call(this);
        this.name = name;
      }
      Children.prototype = Object.create(Parent.prototype);
      Children.prototype.constructor = Children;
      // let child = new Children("111");
      // // console.log(child.name);
      // // child.say();
      // // child.play();
      

      ----问题知识点分割线----

      DNS同时使用TCP和UDP协议?

      DNS占用53号端口,同时使用TCP和UDP协议。 (1)在区域传输的时候使用TCP协议

      • 辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。
      • TCP是一种可靠连接,保证了数据的准确性。

      (2)在域名解析的时候使用UDP协议

      • 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上说,客户端也可以指定向DNS服务器查询时用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

      ----问题知识点分割线----

      AJAX

      const getJSON = function(url) {
          return new Promise((resolve, reject) => {
              const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
              xhr.open('GET', url, false);
              xhr.setRequestHeader('Accept', 'application/json');
              xhr.onreadystatechange = function() {
                  if (xhr.readyState !== 4) return;
                  if (xhr.status === 200 || xhr.status === 304) {
                      resolve(xhr.responseText);
                  } else {
                      reject(new Error(xhr.responseText));
                  }
              }
              xhr.send();
          })
      }
      

      实现数组原型方法

      forEach

      Array.prototype.forEach2 = function(callback, thisArg) {
          if (this == null) {
              throw new TypeError('this is null or not defined')
          }
          if (typeof callback !== "function") {
              throw new TypeError(callback + ' is not a function')
          }
          const O = Object(this)  // this 就是当前的数组
          const len = O.length >>> 0  // 后面有解释
          let k = 0
          while (k < len) {
              if (k in O) {
                  callback.call(thisArg, O[k], k, O);
              }
              k++;
          }
      }
      

      O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型

      map

      基于 forEach 的实现能够很容易写出 map 的实现:

      - Array.prototype.forEach2 = function(callback, thisArg) {
      + Array.prototype.map2 = function(callback, thisArg) {
          if (this == null) {
              throw new TypeError('this is null or not defined')
          }
          if (typeof callback !== "function") {
              throw new TypeError(callback + ' is not a function')
          }
          const O = Object(this)
          const len = O.length >>> 0
      -   let k = 0
      +   let k = 0, res = []
          while (k < len) {
              if (k in O) {
      -           callback.call(thisArg, O[k], k, O);
      +           res[k] = callback.call(thisArg, O[k], k, O);
              }
              k++;
          }
      +   return res
      }
      

      filter

      同样,基于 forEach 的实现能够很容易写出 filter 的实现:

      - Array.prototype.forEach2 = function(callback, thisArg) {
      + Array.prototype.filter2 = function(callback, thisArg) {
          if (this == null) {
              throw new TypeError('this is null or not defined')
          }
          if (typeof callback !== "function") {
              throw new TypeError(callback + ' is not a function')
          }
          const O = Object(this)
          const len = O.length >>> 0
      -   let k = 0
      +   let k = 0, res = []
          while (k < len) {
              if (k in O) {
      -           callback.call(thisArg, O[k], k, O);
      +           if (callback.call(thisArg, O[k], k, O)) {
      +               res.push(O[k])                
      +           }
              }
              k++;
          }
      +   return res
      }
      

      some

      同样,基于 forEach 的实现能够很容易写出 some 的实现:

      - Array.prototype.forEach2 = function(callback, thisArg) {
      + Array.prototype.some2 = function(callback, thisArg) {
          if (this == null) {
              throw new TypeError('this is null or not defined')
          }
          if (typeof callback !== "function") {
              throw new TypeError(callback + ' is not a function')
          }
          const O = Object(this)
          const len = O.length >>> 0
          let k = 0
          while (k < len) {
              if (k in O) {
      -           callback.call(thisArg, O[k], k, O);
      +           if (callback.call(thisArg, O[k], k, O)) {
      +               return true
      +           }
              }
              k++;
          }
      +   return false
      }
      

      reduce

      Array.prototype.reduce2 = function(callback, initialValue) {
          if (this == null) {
              throw new TypeError('this is null or not defined')
          }
          if (typeof callback !== "function") {
              throw new TypeError(callback + ' is not a function')
          }
          const O = Object(this)
          const len = O.length >>> 0
          let k = 0, acc
      
          if (arguments.length > 1) {
              acc = initialValue
          } else {
              // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
              while (k < len && !(k in O)) {
                  k++
              }
              if (k > len) {
                  throw new TypeError( 'Reduce of empty array with no initial value' );
              }
              acc = O[k++]
          }
          while (k < len) {
              if (k in O) {
                  acc = callback(acc, O[k], k, O)
              }
              k++
          }
          return acc
      }
      

      ----问题知识点分割线----

      Compositon api

      Composition API也叫组合式API,是Vue3.x的新特性。

      通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要
      • Vue2.0中,随着功能的增加,组件变得越来越复杂,越来越难维护,而难以维护的根本原因是Vue的API设计迫使开发者使用watch,computed,methods选项组织代码,而不是实际的业务逻辑。
      • 另外Vue2.0缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以minxis完成逻辑复用,但是当mixin变多的时候,会使得难以找到对应的data、computed或者method来源于哪个mixin,使得类型推断难以进行。
      • 所以Composition API的出现,主要是也是为了解决Option API带来的问题,第一个是代码组织问题,Compostion API可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。
      • 第二个是实现代码的逻辑提取与复用,当然mixin也可以实现逻辑提取与复用,但是像前面所说的,多个mixin作用在同一个组件时,很难看出property是来源于哪个mixin,来源不清楚,另外,多个mixinproperty存在变量命名冲突的风险。而Composition API刚好解决了这两个问题。

      通俗的讲:

      没有Composition API之前vue相关业务的代码需要配置到option的特定的区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的

      compositon api提供了以下几个函数:

      • setup
      • ref
      • reactive
      • watchEffect
      • watch
      • computed
      • toRefs
      • 生命周期的hooks

      都说Composition API与React Hook很像,说说区别

      从React Hook的实现角度看,React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState,所以出现了以下限制
      • 不能在循环、条件、嵌套函数中调用Hook
      • 必须确保总是在你的React函数的顶层调用Hook
      • useEffect、useMemo等函数必须手动确定依赖关系
      而Composition API是基于Vue的响应式系统实现的,与React Hook的相比
      • 声明在setup函数内,一次组件实例化只调用一次setup,而React Hook每次重渲染都需要调用Hook,使得React的GC比Vue更有压力,性能也相对于Vue来说也较慢
      • Compositon API的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用
      • 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由Vue内部自己完成,而React Hook需要手动传入依赖,而且必须必须保证依赖的顺序,让useEffectuseMemo等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。
      虽然Compositon API看起来比React Hook好用,但是其设计思想也是借鉴React Hook的。

      ----问题知识点分割线----

      HTTP之URL

      社招前端必会面试题_第8张图片

      • URI 是用来唯一标记服务器上资源的一个字符串,通常也称为 URL;
      • URI 通常由 schemehost:portpathquery 四个部分组成,有的可以省略;
      • scheme 叫“方案名”或者“协议名”,表示资源应该使用哪种协议来访问;
      • host:port”表示资源所在的主机名和端口号;
      • path 标记资源所在的位置;
      • query 表示对资源附加的额外要求;
      • URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP报文后会无法正确处理

      ----问题知识点分割线----

      absolute与fixed共同点与不同点

      共同点:

      • 改变行内元素的呈现方式,将display置为inline-block
      • 使元素脱离普通文档流,不再占据文档物理空间
      • 覆盖非定位文档元素

      不同点:

      • abuselute与fixed的根元素不同,abuselute的根元素可以设置,fixed根元素是浏览器。
      • 在有滚动条的页面中,absolute会跟着父元素进行移动,fixed固定在页面的具体位置。

      ----问题知识点分割线----

      代码输出结果

      Promise.resolve().then(() => {
        return new Error('error!!!')
      }).then(res => {
        console.log("then: ", res)
      }).catch(err => {
        console.log("catch: ", err)
      })
      

      输出结果如下:

      "then: " "Error: error!!!"
      

      返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!')),因此它会被then捕获而不是catch。

      ----问题知识点分割线----

      AJAX

      实现:利用 XMLHttpRequest

      // get
      const getJSON = (url) => {
          return new Promise((resolve, reject) => {
              let xhr = new XMLHttpRequest();
              // open 方法用于指定 HTTP 请求的参数: method, url, async(是否异步,默认true)
              xhr.open("GET", url, false);
              xhr.setRequestHeader('Content-Type', 'application/json');
              // onreadystatechange 属性指向一个监听函数。
              // readystatechange 事件发生时(实例的readyState属性变化),就会执行这个属性。
              xhr.onreadystatechange = function(){
                  // 4 表示服务器返回的数据已经完全接收,或者本次接收已经失败
                  if(xhr.readyState !== 4) return;
                  // 请求成功,基本上只有2xx和304的状态码,表示服务器返回是正常状态
                  if(xhr.status === 200 || xhr.status === 304) {
                      // responseText 属性返回从服务器接收到的字符串
                      resolve(xhr.responseText);
                  }
                  // 请求失败
                  else {
                      reject(new Error(xhr.responseText));
                  }
              }
              xhr.send();
          });
      }
      
      // post
      const postJSON = (url, data) => {
          return new Promise((resolve, reject) => {
              let xhr = new XMLHttpRequest();
              xhr.open("POST", url);
              xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
              xhr.onreadystatechange = function(){
                  if(xhr.readyState !== 4) return;
                  if(xhr.status === 200 || xhr.status === 304) {
                      resolve(xhr.responseText);
                  }
                  else {
                      reject(new Error(xhr.responseText));
                  }
              }
              xhr.send(data);
          });
      }
      

你可能感兴趣的:(前端javascript)