导图社区 JS面试题思维导图
JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。下图详细的梳理了JS面试题,有需要的朋友收藏下图吧!
编辑于2021-07-05 21:42:37JS
闭包
什么是闭包?
闭包就是能够读取其他函数内部变量的函数。 当外部函数返回之后,内部函数可以访问外部函数的属性或者方法。 优点:可以在函数外部访问函数内部的变量,可以把局部变量驻留在内存中,不面使用全局变量。 闭包的缺点:使用闭包会有一块空间内存长期被占用,而不被释放,滥用闭包函数会造成内存泄露。 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包常见用途: 原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。 创建特权方法用于访问控制 事件处理程序及回调
哪些操作会造成内存泄漏?开发中如果遇到内存泄露,该如何解决?
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。 解决方法:将暴露在外部的闭包变量置为null 出现闭包的情况: 1. setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。 2. 闭包 3. 控制台日志 4. 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
js的垃圾回收机制?
js中的内存管理是自动执行的,而且是不可见的。我们创建函数,对象...都需要内存。垃圾收集器固定一段时间收集一次。js中最常见的回收方式就是标记清除 工作原理:当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记离开环境的就回收内存。 工作流程: 1.垃圾回收器,在运行时给内存中所有变量都加上标记 2.去掉环境中的变量及其引用的变量的标记 3.再被加上标记的就是准备删除的变量 4.垃圾回收器运行结束,释放被占用内存空间 代码回收规则如下: 1.全局变量不会被回收。 2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。 3.只要被另外一个作用域所引用就不会被回收
Ajax
Ajax 是什么? 如何创建一个 Ajax?
ajax是一种在无需重新加载整个页面(刷新页面)的情况下,能够更新部分页面的技术 Ajax的最大的特点是可以实现异步通信效果,实现页面局部刷新,带来更好的用户体验;按需获取数据,节约带宽资源. 优点: 1.无刷新更新数据。 2.异步与服务器通信 3.前端和后端负载平衡 4.页面与应用相分离 缺点: 1、ajax不支持浏览器back 按钮。 2、安全问题, AJAX 暴露了与服务器交互的细节。 3、对搜索引擎的支持比较弱。 4、不能很好地支持移动设备 先创建XHR对象即XMLHttpRequest() 然后open准备发送,open中有三个参数一是提交方式get和post,二是接口地址,三是同步和异步 第三步是用send发送 第四步再发送的过程中通过onreadystatechange来监听接收的回调函数,可以通过判断readyState==4和status==200来判断是否成功返回,然后通过responseText接收成功返回的数据
同步和异步的区别?
同步的思想是:所有的操作都做完才返回给用户。这样用户在现在等待时间太长,给客户一种卡死的感觉。这种情况下,用户不能关闭界面,如果关闭了,程序就中断了。 异步就是将用户请求放在消息队列,并反馈给用户,程序已经启动,你可以关闭浏览器了。然后程序再去慢慢的写入程序库,这就是异步,但是用户没有卡死的感觉。 同步就相当于客户端给服务端发送请求,客户端在等待服务端响应的同时,不能再做任何事情,用户等待时间太长,体验很差。 异步就相当于客户端给服务端发送请求,客户端再等待服务端响应的同时,还能再做其他事情,既节约了时间,又提升了效率。 异步解决方案: 1.回调函数 2.promise(重点掌握) 3.generator(了解) 4.async和await(重点掌握)
如何解决跨域问题?(jsonp的原理)
动态创建script标签,利用了src天然的跨域属性,指定对方的接口地址,将请求发送给对方,找到数据后通过回调函数将数据返回。
页面编码和被请求的资源编码如果不一致如何处理?
get请求encodeURIComponent post请求不需要进行编码
JavaScript 的同源策略
同源策略是一种约定,是浏览器最核心也是最基本的安全功能。同源指的是:协议,域名,端口相同,同源策略会阻止一个域的javascript脚本和另一个域的内容进行交互。
GET 和 POST 的区别,何时使用 POST?
get 快 小1KB url后传参 不安全 会在记录中存在 post 慢 大2M http的请求体中 相对安全 不会在历史中产生 1. 无法使用缓存文件(更新服务器上的文件或数据库) 2. 向服务器发送大量数据(POST 没有数据量限制) 3. 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
http 常见的状态码有那些?
状态码就是浏览器向服务器发送请求时,服务器返回的一种状态标识。http状态码是有3个十进制数注册的,第一个十进制数定义了状态码的类型,例如: 1**表示服务器成功接收客户端的请求,需要请求继续操作 2**表示请求成功被处理 3**表示重定向 4**表示客户端错误 5**表示服务端错误 常见的状态码:200(请求成功) 301(资源重定向) 404(客户端请求资源不存在) 500(服务器内部错误)
解析JSON字符串
使用 eval() 或者 JSON.parse() 鉴于安全性考虑,推荐使用 JSON.parse()更靠谱,对 数据的安全性更好。 (new function('return' + str))()
说一下你对跨域的理解?
在客户端使用ajax发送异步请求时会出现跨域的问题,这是因为受到浏览器同源策略的限制。同源策略是一种约定,他是浏览器最基本也是最核心的安全功能。同源策略会阻止一个域的js脚本与另一个域的内容进行交互。当一个请求url的协议、域名、端口三者之间有一个与当前页面的url不同就是跨域。 解决方案: 在客户端可以使用代理或JSONP的方式来解决 在服务端设置Access-Control-Allow-Origin解决。 如果要跟本解决,还需要在服务端设置CORS,服务端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的,如果浏览器检测到相应的位置,就可以允许Ajax进行跨域的访问。
表单可以跨域吗?
表单不存在跨域问题的 因为要保持兼容性,当请求到另一个域名后,原页面的脚本无法获取到新页面的内容,提交的form表单数据不需要返回,所以浏览器认为是安全的,而ajax需要返回的数据,浏览器认为不安全,所以会阻止这种请求 浏览器同源策略的本质上是,一个域名的js在未经允许的情况下,不可以读取另一个页面的内容,但浏览器并不阻止你向另一个域名发送请求。使用form提交表单,请求是可以发送出去的,但是在当前页面没办法读取响应结果而已
javascript 的本地对象、内置对象和宿主对象
本地对象为独立于宿主环境的 ECMAScript 提供的对象,包括 Array Object RegExp 等 可以 new 实例化的对象 内置对象为 Gload,Math 等不可以实例化的(他们也是本地对象,内置对象是本地对象 的一个子集) 宿主对象为所有的非本地对象,所有的 BOM 和 DOM 对象都是宿主对象,如浏览器自带的 document,window 等对象
请说出三种减低页面加载时间的方法
1、压缩 css、js 文件 2、合并 js、css 文件,减少 http 请求 3、外部 js、css 文件放在最底下 4、减少 dom 操作,尽可能用变量替代不必要的 dom 操作
为什么利用多个域名来存储网站资源会更有效?
确保用户在不同地区能用最快的速度打开网站,其中某个域名崩溃用户也能通过其他域名访问网站,并且不同的资源放到不同的服务器上有利于减轻单台服务器的压力。
ajax的工作原理
首先创建一个XMLHttpRequest对象,通过这个对象与服务器建立连接,将请求发送给服务器,通过事件监听请求的状态和响应的状态,最后将响应的数据通过回调函数返回给前端。
什么是Promise?
promise是ES6提出的是异步编程的一种解决方案,通过promise对象可以获取异步操作的消息。 promise有三种状态,pending、resolved、rejected promise用来解决两个问题: 解决回调地狱的问题。当第一个函数的返回值是第二个函数的参数,而且回调嵌套很多层,就会造成回调地狱。 可以支持多个并发的请求,获取并发请求中的数据 promise对象的方法 then() : 当promise对象返回resolve状态时,可以调用then方法 catch() : 当promise对象返回reject状态时,可以调用 catch方法 promise的静态方法: Promise.all() 后端代理: Jsonp、Xhr2、Nginx
谈谈你对 Promise 的理解? 和 ajax 有关系么?
Promise : 是ES6提出的异步编程解决方案。 Promise和ajax没有半毛钱直接关系.promise只是为了解决"回调地狱"而诞生的; 平时结合 ajax是为了更好的梳理和控制流程. 异步编程中有哪些问题? 回调嵌套会产生回调地狱 异步程序中如果有依赖问题。 promise对象的方法 then() : 当promise对象返回resolve状态时,可以调用then方法 catch() : 当promise对象返回reject状态时,可以调用 catch方法 promise有三状态: resolved : 成功状态 rejected : 失败状态 pending : 进行中状态 promise的静态方法: Promise.all() 后端代理: Jsonp、Xhr2、Nginx
promise、async有什么区别?
promise是ES6新增的解决异步回调问题的方法,避免出现回调地域。async/await是编写异步编程的新方式,使用这种方式可以让异步看起来像同步。而且async/await是基于promise实现的,它不能用于普通的回调函数。 使用async/await可以减少代码量,不需要写.then函数,不需要写匿名函数处理promise的resolve值,避免了嵌套代码。
继承
原型链
每一个函数都有一个属性prototype,通过这个属性可以找到该函数的原型对象,每一个原型对象都有一个属性constructor,通过这个属性可以找到该原型对象的构造函数 每一个对象都有一个__proto__属性,通过这个属性可以找到该对象的原型对象,原型对象通过__proto__属性,找到该原型对象的父级原型对象,像这样通过__proto__属性依次向上查找的过程,就叫做原型链 作用:是js中继承的基础,js的继承就是基于原型的继承
JavaScript中如何实现继承?
js中继承的方式有很多, 构造函数继承,通过父类的构造函数来增加子类实现继承,只能继承父类的实例属性和方法,不能继承原型属性和方法,每实例一个对象,就重新开辟一次空间,所以造成大量内存空间浪费,影响性能,所以不推荐使用。 原型链继承,将父类的实例作为子类的原型实现继承,但无法实现多继承,并且无法传参,所以也不推荐。 还有实例继承和拷贝继承,都有一定的缺陷。 推荐混合原型继承和混合寄生式继承。
es6中和原型一样用来继承的class和继承是怎么实现的?
class Point { // ... } typeof Point // "function",类的数据类型就是函数 Point === Point.prototype.constructor // true,类本身就指向构造函数 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致,不使用new会报错。 类的所有方法都定义在类的prototype属性上面。 class Point { constructor() { // ... } toString() { // ... } toValue() { // ... } } // 等同于 Point.prototype = { constructor() {}, toString() {}, toValue() {}, }; //Object.assign方法可以很方便地一次向类添加多个方法 Object.assign(Point.prototype, { toString(){}, toValue(){} }); 类不存在变量提升(hoist),这一点与 ES5 完全不同。 Class 可以通过extends关键字实现继承。 class ColorPoint extends Point {}//ColorPoint继承了Point类所有的属性和方法 super关键字,表示父类的构造函数,用来新建父类的this对象。 子类必须在constructor方法中调用super方法,否则新建实例时会报错,只有调用super之后,才可以使用this关键字。 区别: ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。 ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。 Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。 (1)子类的_proto_属性,表示构造函数的继承,总是指向父类。 (2)子类prototype属性的_proto_属性,表示方法的继承,总是指向父类的prototype属性。 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。 Object.getPrototypeOf方法判断,一个类是否继承了另一个类。
ES6
ES6新增特性?
新增两个变量let和const 字符串方面新增模板字符串 函数方面新增箭头函数和函数的默认参数 在赋值运算方面新增解构赋值 数组方面新增方法:from() of() find() findIndex() fill() 新增class类。继承等新的语法 异步编程方面新增promise和async 新增模块化的解决方案,import和exports 在数据结构方面新增set和map
es6中const、let、var之间的区别?
不能变量提升,先声明后调用 重复作用域下,不能重复声明一个变量 不再是window对象的属性 声明变量产生了块作用域,在块作用域中声明的变量,只能在块作用域中使用
ES6箭头函数和function的区别?
声明写法不同 没有this指向,他指向是所在的function或其他作用域中的this 不能创建构造函数 不能做变量提升
Set和Map数据结构
set和map都是ES6新增的,set是一种无序不重复的集合,set和map在很大程度上都很相似,但是map是用键值对的形式来存储数据的 set和map都有一个属性size,用于返回实例成员的总数 set和map他们都有对数据的增删改遍历的方式 add set get clear has delete keys values entries forEach
this指向
this始终指向的都是调用它的对象 对象中的方法中的this,指向的是调用它的对象; eval中的this指向调用上下文的this; apply和call中的this指向参数中的this; 在全局函数、匿名函数、定时器中的this绑定的都是全局对象window;
ES6中数组新增了哪些方法?
... from() of() find() findindex() fill() includes() keys() vaules() entries()
深拷贝和浅拷贝
说一下call、apply和bind的区别?
这三个方法都是用来改变函数内部的this指向的 call(对象,参数,参数,……) : 返回对象 apply(对象,数组|arguments) : 返回对象 bind(对象,参数,参数,……) :返回函数 bind、apply、call还有个很大的区别就是,bind不会立即调用,其他两个方法会立即调用。
symbol
symbol类型是ES6新增的原始数据类型,表示独一无二的值。Symbol函数接受字符串作为参数,参数仅表示Symbol值得描述,相同的参数返回值也不一样。 可以使用Symbol来作为对象属性名,或者使用Symbol来替代常量,Symbol也可以用来定义类的私有属性和方法。
暂时性死区
http
线程和进程
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位,一个进程是由多个线程组成的,线程是一个进程中代码的不同执行路线。 进程之间相互独立,但是同一进程下的各个线程之间共享程序的内存空间以及一些进程的资源。一个进程中的线程在其他进程中是不可见的。 线程的上下文切换比进程的上小文切换要快。 例如:一个360是一个进程,360下面的功能就是一个一个的线程。
http和https的区别
简单来说,http是超文本传输协议,https是加密的http。 详细说, http超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,他是互联网上应用最广泛的传输协议,所有的WWW文件都必须遵守这个标准。设计http的初衷是为了提供一种发布和接收HTML页面的方法。 HTTPS是一种通过计算机网络进行安全通信的传输协议,经由http进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。
常见的HTTP请求头有哪些?
Accept:浏览器可以接收的媒体类型; Accept-Encoding:浏览器申明自己接收的编码方法; Accept-Language:浏览器申明自己接收的语言; Host:请求报头域主要用于指定被请求资源的Internet主机和端口号; Referer:当浏览器向服务器发送请求时,Referer会告诉服务器我是从哪个页面链接过来的; cookie:用来存储一些用户信息以便让服务器辨别用户的身份; Content-Type:请求体的MIME类型(用于POST和PUT请求中);
三次握手四次挥手
怎么添加、移除、复制、移动和查找节点?
创建元素节点 document.createElement(元素名) 创建文本节点 document.createTextNode(文本内容) 创建文档碎片 document.createDocumentFragment() 添加 父节点.appendChild(子节点) 删除 父节点.removeChild(子节点) 当前节点.remove() 复制 父节点.cloneNode([true]) 插入 当前节点.insertBefore(新节点,指定节点) 替换 父节点.replace(新节点,旧节点) 查找 document.getElementById(id) document.getElementsByName('name属性') document.getElementsByClassName('class属性') document.getElementsByTagName('标签') document.querySelector('选择器') document.querySelectorAll('选择器')
cookie
分别介绍一下cookie、sessionStorage和localStorage?
cookie是一小段文本信息,伴随着用户请求和页面在服务器和浏览器之间传递。因为浏览器是无状态的,对于浏览器发出的多次请求,web服务器无法区分是否是来自同一浏览器,此时需要额外的数据用于维持回话。 sessionStorage和localStorage是H5中新增的Web存储方式。 sessionStorage用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问,会话结束数据随之销毁,它并非持久化的本地存储。 localStorage用于持久化的本地存储,除非主动删除数据,否则数据永远不会过期。 localStorage sessionStorage cookie 大小 5M 5M 4KB 站内数量 无限 无限 50条 生存周期 永久 会话结束时 可以设置有效期 安全性 隐私不可见 隐私不可见 隐私可见 不能被爬到 不能被爬到 爬得到
字符串有哪些方法?
查替截转 包查重模展 indexOf lastIndexOf charAt charCodeAt replace substring substr slice toUpperCase toLowerCase spilt includes startsWith endsWith repeat `${}` ...
数组中有哪些方法?
增删改截拼复排转 查遍或与滤 unshift push shift pop splice reverse sort slice concat filter等
浏览器内核
IE trident -ms- 火狐 gecko -moz- 谷歌 webkit=>blink -webkit- 欧朋 presto=>webkit=>blink -o- 苹果 webkit -webkit-
说一下节流和防抖?
防抖和节流属于性能优化的范畴。
事件委托事件冒泡事件捕获
null和undefined有哪些差异?
相同点: 在 if判断语句中,值都默认为 false; 大体上两者都是代表无,具体看差异; 差异: null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number); undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined; null是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象); 设置为null的变量或者对象会被内存收集器回收;
设计模式
最常用的有单例模式、工厂模式、适配模式等,也会用到观察者模式,一般在实现双向数据绑定的前端框架中,会用到订阅发布模式,例如Vue.js的响应式中,就使用了订阅发布模式。
观察者和订阅发布模式的区别?
观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。
性能优化
客户端入手: 压缩代码(JS/CSS),压缩图片 合并一些小图片(css sprite) 若是打包的代码尽可能切割成多个 chunk,减少单一 chunk过大 静态文件采用 cdn 引入 HTTP的缓存头使用的合理 减小第三方库的依赖 对于代码应该考虑性能来编写,比如使用requestAnimationFrame绘制动画,尽可能减少页面重绘(DOM 改变) 渐进升级,引入preload这些预加载资源 看情况用service worker来缓存资源(比如移动端打算搞 PWA) 从服务端入手: 带宽,域名解析, 多域名解析等 页面做服务端渲染,减小对浏览器的依赖(不用客户端解析) 渐进升级,比如引入 HTTP2(多路复用,头部压缩这些可以明显加快加载速度)
代码题
数组中排序的方法有几种?
//冒泡排序 var arr = [1,5,3,6,1,5,4,2,3]; function fnArrFromSomleToBig(arr){ for(var i = 1;i < arr.length;i ++){ for(var j = 0;j < arr.length;j ++){ if(arr[j] > arr[j + 1]){ var t = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = t; } } } return arr; } console.log(fnArrFromSomleToBig(arr)); //选择排序 var arr = [1,5,3,6,1,5,4,2,3]; function fnArrFromSomleToBig(arr){ for(var i = 0;i < arr.length - 1;i ++){ for(var j = i + 1;j < arr.length;j ++){ if(arr[i] > arr[j]){ var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } } return arr; } console.log(fnArrFromSomleToBig(arr)); //sort排序 var arr = [1,5,3,6,1,5,4,2,3]; arr.sort(function(a,b){ return a - b; }) console.log(arr);
如何去掉字符串中的空格?
replace正则匹配方法 var str = ' 12 34 '; var str2 = str.replace(/\s*/g,""); consloe.log(str2); str.trim()方法 var str = ' 12 34 '; var str2 = str.trim(); consloe.log(str2);
判断一个字符串出现次数最多的字符,统计这个次数并输出?
var str = 'asnfoinsdpnf'; var new_str = {}; for(var i = 0;i < str.length;i ++){ if(!new_str[str.charAt(i)]){ new_str[str.charAt(i)] = 1; }else{ new_str[str.charAt(i)] ++; } }; var iMax = 0; var iIndex = ''; for(var i in new_str){ if(new_str[i] > iMax){ iMax = new_Str[i]; iIndex = i; } } console.log('出现次数最多的是:' + iIndex + '\n出现次数:' + iMax);
使用ES6的方法进行数组的求和
//for var arr = [1,2,3,4,5]; var sum = 0; for(var i = 0;i < arr.length;i ++){ sum += arr[i]; } console.log(sum); //forEach map filter arr.forEach(item => sum += item); arr.map(item => sum += item); arr.filter(item => sum += item); //some arr.some(item => {sum += item});
如何实现数组的去重?
indexOf方法 var arr = [1,5,3,2,4,5,1,2,3,4,5,3]; function fnArrNoRepeat(arr){ var list = []; for(var i = 0;i < arr.length;i ++){ if(list.indexOf(arr[i]) === -1){ list.push(arr[i]) } } return list; } console.log(fnArrNoRepeat(arr)); set方法 function fnArrNoRepeat(arr){ return[...new Set(arr)] } console.log(fnArrNoRepeat(arr));
扁平化一个嵌套的数组
let arrays = [1, [2, [3, [4, 5]]], 6]; 需求:多维数组=>一维数组 第一种方法:直接调用arr的flat方法 第二种方法:正则表达式 let str = JSON.stringify(arrays); arr = str.replace(/(\]|\[)/g, '').split(',');//["1", "2", "3", "4", "5", "6"] 第三种:正则表达式 str = str.replace(/(\[|\])/g, ''); str = '[' + str + ']'; ary = JSON.parse(str);//[1, 2, 3, 4, 5, 6] 第四种:递归 let result = []; function fn(array) { for (let i = 0; i < array.length; i++) { let item = array[i]; if (Array.isArray(array[i])) { fn(item); } else { result.push(item); } } } fn(arrays) 第五种:reduce const flatten = (arr) => { return arr.reduce((prev, next) => { return prev.concat(Object.prototype.toString.call(next) === '[object Array]' ? flatten(next): next) }, []) } 第六种:…扩展运算符 【注意】:扩展运算符(…)内部使用for…of循环 arrays = [].concat(...arrays); }//[1, 2, 3, 4, 5, 6]