写点什么

2022 面试官常考的前端面试题

作者:loveX001
  • 2022-12-16
    浙江
  • 本文字数:8033 字

    阅读完需:约 26 分钟

Ajax

它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。



面试手写(原生):


//1:创建Ajax对象var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本//2:配置 Ajax请求地址xhr.open('get','index.xml',true);//3:发送请求xhr.send(null); // 严谨写法//4:监听请求,接受响应xhr.onreadysatechange=function(){     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )          console.log(xhr.responsetXML)}
复制代码


jQuery 写法


$.ajax({  type:'post',  url:'',  async:ture,//async 异步  sync  同步  data:data,//针对post请求  dataType:'jsonp',  success:function (msg) {
}, error:function (error) {
}})
复制代码


promise 封装实现:


// promise 封装实现:
function getJSON(url) { // 创建一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest();
// 新建一个 http 请求 xhr.open("GET", url, true);
// 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } };
// 设置错误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); };
// 设置响应的数据类型 xhr.responseType = "json";
// 设置请求头信息 xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求 xhr.send(null); });
return promise;}
复制代码

数组扁平化

ES5 递归写法 —— isArray()、concat()

function flat11(arr) {    var res = [];    for (var i = 0; i < arr.length; i++) {        if (Array.isArray(arr[i])) {            res = res.concat(flat11(arr[i]));        } else {            res.push(arr[i]);        }    }    return res;}
复制代码


如果想实现第二个参数(指定“拉平”的层数),可以这样实现,后面的几种可以自己类似实现:


function flat(arr, level = 1) {    var res = [];    for(var i = 0; i < arr.length; i++) {        if(Array.isArray(arr[i]) || level >= 1) {            res = res.concat(flat(arr[i]), level - 1);        }        else {            res.push(arr[i]);        }    }    return res;}
复制代码

ES6 递归写法 — reduce()、concat()、isArray()

function flat(arr) {    return arr.reduce(        (pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), []    );}
复制代码

ES6 迭代写法 — 扩展运算符(...)、some()、concat()、isArray()

ES6 的扩展运算符(...) 只能扁平化一层


function flat(arr) {    return [].concat(...arr);}
复制代码


全部扁平化:遍历原数组,若arr中含有数组则使用一次扩展运算符,直至没有为止。


function flat(arr) {    while(arr.some(item => Array.isArray(item))) {        arr = [].concat(...arr);    }    return arr;}
复制代码

toString/join & split

调用数组的 toString()/join() 方法(它会自动扁平化处理),将数组变为字符串然后再用 split 分割还原为数组。由于 split 分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型。


function flat(arr){    return arr.toString().split(',').map(item => Number(item));    // return arr.join().split(',').map(item => Number(item));}
复制代码

使用正则

JSON.stringify(arr).replace(/[|]/g, '') 会先将数组arr序列化为字符串,然后使用 replace() 方法将字符串中所有的[] 替换成空字符,从而达到扁平化处理,此时的结果为 arr 不包含 [] 的字符串。最后通过JSON.parse() 解析字符串。


function flat(arr) {    return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') + "]");}
复制代码

类数组转化为数组

类数组是具有 length 属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果(如document.querySelectorAll('div'))等。

扩展运算符(...)

注意:扩展运算符只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值。


let arr = [...arrayLike]
复制代码

Array.from()

let arr = Array.from(arrayLike);
复制代码

Array.prototype.slice.call()

let arr = Array.prototype.slice.call(arrayLike);
复制代码

Array.apply()

let arr = Array.apply(null, arrayLike);
复制代码

concat + apply

let arr = Array.prototype.concat.apply([], arrayLike);
复制代码

async/await 如何捕获异常

async function fn(){    try{        let a = await Promise.reject('error')    }catch(error){        console.log(error)    }}
复制代码

响应式设计的概念及基本原理

响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。


关于原理: 基本原理是通过媒体查询(@media)查询检测不同的设备屏幕尺寸做处理。关于兼容: 页面头部必须有 mate 声明的viewport


<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>
复制代码

Sass、Less 是什么?为什么要使用他们?

他们都是 CSS 预处理器,是 CSS 上的一种抽象层。他们是一种特殊的语法/语言编译成 CSS。 例如 Less 是一种动态样式语言,将 CSS 赋予了动态语言的特性,如变量,继承,运算, 函数,LESS 既可以在客户端上运行 (支持 IE 6+, Webkit, Firefox),也可以在服务端运行 (借助 Node.js)。


为什么要使用它们?


  • 结构清晰,便于扩展。 可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理, 减少无意义的机械劳动。

  • 可以轻松实现多重继承。 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。

替换元素的概念及计算规则

通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。


替换元素除了内容可替换这一特性以外,还有以下特性:


  • 内容的外观不受页面上的 CSS 的影响:用专业的话讲就是在样式表现在 CSS 作用域之外。如何更改替换元素本身的外观需要类似 appearance 属性,或者浏览器自身暴露的一些样式接口。

  • 有自己的尺寸:在 Web 中,很多替换元素在没有明确尺寸设定的情况下,其默认的尺寸(不包括边框)是 300 像素×150 像素,如

  • 在很多 CSS 属性上有自己的一套表现规则:比较具有代表性的就是 vertical-align 属性,对于替换元素和非替换元素,vertical-align 属性值的解释是不一样的。比方说 vertical-align 的默认值的 baseline,很简单的属性值,基线之意,被定义为字符 x 的下边缘,而替换元素的基线却被硬生生定义成了元素的下边缘。

  • 所有的替换元素都是内联水平元素:也就是替换元素和替换元素、替换元素和文字都是可以在一行显示的。但是,替换元素默认的 display 值却是不一样的,有的是 inline,有的是 inline-block。


替换元素的尺寸从内而外分为三类:


  • 固有尺寸: 指的是替换内容原本的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着自己的宽度和高度的。

  • HTML 尺寸: 只能通过 HTML 原生属性改变,这些 HTML 原生属性包括的 width 和 height 属性、的 size 属性。

  • CSS 尺寸: 特指可以通过 CSS 的 width 和 height 或者 max-width/min-width 和 max-height/min-height 设置的尺寸,对应盒尺寸中的 content box。


这三层结构的计算规则具体如下:(1)如果没有 CSS 尺寸和 HTML 尺寸,则使用固有尺寸作为最终的宽高。(2)如果没有 CSS 尺寸,则使用 HTML 尺寸作为最终的宽高。(3)如果有 CSS 尺寸,则最终尺寸由 CSS 属性决定。(4)如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素依然按照固有的宽高比例显示。(5)如果上面的条件都不符合,则最终宽度表现为 300 像素,高度为 150 像素。(6)内联替换元素和块级替换元素使用上面同一套尺寸计算规则。


参考 前端进阶面试题详细解答

代码输出结果

async function async1() {  console.log("async1 start");  await async2();  console.log("async1 end");  setTimeout(() => {    console.log('timer1')  }, 0)}async function async2() {  setTimeout(() => {    console.log('timer2')  }, 0)  console.log("async2");}async1();setTimeout(() => {  console.log('timer3')}, 0)console.log("start")
复制代码


输出结果如下:


async1 startasync2startasync1 endtimer2timer3timer1
复制代码


代码的执行过程如下:


  1. 首先进入async1,打印出async1 start

  2. 之后遇到async2,进入async2,遇到定时器timer2,加入宏任务队列,之后打印async2

  3. 由于async2阻塞了后面代码的执行,所以执行后面的定时器timer3,将其加入宏任务队列,之后打印start

  4. 然后执行 async2 后面的代码,打印出async1 end,遇到定时器 timer1,将其加入宏任务队列;

  5. 最后,宏任务队列有三个任务,先后顺序为timer2timer3timer1,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。

对对象与数组的解构的理解

解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 1)数组的解构 在解构数组时,以元素的位置为匹配条件来提取想要的数据的:


const [a, b, c] = [1, 2, 3]
复制代码


最终,a、b、c 分别被赋予了数组第 0、1、2 个索引位的值:


数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:


const [a,,c] = [1,2,3]
复制代码


通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量:


2)对象的解构 对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:


const stu = {  name: 'Bob',  age: 24}
复制代码


假如想要解构它的两个自有属性,可以这样:


const { name, age } = stu
复制代码


这样就得到了 name 和 age 两个和 stu 平级的变量:


注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:


const { age, name } = stu
复制代码

两栏布局的实现

一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:


  • 利用浮动,将左边元素宽度设置为 200px,并且设置向左浮动。将右边元素的 margin-left 设置为 200px,宽度设置为 auto(默认为 auto,撑满整个父元素)。


.outer {  height: 100px;}.left {  float: left;  width: 200px;  background: tomato;}.right {  margin-left: 200px;  width: auto;  background: gold;}
复制代码


  • 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置 overflow: hidden; 这样右边就触发了 BFC,BFC 的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。


.left{     width: 100px;     height: 200px;     background: red;     float: left; } .right{     height: 300px;     background: blue;     overflow: hidden; }
复制代码


  • 利用 flex 布局,将左边元素设置为固定宽度 200px,将右边的元素设置为 flex:1。


.outer {  display: flex;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  flex: 1;  background: gold;}
复制代码


  • 利用绝对定位,将父级元素设置为相对定位。左边元素设置为 absolute 定位,并且宽度设置为 200px。将右边元素的 margin-left 的值设置为 200px。


.outer {  position: relative;  height: 100px;}.left {  position: absolute;  width: 200px;  height: 100px;  background: tomato;}.right {  margin-left: 200px;  background: gold;}
复制代码


  • 利用绝对定位,将父级元素设置为相对定位。左边元素宽度设置为 200px,右边元素设置为绝对定位,左边定位为 200px,其余方向定位为 0。


.outer {  position: relative;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  position: absolute;  top: 0;  right: 0;  bottom: 0;  left: 200px;  background: gold;}
复制代码

浏览器乱码的原因是什么?如何解决?

产生乱码的原因:


  • 网页源代码是gbk的编码,而内容中的中文字是utf-8编码的,这样浏览器打开即会出现html乱码,反之也会出现乱码;

  • html网页编码是gbk,而程序从数据库中调出呈现是utf-8编码的内容也会造成编码乱码;

  • 浏览器不能自动检测网页编码,造成网页乱码。


解决办法:


  • 使用软件编辑 HTML 网页内容;

  • 如果网页设置编码是gbk,而数据库储存数据编码格式是UTF-8,此时需要程序查询数据库数据显示数据前进程序转码;

  • 如果浏览器浏览时候出现网页乱码,在浏览器中找到转换编码的菜单进行转换。

实现一个宽高自适应的正方形

  • 利用 vw 来实现:


.square {  width: 10%;  height: 10vw;  background: tomato;}
复制代码


  • 利用元素的 margin/padding 百分比是相对父元素 width 的性质来实现:


.square {  width: 20%;  height: 0;  padding-top: 20%;  background: orange;}
复制代码


  • 利用子元素的 margin-top 的值来实现:


.square {  width: 30%;  overflow: hidden;  background: yellow;}.square::after {  content: '';  display: block;  margin-top: 100%;}
复制代码

代码输出结果

function Foo(){    Foo.a = function(){        console.log(1);    }    this.a = function(){        console.log(2)    }}
Foo.prototype.a = function(){ console.log(3);}
Foo.a = function(){ console.log(4);}
Foo.a();let obj = new Foo();obj.a();Foo.a();
复制代码


输出结果:4 2 1


解析:


  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4

  2. let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。

  3. obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2

  4. Foo.a() ; 根据第 2 步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1

说下对 JS 的了解吧

是基于原型的动态语言,主要独特特性有 this、原型和原型链。


JS 严格意义上来说分为:语言标准部分(ECMAScript)+ 宿主环境部分


语言标准部分


2015 年发布 ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自 2015 之后以年号代号,每年一更


宿主环境部分


  • 在浏览器宿主环境包括 DOM + BOM 等

  • 在 Node,宿主环境包括一些文件、数据库、网络、与操作系统的交互等

回流与重绘的概念及触发条件

(1)回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流


下面这些操作会导致回流:


  • 页面的首次渲染

  • 浏览器的窗口大小发生变化

  • 元素的内容发生变化

  • 元素的尺寸或者位置发生变化

  • 元素的字体大小发生变化

  • 激活 CSS 伪类

  • 查询某些属性或者调用某些方法

  • 添加或者删除可见的 DOM 元素


在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的 DOM 元素重新排列,它的影响范围有两种:


  • 全局范围:从根节点开始,对整个渲染树进行重新布局

  • 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

(2)重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘


下面这些操作会导致回流:


  • color、background 相关属性:background-color、background-image 等

  • outline 相关属性:outline-color、outline-width 、text-decoration

  • border-radius、visibility、box-shadow


注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

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

  • 有空格时会有间隙,可以删除空格解决;

  • margin正值时,可以让margin使用负值解决;

  • 使用font-size时,可通过设置font-size:0letter-spacingword-spacing解决;

事件传播机制(事件流)

冒泡和捕获

说一说前端性能优化方案

三个方面来说明前端性能优化一: webapck优化与开启gzip压缩    1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件    其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'    2.文件采用按需加载等等    3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:    accept-encoding:gzip    4.图片优化,采用svg图片或者字体图标    5.浏览器缓存机制,它又分为强缓存和协商缓存二:本地存储——从 Cookie 到 Web Storage、IndexedDB    说明一下SessionStorage和localStorage还有cookie的区别和优缺点三:代码优化    1.事件代理    2.事件的节流和防抖    3.页面的回流和重绘    4.EventLoop事件循环机制    5.代码优化等等

复制代码

防抖

**防抖(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);    }}
复制代码

哪些情况会导致内存泄漏

1、意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收2、被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。3、脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。4、闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。
复制代码

手写 bind、apply、call

// call
Function.prototype.call = function (context, ...args) { context = context || window;
const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
context[fnSymbol](...args); delete context[fnSymbol];}
复制代码


// apply
Function.prototype.apply = function (context, argsArr) { context = context || window;
const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
context[fnSymbol](...argsArr); delete context[fnSymbol];}
复制代码


// bind
Function.prototype.bind = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this;
return function (..._args) { args = args.concat(_args);
context[fnSymbol](...args); delete context[fnSymbol]; }}

复制代码


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
2022面试官常考的前端面试题_JavaScript_loveX001_InfoQ写作社区