2023前端超全面试题,全是二三月份面试真题整理!附答案。

目录

HTML

标签语意化

HTML5新特性

SEO

input元素的类型

iframe的特点

CSS

Flex

BFC

重排重绘

CSS优先级

CSS3新特性

清除浮动的方法

盒模型的理解

响应式布局

移动适配方案

三栏布局

圣杯布局和双飞翼布局

JS

JS为何是单线程

JS数据类型

js判断数据类型

js中的length属性

判断空对象

判断空数组

ES6新特性

ES6 Module 和 CommonJS 的区别

箭头函数和普通函数区别

new一个对象做了什么事情

构造函数与普通函数的区别

防抖和节流

深拷贝浅拷贝

数组去重

原型链

call、bind、apply的含义和区别

Class

Class和构造函数的区别

js继承的几种方式及其优缺点

闭包

浏览器的缓存机制

任务队列(宏/微任务)

事件委托

请求

请求的方式有哪些,应用场景是什么

什么是promise,解决了什么问题

谈谈对async/await的理解

跨越问题如何解决

输入网站url地址后发生了什么

ajax工作流程及原理

axios 是什么、怎么使用

Localstorage、sessionStorage、cookie 的区别

后端接口没写出来,前端如何进行开发

post请求和get请求的区别

Session,Token,Cookie在身份认证方面的区别

常见的状态码

Vue2

Vue是什么

MVVM

观察者模式和发布订阅者模式

SPA(单页应用)的理解

响应式原理

Vue2生命周期

data为什么是函数的存在

Vue组件通信的方式

keep-alive原理及缓存策略

computed原理

watch原理

computed和methods、watch的区别

vue-router原理

Vue3

Vue2和Vue3响应式原理的区别

Vue3生命周期

选项式API和组合式API

VueX和Pinia的区别、优劣

Vite好在哪为什么

TS

对ts的理解

ts的数据类型

never和void的区别

枚举的理解

泛型

项目

token超时处理

登录页面安全问题

发布通知功能怎么实现

项目刚上线出现bug

用什么进行版本管理

Echarts在Vue里怎么使用

Echarts里的配置项不够用怎么办

团队中有的人用vue2有的人用vue3怎么办

项目优化

PC端兼容问题

img下的留白

如果图片加a标签在IE9-中会有边框

移动端问题

移动端页面滚动滞涩感

修改移动端的点击高亮效果

滚动穿透问题

在ios和andriod中,audio元素和video元素在无法自动播放

iOS 系统中文输入法输入英文时,字母之间可能会出现一个六分之一空格

IOS移动端click事件300ms的延迟响应

阻止旋转屏幕时自动调整字体大小

图片模糊问题

移动端某些情况下input的placeholder会出现文本位置偏上的现象

h5底部输入框被键盘遮挡问题

移动端如何做真机测试

H5和app的区别


HTML

标签语意化

简单来说:就是用正确的标签做正确的事。比如:

  • 头部:header

  • 导航:nav

  • 主体内容:main

  • 标题:h1 ~ h6

  • 段落:p

  • 侧边栏:aside

  • 页脚:footer

这样,整篇HTML结构非常清晰,好看。

HTML语义化有什么好处呢?

  • 网页加载慢导致CSS文件还未加载时(没有CSS),页面仍然清晰、可读、好看。

  • 提升用户体验,例如title、alt可用于解释名词或解释图片信息。

  • 有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息。

  • 方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。

  • 使代码更具可读性,便于团队开发和维护。

HTML5新特性

  • 更加语义化的元素。 article、footer、header、nav、section

  • 本地化储存。 localStorage 和 sessionStorage

  • 拖曳以及释放的api。 Drag and drop(draggable="true";当拖拽一个项目到 HTML 元素中时,浏览器默认不会有任何响应。想要让一个元素变成可释放区域,该元素必须设置 ondragover和 ondrop 事件。)

  • 媒体播放。 video 和 audio

  • 增强表单控件类型。date、time、number、email、color、search

  • 全双工通信协议。 websocket

  • 跨域资源共享(CORS) Access-Control-Allow-Origin

补充:Web Storage 的概念和 cookie 相似,区别是它是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽,另外 cookie 还需要指定作用域,不可以跨域调用。

1.localStorage: 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

2.sessionStorage: 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储,仅仅是会话级别的存储。

Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie(安装依赖js-cookie、Cookies.set()、Cookies.get()、Cookies.remove)

SEO

SEO(Search Engine Optimization),即搜索引擎优化。SEO的存在就是为了提升网站在搜索引擎中的权重,增加对搜索引擎的友好度,使得用户在访问网站时能排在前面。

input元素的类型

① button ② checkbox ③ file ④ hidden ⑤ image ⑥ password ⑦ radio ⑧ reset ⑨ submit ⑩ Text ⑪ Date

iframe的特点

  • 优点:

  1. iframe能够原封不动的把嵌入的网页展现出来。

  2. 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。

  3. 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。

  4. 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

  • 缺点:

  1. iframe会阻塞主页面的onload事件;

  2. iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。,会产生很多页面,不容易管理。

  3. iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。

  4. 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO)。

  5. 很多的移动设备无法完全显示框架,设备兼容性差。

  6. iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。

CSS

Flex

  • 哪些属性作用在父元素上?

justify-content:space-around space-between space-evently center flex-end

align-items:flex-star flex-end

flex-direction :row row-reverse column column-reverse

flex-wrap :nowrap(如果子孩子,的宽度超过父盒子,会进行总动的伸缩) wrap(不去管)

  • 哪些属性作用在子元素上?

order (项目排列的顺序 越小越靠前)

align-self(单个属性的操作 flex-end center flex-star)

flex-grow(如果剩余有宽度是否进行扩张方法,默认是0,)

flex-shrink (空间不足的时候进行缩小默认是1 进行缩小)

BFC

定义:

块级格式化上下文。BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。用于对块级元素排版。默认情况下只有根元素(body)一个块级上下文。

布局规则:

  • 内部的盒子会在垂直方向,一个个地放置;

  • 盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻盒子的上下 margin 会发生重叠;

  • 每一个元素的左边,与包含块的左边相接触(对于从右往左的布局,则相反),即使存在浮动也是如此;

  • BFC 的区域不会与 float 重叠;(应用:三栏布局)

  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;

  • 计算 BFC 的高度时,浮动元素也參与计算。(运用:清除浮动)

  • 当一个元素设置了新的 BFC 后,就和这个元素外部的 BFC 没有关系了,这个元素只会去约束自己内部的子元素。(应用:外边距塌陷)

怎么触发BFC:

  • overflow:不为 visible

  • float: 不为 none

  • display: 为 inline-blocktabletable-celltable-captionflexinline-flexgridinline-gridflow-root

  • position: 为 absolute 或者 fixed

重排重绘

页面生成的过程:

1.HTML 被 HTML 解析器解析成 DOM 树;

2.CSS 被 CSS 解析器解析成 CSSOM 树;

3.结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;

4.生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;

5.将布局绘制(paint)在屏幕上,显示出整个页面。

第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。

2023前端超全面试题,全是二三月份面试真题整理!附答案。_第1张图片

  • 重绘:某些元素的外观被改变,例如:元素的填充颜色

  • 重排:重新生成布局,重新排列元素。

单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。

也就是说:重绘不一定导致重排,但重排一定会导致重绘

CSS优先级

!important > style > id > class > 标签 > 通配符 > 默认 > 继承

CSS3新特性

  1. 新增的选择器(各种伪类选择器)

  2. 圆角(border-radius)

  3. 阴影(box-shadow)

  4. 动画(animation)

  5. 过渡(transition)

  6. 翻转(transform)

  7. 渐变

  8. 媒体查询 @media

  9. 弹性盒子(flex)

  10. rgba

清除浮动的方法

为什么要清除浮动?

如果子元素浮动,此时子元素不能撑开标准流的块级父元素。

方法:

1、直接给父元素设置高度

  • 优点:简单、方便

  • 缺点:有很多布局不能固定父元素高度:比如长列表、推荐模块,是无法确定子项内容有多少的

2、额外标签法

  • 实现方式:

    1. 给父元素内容的最后添加一个块级元素

    2. 给添加的块级元素设置clear: both;(可以认为,设置了clear:both的当前元素会把前边元素中设有浮动属性的元素,当做没设浮动一样来看待,以此来消除其对自己的影响)

  • 缺点:

    • 会添加进去额外的标签,让HTML结构变得复杂

3、单伪元素清除法

用伪元素替代额外标签

4、双伪元素清除法

除了可以清除浮动的影响,还可以解决外边距折叠的塌陷现象(原理:里面的"display:table;"触发BFC)

5、为父元素设置overflow: hidden

盒模型的理解

分标准盒模型和怪异盒模型。

标准盒模型采用的W3C标准,盒子的content内容部分由width宽度和height高度决定,添加padding内边距或border外边框后,宽高都会进行相应增长。

怪异盒模型也称为IE盒模型,是IE浏览器设计元素时遵循的规则。怪异盒模型的宽高在div盒子初次设置时就已经规定,添加padding或者border,会从中减少content内容的占据区域,来为padding和border制造空间,宽高不会相对应的进行增长。

盒模型转换用box-sizing:border-box,默认是标准盒模型。

响应式布局

  • bootstrop框架

  • 媒体查询

移动适配方案

  • rem(用flexible方案,flexible.js帮我们计算出1rem 等于多少px)

  • vw和vh

三栏布局

  • flex

  • 定位

  • 浮动

    • 两边浮动中间margin + overflow: hidden(触发BFC)

    • 两边浮动,中间calc()函数

    • 三个部分都浮动(圣杯和双飞翼布局),然后结合margin和定位调整

  • display:table;

  • grid布局

圣杯布局和双飞翼布局

最后我们来总结一下,双飞翼布局其实和圣杯布局的精髓是一样的,都是在三个部分都是浮动的情况下,左右的部分因为父盒子宽度不够被挤下来的问题,通过设置负margin来实现元素的排布。

  • 不同的就是html结构,双飞翼是在middle元素内部又设置了一个milddle-inner并设置它的左右margin,而非圣杯布局的padding,来排除两边元素的覆盖,最后把盒子定位到两侧。

  • 双飞翼布局可以多了一个html结构,但是可以不用设置left,right元素的定位。

JS

JS为何是单线程

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。

注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。

JS数据类型

基本类型分为以下六种:

  • string(字符串)

  • boolean(布尔值)

  • number(数字)

  • symbol(符号)

  • null(空值)

  • undefined(未定义)

  • BigInt(是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示表示任意大的整数。即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。)

引用数据类型:

  • 数组

  • 对象(函数也是对象)

区别:

值类型的变量会保存在 栈内存 中,如果在一个函数中声明一个值类型的变量,那么这个变量当函数执行结束之后会 自动销毁

引用类型的变量名会保存在 栈内存 中,但是变量值会存储在 堆内存 中,引用类型的变量不会自动销毁,当没有引用变量引用它时,系统的 垃圾回收机制 会回收它。

js判断数据类型

判断数据类型方法有很多,实际使用需要根据自己的需求使用最适合自己的方法

1、使用 typeof

测试简单数据类型。对于null及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。

2、使用 obj instanceof Object

测试复杂数据类型,因为instanceof 是用来判断数据是否是某个对象的实例

所以对于 nullundefined 这两个家伙就检测不了

因为原型链继承的关系,instanceof 会把数组都识别为 Object 对象,所有引用类型的祖先都是 Object 对象

3、使用Object.prototype.toString.call

Object.prototype.toString.call() 区分的数据类型适用范围更大,但是无法区分自定义对象类型,区分自定义对象类型使用 instanceof 操作符。

Object.prototype.toString()本身是允许被修改的,而我们目前所讨论的关于Object.prototype.toString()这个方法的应用都是假设toString()方法未被修改为前提的。

因为实例对象有可能会自定义toString()方法,会覆盖Object.prototype.toString(), 所以在使用时,最好加上call()。

4、使用.constructor

constructor不能判断undefined和null,并且使用它是不安全的,因为contructor的指向是可以改变的

js中的length属性

1、length属性常见于字符串和数组,来判断长度。

2、length属性还可以用于函数,来判断函数的长度,即函数中形参的个数。

注意:

  • ...args不计入形参个数

  • 设置了默认值的参数及其之后的所有参数都不计入形参个数,之前的仍旧计入

判断空对象

所谓空对象,就是数组的长度等于0

let obj = {name : '你好'}
//是true为空对象,是false则不是空对象
console.log(JSON.stringify(obj) === '{}');//false
let obj = {}
​
let fn = (obj) => {
    for(let key in obj) {
        return false 
    }
​
    return true
}
//返回false代表不为空,返回true则为空对象
 console.log(fn(obj));//true
let  obj = {name : '1'}
​
//Object.getOwnPropertyNames()获取到对象中的全部属性名,存到一个数组中
let s = Object.getOwnPropertyNames(obj)
console.log(s); //['name']   //为[],代表空对象
let  obj = {name : '1'}
//Object.keys()获取给定对象的所有可枚举属性的字符串数组
let s = Object.keys(obj)
console.log(s);//[ 'name' ]  //若为[],则为空对象
let  obj = {name : '1'}
// 注意for...in 会将对象原型链上的属性也枚举出来,所以要借hasOwnProperty()方法来判断是不是对象本身的属性
// 如果存在返回true,不存在返回false
let fn = (s) => {
    for(let key in s) {
        if(s.hasOwnProperty(key)) {
            return false
        }
        return true
    }
}
console.log(fn(obj));//false   //若是反回true则就是空对象

判断空数组

和判断空对象类似的,我们只要能验证这个对象的keys长度是0,那就是个空对象了。

Array.isArray(arr) & arr.length === 0

ES6新特性

  1. 新的定义变量的方式 let const var 的区别

  2. 模板字符串

  3. class

  4. Promise

  5. async/await

  6. 箭头函数

  7. 新的数组方法 filter some map every forEach reduce Array.from() find findIndex includes flat

  8. 拓展运算符

  9. 赋值解构运算符

  10. Proxy

  11. 对象新增方法 Object.assign() Object.keys()

  12. es6的模块化

  13. Set

  14. Map

ES6 Module 和 CommonJS 的区别

CommonJS 是对模块的浅拷贝;ES6 Module 是对模块的引用,即 ES6。

CommonJS是动态编译,可以放在代码里动态执行;ES6 Module 是静态编译,引用只能放在最前面。

箭头函数和普通函数区别

  1. this指向(普通函数指向调用者,this指向父级作用域的this)

  2. 不可以被当做构造函数

  3. 不可以使用arguments对象,该对象在函数体内不存在,如果要用就用剩余参数替代

  4. 没有prototype属性

new一个对象做了什么事情

  1. new构建函数可以在内存中创建一个空的对象

  2. this会指向刚才创建的空对象

  3. 执行构造函数的代码给这个空对象添加属性和方法

  4. 返回这个对象(所以构造函数不需要return)

构造函数与普通函数的区别

1.构造函数就是一个普通的函数,创建方法和普通函数没有区, 不同的是构造函数习惯上首字母大写。

2.构造函数与普通函数的区别就是调用方式不同,普通函数直接调用,而构造函数使用new关键字调用。

防抖和节流

  • 防抖debounce

    定义:触发高频事件后n秒内函数只会执行最后一次,如果n秒内高频事件再次被触发,则重新计算时间。

    原理:每次触发事件时都取消之前的延时调用方法

    function debounce(fn) {
      let timer = null; // 创建一个标记用来存放定时器的返回值
      return function () {
        // 执行这个函数之前先清掉定时器
        clearTimeout(timer);
        timer = setTimeout(() => {
          // 绑定this的原因是为了让this指向正确
          // 绑定arguments的原因是为了正确使用函数参数位置的事件对象e
          fn.apply(this, arguments);
        }, 500);
      };
    }

    应用场景

    1. 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。

    2. 表单验证

    3. 按钮提交事件。

    4. 浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等

  • 节流throttle

    定义:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

    原理:每次触发事件时都判断当前是否有等待执行的延时函数

    方法一:
    function throttle(func, wait) {
        var prev = 0;
        return function() {
            let now = Date.now();
            if (now - prev > wait) {
                func();
                // 重置起始时间
                prev = now;
            }
        }
    }
    方法二:
    function throttle(fn) {
      // 闭包保存是否可以开启定时器,默认是开启的
      let canRun = true;
      return function () {
        // 如果没有开启就直接返回
        if (!canRun) return;
        // 开启了就立即关闭定时器入口,然后开启定时器
        canRun = false;
        setTimeout(() => {
          fn.apply(this, arguments);
          // 定时器运行后,下次定时器才可开启
          canRun = true;
        }, 500);
      };
    }

    应用场景

    1. 按钮点击事件

    2. 拖拽事件

    3. onScoll

    4. 计算鼠标移动的距离(mousemove)

深拷贝浅拷贝

  • 浅拷贝

    1. 展开运算符...

      const array = [{ type: '' }, '', '']
      const copyArray = [...array]
      ​
      array[0].type = '' // 修改原数组
      console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
    2. Object.assign()

      const array = [{ type: '' }, '', '']
      const copyArray = Object.assign([], array)
      ​
      array[0].type = '' // 修改原数组
      console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
    3. Array.prototype.concat()

      const array = [{ type: '' }, '', '']
      const copyArray = array.concat([])
      ​
      array[0].type = '' // 修改原数组
      console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
    4. Array.prototype.slice()

      const array = [{ type: '' }, '', '']
      const copyArray = array.slice()
      ​
      array[0].type = '' // 修改原数组
      console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
    5. Array.from()

      const array = [{ type: '' }, '', '']
      const copyArray = Array.from(array)
      ​
      array[0].type = '' // 修改原数组
      console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '' }, '', '']
  • 深拷贝

    1、JSON.parse(JSON.stringify(obj))

    • 会忽略undefined Symbol

    • 不能序列化函数

    • 不能解决循环引用的对象

    • 不能正确处理 new Date()

    • 不能处理正则

    2、浅拷贝+递归

    module.exports = function clone(target) {
        if (typeof target === 'object') {
            let cloneTarget = Array.isArray(target) ? [] : {};
            for (const key in target) {
                cloneTarget[key] = clone(target[key]);
            }
            return cloneTarget;
        } else {
            return target;
        }
    };

数组去重

对于 JS 数组去重来说,其实万变不离其中,我简单的总结了以下 4 种去重类型

1、数组元素比较型:

该方法是将数组的值取出与其他值比较并修改数组

  • 双层 for 循环

取出一个元素,将其与其后所有元素比较,若在其后发现相等元素,则将后者删掉

function uniq(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] === arr[j]) {
                arr.splice(j, 1);
                j--;
            }
        }
    }
    return arr;
}

// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN, NaN]
// 与 lodash 结果相比 NaN 没有去掉
复制代码
由于 NaN === NaN 等于 false,所以重复的 NaN 并没有被去掉

  • 排序并进行相邻比较

先对数组内元素进行排序,再对数组内相邻的元素两两之间进行比较,经测试,该方法受限于 sort 的排序能力,所以若数组不存在比较复杂的对象(复杂对象难以排序),则可尝试此方法

function uniq(arr) {
    arr.sort();
    for (let i = 0; i < arr.length - 1; i++) {
        arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
    }
    return arr;
}
// 运行结果
//[[], [], 1, "1", [1], NaN, NaN, {}, {}, { a: 1 }, {}, { a: 1 }, "a", null, undefined];
// 与 lodash 结果相比 NaN 没有去掉,且对象的去重出现问题

同样由于 NaN === NaN 等于 false,所以重复的 NaN 并没有被去掉,并且由于 sort 没有将对象很好的排序,在对象部分,会出现一些去重失效

2、查找数组元素位置型:

该类型即针对每个数组元素进行一次查找其在数组内的第一次出现的位置,若第一次出现的位置等于该元素此时的索引,即收集此元素

  • indexOf

indexOf 来查找元素在数组内第一次出现的位置,若位置等于当前元素的位置,则收集

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr.indexOf(arr[i]) === i) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN

indexOf 采用与 === 相同的值相等判断规则,所以在数组内没有元素与 NaN 相等,包括它自己,所以 NaN 一个都不会被留下

  • findIndex

findIndex 方法来查找元素在数组内第一次出现的位置

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr.findIndex(item => item === arr[i]) === i) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN

结果原理和 indexOf 相同,因为用了 === 的规则来判断元素是否相等,但此方法相当于双层 for 循环

3、查找元素是否存在型:

该方法基本依托 includes 方法来判断对应元素是否在新数组内存在,若不存在则收集

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (!res.includes(arr[i])) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同

includes 方法采用 SameValueZero 判断规则,所以可以判断出并去重 NaN

4、依托数据类型特性型:

该方案依托于数据类型的不重复特性,以达到去重效果

  • Map

Map 类型的数据可以像 Object 一样,在设定元素键值对的时候可以保证键的唯一,并且将键的类型拓展到了基本所有元素,包括对象,在设定好一个唯一键的 Map 数据类型后,再用其自带的 Map.prototype.keys() 方法取到相应的键类数组,最后将类数组进行一次转化即可

function uniq(arr) {
    let map = new Map();
    for (let i = 0; i < arr.length; i++) {
        !map.has(arr[i]) && map.set(arr[i], true);
    }
    return [...map.keys()];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
  • Set

与 Map 类似,但是它里面每一项的值是唯一的,没有重复的值,Set是一个构造函数,用来生成set的数据结构。运用数据类型的特性完成去重,这个方法也是最热门的方法

function uniq(arr) {
    return [...new Set(arr)];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同

原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。

简单地说:原型链就是查找规则,__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

call、bind、apply的含义和区别

  • 区别 : 传参不一样

call()

  • 改变this指向

  • 可以调用函数

  • 参数: 接受的是若干个参数的列表 call(this,1,2,3)

apply()

  • 改变this指向

  • 可以调用函数

  • 参数: 参数的数组 apply(this,[1,2,3])

bind()

  • 只改变this指向,不调用函数,比如用来改变定时器内部的this指向

Class

  1. class本质还是function

  2. 类的所有方法都定义在类的 prototype属性上

  3. 类创建的实例 里面也有 __ proto __ 指向类的 prototype 原型对象

  4. 所以 ES6 的类 他的绝大部分功能 es5 都可以做到 新的 calss 写法 只是让对象原型的写法 更加清晰 更像面向对象编程的语法而已

  5. 所以 ES6 的类 其实就是语法糖

Class和构造函数的区别

  1. 类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)

  2. 类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者可以直接调用

  3. Class不存在变量提升(hoist),这一点与ES5完全不同

  4. ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

js继承的几种方式及其优缺点

原型链继承

function Parent() {
  this.name = 'kevin';
}
​
Parent.prototype.getName = function () {
  console.log(this.name);
}
​
function Child() {
​
}
​
Child.prototype = new Parent();
Child.prototype.constructor = Child;
​
var child1 = new Child();
console.log(child1.getName()) // kevin

缺点:

  1. 在创建 Child 的实例时,不能向Parent传参

  2. 引用类型的属性被所有实例共享

借用构造函数继承

function Parent() {
  this.names = ['kevin', 'daisy'];
}
​
function Child() {
  Parent.call(this);
}
​
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
​
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]

优点:

  1. 可以在 Child 中向 Parent 传参

  2. 避免了引用类型的属性被所有实例共享

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法

  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
​
Parent.prototype.getName = function () {
  console.log(this.name)
}
​
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
​
Child.prototype = new Parent();
Child.prototype.constructor = Child;
​
var child1 = new Child('kevin', '18');
child1.colors.push('black');
​
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
​
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

缺点:

构建原型链的时候,Child.prototype的原型上面会有name、colors两个属性;在创建child1的时候它的实例上也会有name、colors。实例对象child1上的两个属性就屏蔽了其原型对象Child.prototype的两个同名属性。

所以,组合模式的缺点就是在使用子类创建实例对象时,会调用两次父构造函数,其原型链中会存在两份相同的属性/方法。

原型式继承

略。不重要可不说

寄生式继承

略。不重要可不说

寄生组合式继承

对于组合式继承的缺点,思考一下,如何不调用两次父构造函数,不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
​
Parent.prototype.getName = function () {
  console.log(this.name)
}
​
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
​
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
// Child.prototype = new F();
let f = new F()
f.constructor = Child
Child.prototype = f
​
​
​
var child1 = new Child('kevin', '18');
console.log(child1);

最后我们把第三步的方法封装一下

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
​
function prototype(child, parent) {
  var prototype = object(parent.prototype);
  prototype.constructor = child;
  child.prototype = prototype;
}
​
// 当我们使用的时候:
prototype(Child, Parent);

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

ES6类继承extends

ES6继承的原理跟寄生组合式继承是一样的。

闭包

定义

  • 闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包就是将函数内部和函数外部连接起来的桥梁。

优缺点:

  • 能够访问局部变量

  • 保护局部变量

  • 由于闭包会将它的外部函数的作用域也保存在内存中,因此会比其他函数更占用内存,这样的话,如果过度使用闭包,就会有内存泄露的威胁。解决方法——使用完变量后,手动将它赋值为null。

应用:

  • 构造函数的私有属性。由于javascript中天然没有类的实现,某些不希望被外部修改的私有属性可以通过闭包的方式实现

  • 函数防抖、节流

浏览器的缓存机制

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。

  • 强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程

  • 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存,协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。

任务队列(宏/微任务)

首先我们需要明白以下几件事情:

  • JS分为同步任务和异步任务

  • 同步任务都在主线程上执行,形成一个执行栈

  • 所谓 "异步",简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。

  • 主线程之外,有一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。

  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

宏任务

可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

主要包含:script(整体代码)、setTimeout、setInterval、DOM事件

微任务

可以理解是在当前(主线程)任务执行结束后立即执行的任务。也就是说,在当前任务任务后,下一个任务之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

主要包含:Promise.then

运行机制(时间循环)

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(全局Script脚本)

  • 产生的的宏任务和微任务进入各自的队列中

  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

事件委托

利用事件冒泡的原理,将原本绑定给子元素的事件,绑定给父元素,子元素触发的事件冒泡给父元素,让父元素触发事件,执行函数。

事件委托好处:1. 效率好;2. 对于新增的子元素事件依然生效

请求

请求的方式有哪些,应用场景是什么

  • GET

GET请求是向服务端请求获取某个或某些资源(resource),比如查询数据库单个或list数据,服务端成功的话,一般状态码返回200。

  • POST

POST请求是用来向服务端请求新增资源(resource),处理成功的话,服务端一般返回状态码201。

  • PUT

PUT请求一般是用来向服务端请求修改某个已存在的资源(resource),服务端一般返回状态码200,204等。

  • DELETE

DELETE请求一般是用来向服务端请求删除某个已存在的资源(resource),服务端一般返回200,202等。

  • PATCH

PATCH请求一般是对某个资源做局部修改,如个别字段

什么是promise,解决了什么问题

Promise

Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息

  • promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态)

    • 状态一旦改变,就不会再变。创造promise实例后,它会立即执行。

  • resolve reject都是函数, resolve用来处理成功的状态,reject用来处理失败的状态

  • 如何访问promise实例中的数据

    • promise提供了两个方法 .then()处理成功 .catch()处理失败(不会同时存在2中状态)

promise基本概念

  1. Promise 是一个构造函数

  2. promise.prototype上包含一个 .then 方法

  3. .then()方法 可以用来预先指定失败的回调函数

  • p.then(成功的回调函数 ,失败的回调函数) p.then(result=>{},error=>{})

  • 调用p.then()方法时,成功的函数是必选的 失败的回调函数是可选的

谈谈对async/await的理解

async和await

  • await必须结合async使用,async通常不会单独使用

  • async表示这个函数是一个异步函数,它的返回值如果是一个普通数据类型,默认会用Promise将数据包裹

  • await 等待?等待什么?等待的是一个异步的结果

  • 异步和同步从上往下的顺序执行

相对于promise的优点:

  • 解决了地狱回调问题

  • 更简洁 更容易调试 有更好的错误处理 有条件分支处理

  • 处理中间值更简单 更清楚异常堆栈来自哪里

跨越问题如何解决

  • 同源:2个页面的协议/域名/端口均相同为同源

  • 同源策略 :A网站的js不允许和非同源的网站进行资源交互

  • 跨域 :协议/域名/端口不一致时(会被浏览器拦截)

  • 解决方案

  • JSONP 和 CORS和代理服务器

  • CORS:出现的较晚,它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器

    • 原理: 设置允许的响应头(需要浏览器和服务器同时支持).后台里面设置

  • JSONP:出现的早,兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。

    • 原理:根据< script>标签的src属性不受浏览器同源策略的限制

    • 动态创建< script>标签,结合它的src属性传递可查询参数

      • 参数的值是一个函数名(函数名最好随机生成,防止变量污染)

    • 服务器返回的是一段函数执行代码

    缺点是只支持 GET 请求,不支持 POST 请求。

    • 实现过程:

    • script标签里的src属性动态创建和移除

    • 在Jsonp请求时,动态向中append(添加)一个

你可能感兴趣的:(前端面试,前端,面试,职场和发展)