关于前端:前端高频面试题二附答案

6次阅读

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

什么是 DOM 和 BOM?

  • DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象次要定义了解决网页内容的办法和接口。
  • BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来看待,这个对象次要定义了与浏览器进行交互的法和接口。BOM 的外围是 window,而 window 对象具备双重角色,它既是通过 js 拜访浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者办法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最基本的对象 document 对象也是 BOM 的 window 对象的子对象。

协商缓存和强缓存的区别

(1)强缓存

应用强缓存策略时,如果缓存资源无效,则间接应用缓存资源,不用再向服务器发动申请。

强缓存策略能够通过两种形式来设置,别离是 http 头信息中的 Expires 属性和 Cache-Control 属性。

(1)服务器通过在响应头中增加 Expires 属性,来指定资源的过期工夫。在过期工夫以内,该资源能够被缓存应用,不用再向服务器发送申请。这个工夫是一个相对工夫,它是服务器的工夫,因而可能存在这样的问题,就是客户端的工夫和服务器端的工夫不统一,或者用户能够对客户端工夫进行批改的状况,这样就可能会影响缓存命中的后果。

(2)Expires 是 http1.0 中的形式,因为它的一些毛病,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更准确的管制。它有很多不同的值,

Cache-Control可设置的字段:

  • public:设置了该字段值的资源示意能够被任何对象(包含:发送申请的客户端、代理服务器等等)缓存。这个字段值不罕用,个别还是应用 max-age= 来准确管制;
  • private:设置了该字段值的资源只能被用户浏览器缓存,不容许任何代理服务器缓存。在理论开发当中,对于一些含有用户信息的 HTML,通常都要设置这个字段值,防止代理服务器 (CDN) 缓存;
  • no-cache:设置了该字段须要先和服务端确认返回的资源是否产生了变动,如果资源未发生变化,则间接应用缓存好的资源;
  • no-store:设置了该字段示意禁止任何缓存,每次都会向服务端发动新的申请,拉取最新的资源;
  • max-age=:设置缓存的最大有效期,单位为秒;
  • s-maxage=:优先级高于 max-age=,仅实用于共享缓存(CDN),优先级高于 max-age 或者 Expires 头;
  • max-stale[=]:设置了该字段表明客户端违心接管曾经过期的资源,然而不能超过给定的工夫限度。

一般来说只须要设置其中一种形式就能够实现强缓存策略,当两种形式一起应用时,Cache-Control 的优先级要高于 Expires。

no-cache 和 no-store 很容易混同:

  • no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,然而会有协商缓存;
  • no-store 是指不应用任何缓存,每次申请都间接从服务器获取资源。

(2)协商缓存

如果命中强制缓存,咱们无需发动新的申请,间接应用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。

下面曾经说到了,命中协商缓存的条件有两个:

  • max-age=xxx 过期了
  • 值为no-store

应用协商缓存策略时,会先向服务器发送一个申请,如果资源没有产生批改,则返回一个 304 状态,让浏览器应用本地的缓存正本。如果资源产生了批改,则返回批改后的资源。

协商缓存也能够通过两种形式来设置,别离是 http 头信息中的 EtagLast-Modified 属性。

(1)服务器通过在响应头中增加 Last-Modified 属性来指出资源最初一次批改的工夫,当浏览器下一次发动申请时,会在申请头中增加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当申请发送到服务器后服务器会通过这个属性来和资源的最初一次的批改工夫来进行比拟,以此来判断资源是否做了批改。如果资源没有批改,那么返回 304 状态,让客户端应用本地的缓存。如果资源曾经被批改了,则返回批改后的资源。应用这种办法有一个毛病,就是 Last-Modified 标注的最初批改工夫只能准确到秒级,如果某些文件在 1 秒钟以内,被批改屡次的话,那么文件已将扭转了然而 Last-Modified 却没有扭转,这样会造成缓存命中的不精确。

(2)因为 Last-Modified 的这种可能产生的不准确性,http 中提供了另外一种形式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中增加了 Etag 属性,这个属性是资源生成的惟一标识符,当资源产生扭转的时候,这个值也会产生扭转。在下一次资源申请时,浏览器会在申请头中增加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接管到申请后会依据这个值来和资源以后的 Etag 的值来进行比拟,以此来判断资源是否产生扭转,是否须要返回资源。通过这种形式,比 Last-Modified 的形式更加准确。

当 Last-Modified 和 Etag 属性同时呈现的时候,Etag 的优先级更高。应用协商缓存的时候,服务器须要思考负载平衡的问题,因而多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因而在思考负载平衡时,最好不要设置 Etag 属性。

总结:

强缓存策略和协商缓存策略在缓存命中时都会间接应用本地的缓存正本,区别只在于协商缓存会向服务器发送一次申请。它们缓存不命中时,都会向服务器发送申请来获取资源。在理论的缓存机制中,强缓存策略和协商缓存策略是一起单干应用的。浏览器首先会依据申请的信息判断,强缓存是否命中,如果命中则间接应用资源。如果不命中则依据头信息向服务器发动申请,应用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器间接应用本地资源的正本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

display:inline-block 什么时候会显示间隙?

  • 有空格时会有间隙,能够删除空格解决;
  • margin正值时,能够让 margin 应用负值解决;
  • 应用 font-size 时,可通过设置 font-size:0letter-spacingword-spacing 解决;

说一下怎么取出数组最多的一项?

// 我这里只是一个示例
const d = {};
let ary = ['赵', '钱', '孙', '孙', '李', '周', '李', '周', '周', '李'];
ary.forEach(k => !d[k] ? d[k] = 1 : d[k]++);
const result = Object.keys(d).sort((a, b) => d[b] - d[a]).filter((k, i, l) => d[k] === d[l[0]]);
console.log(result)
复制代码

代码输入后果

Promise.resolve('1')
  .then(res => {console.log(res)
  })
  .finally(() => {console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {console.log('finally2')
      return '我是 finally2 返回的值'
  })
  .then(res => {console.log('finally2 前面的 then 函数', res)
  })
复制代码

输入后果如下:

1
finally2
finally
finally2 前面的 then 函数 2
复制代码

.finally()个别用的很少,只有记住以下几点就能够了:

  • .finally()办法不论 Promise 对象最初的状态如何都会执行
  • .finally()办法的回调函数不承受任何的参数,也就是说你在 .finally() 函数中是无奈晓得 Promise 最终的状态是 resolved 还是 rejected
  • 它最终返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异样则返回异样的 Promise 对象。
  • finally 实质上是 then 办法的特例

.finally()的谬误捕捉:

Promise.resolve('1')
  .finally(() => {console.log('finally1')
    throw new Error('我是 finally 中抛出的异样')
  })
  .then(res => {console.log('finally 前面的 then 函数', res)
  })
  .catch(err => {console.log('捕捉谬误', err)
  })
复制代码

输入后果为:

'finally1'
'捕捉谬误' Error: 我是 finally 中抛出的异样
复制代码

什么是 CSRF 攻打?

(1)概念

CSRF 攻打指的是 跨站申请伪造攻打,攻击者诱导用户进入一个第三方网站,而后该网站向被攻打网站发送跨站申请。如果用户在被攻打网站中保留了登录状态,那么攻击者就能够利用这个登录状态,绕过后盾的用户验证,假冒用户向服务器执行一些操作。

CSRF 攻打的 实质是利用 cookie 会在同源申请中携带发送给服务器的特点,以此来实现用户的假冒。

(2)攻打类型

常见的 CSRF 攻打有三种:

  • GET 类型的 CSRF 攻打,比方在网站中的一个 img 标签里构建一个申请,当用户关上这个网站的时候就会主动发动提交。
  • POST 类型的 CSRF 攻打,比方构建一个表单,而后暗藏它,当用户进入页面时,主动提交这个表单。
  • 链接类型的 CSRF 攻打,比方在 a 标签的 href 属性里构建一个申请,而后诱导用户去点击。

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

实现代码如下:

let ul = document.getElementById("container");
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false;}
  // 每页多少条
  let pageCount = Math.min(curTotal, once);
  window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement("li");
      li.innerText = curIndex + i + ":" + ~~(Math.random() * total);
      ul.appendChild(li);
    }
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}
loop(total, index);
复制代码

扩大思考:对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了 大家自行百度哈 虚构列表和虚构表格在日常我的项目应用还是很频繁的

防抖

防抖(debounce):触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。相似王者光荣的回城性能,你重复触发回城性能,那么只认最初一次,从最初一次触发开始计时。

核心思想 :每次事件触发就 革除原来的定时器,建设新的定时器。应用 apply 或 call 调用传入的函数。函数外部反对应用 this 和 event 对象;

利用 :防抖常利用于用户进行搜寻输出节约申请资源,window 触发 resize 事件时进行防抖只触发一次。

实现

function debounce(fn, delay) {
    // 利用闭包的原理
    let timer = null;
    return function(...args){if(timer) clearTimeout(timer);
        timer = setTimeout(() => {
            // 扭转 this 指向为调用 debounce 所指的对象
            fn.call(this, ...args);
            // fn.apply(this, args);
        }, delay);
    }
}
复制代码

Loader 和 Plugin 有什么区别

Loader:直译为 ” 加载器 ”。Webpack 将所有文件视为模块,然而 webpack 原生是只能解析 js 文件,如果想将其余文件也打包的话,就会用到loader。所以 Loader 的作用是让 webpack 领有了加载和解析非 JavaScript 文件的能力。Plugin:直译为 ” 插件 ”。Plugin 能够扩大 webpack 的性能,让 webpack 具备更多的灵活性。在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。

事件流传机制(事件流)

冒泡和捕捉

事件是什么?事件模型?

事件是用户操作网页时产生的交互动作,比方 click/move,事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息(event 的属性)以及能够对事件进行的操作(event 的办法)。

事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:

  • DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在 dom 对象上注册事件名称,就是 DOM0 写法。
  • IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
  • DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。

代码输入后果

const p1 = new Promise((resolve) => {setTimeout(() => {resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {console.log(res)  // resolve1
  setTimeout(() => {console.log(p1)
  }, 1000)
}).finally(res => {console.log('finally', res)
})
复制代码

执行后果为如下:

resolve1
finally  undefined
timer1
Promise{<resolved>: undefined}
复制代码

须要留神的是最初一个定时器打印出的 p1 其实是 .finally 的返回值,咱们晓得 .finally 的返回值如果在没有抛出谬误的状况下默认会是上一个 Promise 的返回值,而这道题中 .finally 上一个 Promise 是 .then(),然而这个.then() 并没有返回值,所以 p1 打印进去的 Promise 的值会是undefined,如果在定时器的上面加上一个return 1,则值就会变成 1。

代码输入后果

var obj = { 
  name : 'cuggz', 
  fun : function(){console.log(this.name); 
  } 
} 
obj.fun()     // cuggz
new obj.fun() // undefined
复制代码

应用 new 构造函数时,其 this 指向的是全局环境 window。

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

⽹络劫持分为两种:

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

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

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

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

call apply bind

题目形容: 手写 call apply bind 实现

实现代码如下:

Function.prototype.myCall = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this; //this 指向调用 call 的函数
  // 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
  return context[fn](...args);
};

// apply 原理统一  只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  // 执行函数并返回后果
  return context[fn](...args);
};

//bind 实现要简单一点  因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)

Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}
  // 发明惟一的 key 值  作为咱们结构的 context 外部办法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind 状况要简单一点
  const result = function (...innerArgs) {
    // 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
    // 此时因为 new 操作符作用  this 指向 result 实例对象  而 result 又继承自传入的_this 依据原型链常识可得出以下论断
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时 this 指向指向 result 的实例  这时候不须要扭转 this 指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
    } else {
      // 如果只是作为一般函数调用  那就很简略了 间接扭转 this 指向为传入的 context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
  // 实现继承的形式: 应用 Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

// 用法如下

// function Person(name, age) {//   console.log(name); //'我是参数传进来的 name'
//   console.log(age); //'我是参数传进来的 age'
//   console.log(this); // 构造函数 this 指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {//   console.log(123);
// }
// let obj = {
//   objName: '我是 obj 传进来的 name',
//   objAge: '我是 obj 传进来的 age'
// }
// // 一般函数
// function normalFun(name, age) {//   console.log(name);   //'我是参数传进来的 name'
//   console.log(age);   //'我是参数传进来的 age'
//   console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
//   console.log(this.objName); //'我是 obj 传进来的 name'
//   console.log(this.objAge); //'我是 obj 传进来的 age'
// }

// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的 name')
// let a = new bindFun('我是参数传进来的 age')
// a.say() //123

// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的 name')
//  bindFun('我是参数传进来的 age')
复制代码

异步编程的实现形式?

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

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

判断数组的形式有哪些

  • 通过 Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
复制代码
  • 通过原型链做判断
obj.__proto__ === Array.prototype;
复制代码
  • 通过 ES6 的 Array.isArray()做判断
Array.isArrray(obj);
复制代码
  • 通过 instanceof 做判断
obj instanceof Array
复制代码
  • 通过 Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
复制代码

instanceof

作用:判断对象的具体类型。能够区别 arrayobjectnullobject 等。

语法A instanceof B

如何判断的?: 如果 B 函数的显式原型对象在 A 对象的原型链上,返回true,否则返回false

留神:如果检测原始值,则始终返回 false

实现:

function myinstanceof(left, right) {
    // 根本数据类型都返回 false,留神 typeof 函数 返回 "function"
    if((typeof left !== "object" && typeof left !== "function") || left === null) return false;
    let leftPro = left.__proto__;  // 取右边的(隐式)原型 __proto__
    // left.__proto__ 等价于 Object.getPrototypeOf(left)
    while(true) {
        // 判断是否到原型链顶端
        if(leftPro === null) return false;
        // 判断左边的显式原型 prototype 对象是否在右边的原型链上
        if(leftPro === right.prototype) return true;
        // 原型链查找
        leftPro = leftPro.__proto__;
    }
}
复制代码

map 和 foreach 有什么区别

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