2021最新前端面试题及答案(前端最新2022面试题(JS))
JavaScript
1 、解释一下什么是闭包 ?
闭包:就是能够读取外层函数内部变量的函数 。 闭包需要满足三个条件: 访问所在作用域; 函数嵌套; 在所在作用域外被调用 。 优点: 可以重复使用变量 ,并且不会造成变量污染 。 缺点: 会引起内存泄漏 使用闭包的注意点: 由于闭包会使得函数中的变量都被保存在内存中 ,内存消耗很大,所以不能滥用闭包 ,否则会造成网页的性能问题 ,在IE中可能导致内存泄露 。解决方法是 ,在退出函数之前 ,将不使用的局部变量全部删除 。 闭包会在父函数外部 ,改变父函数内部变量的值 。所以 ,如果你把父函数当作对象
(object)使用 ,把闭包当作它的公用方法(Public Method) ,把内部变量当作它的私有属性(private value) ,这时一定要小心,不要随便改变父函数内部变量的值 。2 、解释一下原型和原型链 ?
原型
原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板 。原型链
对象之间的继承关系通过构造函数的prototype指向父类对象 ,直到指向Object对象为止形成的指向链条 。 通俗讲: 原型链是原型对象创建过程的历史记录。 注:在javascript中 ,所有的对象都拥有一个__proto__属性指向该对象的原型(prototype) 。3、说一下 ES6 中你熟悉的一些内容 ?
class 类的继承ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念
async 、await使用 async/await, 搭配promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async 用于申明一个 function 是异步的 ,而 await 用于等待一个异步方法执行完成
Promise是异步编程的一种解决方案 ,比传统的解决方案(回调函数和事件)更合理 、强大
Symbol是一种基本类型 。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数 ,该函数返回的symbol是唯一的
Proxy代理使用代理(Proxy)监听对象的操作 ,然后可以做一些相应事情
Set是类似于数组的数据集合 ,无序 ,插入删除速度快 ,元素不重复 ,查找速度快。
Map是一个类似对象的数据结构 ,和对象不同的在于它的key可以是任意类型 ,但是对象只能使用string和symbol类型 ,Map的存储关联性更强
生成器函数可以进行阻断函数执行的过程,通过传参可以传入新的值进入函数继续执行 ,可以用于将异步变为阻塞式同步
4 、数组排序的方式 ?
冒泡排序: for(var i=0;i<arr.length-1;i++){ for(var j=0;j<arr.length-i-1;j++){ if(arr[j]>arr[j+1]){ var temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } if(arr[j]===arr[j-1]) i++; } 选择排序: for(var i=0;i<arr.length;i++){ var min=i; for(var j=i+1;j<arr.length;j++){ if(arr[j]<arr[min]) min=j; } if(min!==i){ var temp=arr[i]; arr[i]=arr[min]; arr[min]=temp; } if(arr[i]===arr[i+1])i++; } 快速排序: function quickSort(arr) { if (arr.length <= 1) return arr; var centerIndex = ~~(arr.length / 2); var left = []; var right = []; for (var i = 0; i < arr.length; i++) { if (i === centerIndex) continue; if (arr[i] < arr[centerIndex]) left.push(arr[i]); else right.push(arr[i]); } return quickSort(left).concat(arr[centerIndex], quickSort(right)); }5 、什么是事件轮询(EventLoop) ?
一个用来等待和发送消息和事件的程序结构 。
1 、所有任务都在主线程上执行 ,形成一个执行栈 。 2 、主线程发现有异步任务,如果是微任务就把他放到微任务的消息队列里 ,如果是宏任务就把他放到宏任务的消息队列里。 3 、执行栈所有同步任务执行完毕 。 4 、执行微任务队列 ,之后再执行宏任务队列 。 5 、轮询第4步 。6、数组的一些API, 哪些能够改变原数组, 那些不能 ?
改变原数组的方法: shift() unshift() pop() push() reverse() sort() splice() 不改变原数组的方法: concat() every() filter() forEach() indexOf() join() lastIndexOf() map() some() every() slice() reduce() reduceRight() flat() flatMap() find()7 、for 循环与 forEach 的区别 ?
1.for循环可以使用break跳出循环,但forEach不能 。 2.for循环可以控制循环起点(i初始化的数字决定循环的起点) ,forEach只能默认从索引0开始 。 3.for循环过程中支持修改索引(修改 i) ,但forEach做不到(底层控制index自增 ,无法左右它) 。8 、深浅拷贝 ?
深拷贝: function cloneObject(source, target) { if (target === undefined) { if (Node.prototype.isPrototypeOf(source)) { target = document.createElement(source.nodeName); target.style = source.style.cssText; } else if (source.constructor === Uint8Array) { target = new source.constructor(Array.from(source)); } else if (source.constructor === Date || source.constructor === RegExp || source.constructor === Set || source .constructor === Map) { target = new source.constructor(source); } else if (source.constructor === Function) { var arg = source.toString().match(/\((.*?)\)/)[1]; var content = source.toString().replace(/\n|\r/g, "").match(/\{(.*)\}/)[1]; target = new Function(arg, content) } else { target = new source.constructor(); } } var names = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source)); for (var i = 0; i < names.length; i++) { if (names[i] === "constructor") { Object.defineProperty(target, "constructor", { value: source.constructor }); continue; } var desc = Object.getOwnPropertyDescriptor(source, names[i]); if ((typeof desc.value === "object" && desc.value !== null) || typeof desc.value === "function") { var o = cloneObject(desc.value) Object.defineProperty(target, names[i], { value: o, enumerable: desc.enumerable, writable: desc.writable, configurable: desc.configurable }) } else { Object.defineProperty(target, names[i], desc); } } return target; } 浅拷贝: 1、Object.assign(目标对象 ,源对象) 2 、 var obj1={} for(var key in obj){ obj1[key]=obj[key] } 3 、obj1={...obj};9、url 的组成 ?
http:/https: 协议 www.baidu.com 域名 :8080 端口 /sf/vsearch 路径 ?wd=百度热搜 查询(可有可无) #a=1&b=2 哈希值(可有可无)10 、常见的跨域方式 ?
JSONP:
JSONP是利用外链脚本 ,没有跨源限制的特点 ,来实现跨源请求的一种技术 。 CORS:
cors:跨域资源共享 ,是一种实现跨源请求数据的技术 。这就是跨源问题的解决方案之一 。也是广泛的解决方案。 正向代理
先搭建一个属于自己的代理服务器 1 、用户发送请求到自己的代理服务器 2 、自己的代理服务器发送请求到服务器 3 、服务器将数据返回到自己的代理服务器 4 、自己的代理服务器再将数据返回给用户 反向代理 1 、用户发送请求到服务器(访问的其实是反向代理服务器 ,但用户不知道) 2 、反向代理服务器发送请求到真正的服务器 3 、真正的服务器将数据返回给反向代理服务器 4 、反向代理服务器再将数据返回给用户 通过postMassage ,11、Promise 的使用场景 ?
场景1:获取文件信息 。 场景2:配合AJAX获取信息 场景3:解决回调地狱,实现串行任务队列 。 场景4: node中进行本地操作的异步过程12 、let, const, var 的区别 ?
声明方式 变量提升 暂时性死区 重复声明 初始值 作用域 var 允许 不存在 允许 不需要 非块级 let 不允许 存在 不允许 不需要 块级 const 不允许 存在 不允许 需要 块级13 、对 this 的理解, 三种改变 this 的方式 ?
1.任何情况下直接在script中写入的this都是window。
2.函数中的this 非严格模式:this指向window , 严格模式时:this指向undefined 。
3.箭头函数的this
this都指向箭头函数外上下文环境的this指向4.对象中this
对象属性的this 指向对象外上下文环境的this
对象方法(普通函数)中的this ,指向当前对象(谁执行该方法,this就指向谁)5.回调函数的this指向
1)、 setTimeout ,setInterval回调函数不管是否是严格模式都会指向window 。 2) 、通过在函数内执行当前回调函数 非严格模式:this指向window , 严格模式时:this指向undefined。 3)递归函数中的this 非严格模式:this指向window, 严格模式时:this指向undefined 。 使用arguments0执行函数时 this指向arguments 。 5)事件中的回调函数,this指向事件侦听的对象(e.currentTarget);6 、call ,apply ,bind方法执行时this的指向
如果call,apply,bind传参时 ,第一个参数传入的不是null或者undefined ,传入什么this指向什么 如果第一个参数传入的是null或者undefined ,非严格模式下指向window7、在ES6的类中this的指向
构造函数中的this指向实例当前类所产生的新的实例对象 类中实例化方法中this指向谁执行该方法 ,this指向谁 类中静态方法中this执行该类或者该类的构造函数 类中实例化箭头方法 ,this仍然指向当前类实例化的实例对象8 、ES5的原型对象中this的指向
在原型的方法中 ,this指向实例化当前构造函数的实例化对象(谁执行该方法 ,this指向谁); 三种改变this指向的方式 函数名.call(this,…)this写谁就指谁 。 函数名.apply(this,[参数1 ,参数2,…]) this写谁就指谁 。 函数名. bind (this,1,2,3) this写谁就指谁 。14 、cookie, localStorage,sessionStorage 的区别 ?
存储方式 作用与特性 存储数量及大小
cookie 存储方式 存储用户信息 ,获取数据需要与服务器建立连接 。 以路径存储 ,上层路径不能访问下层的路径cookie,下层的路径cookie可以访问上层的路径cookie 作用与特性 可存储的数据有限 ,且依赖于服务器 ,无需请求服务器的数据尽量不要存放在cookie 中,以免影响页面性能 。 可设置过期时间 。 存储数量及大小 将cookie控制在4095B以内 ,超出的数据会被忽略 。 IE6或更低版本 最多存20个cookie; IE7及以上 版本 多可以有50个; Firefox多 50个; chrome和Safari没有做硬性限制。 cookie最大特征就是可以在页面与服务器间互相传递 ,当发送或者接受数据时自动传递 localStorage 存储客户端信息 ,无需请求服务器 。 数据永久保存 ,除非用户手动清理客户端缓存 。 开发者可自行封装一个方法 ,设置失效时间。 5M左右 ,各浏览器的存储空间有差异 。 任何地方都可以存都可以取 操作简单 sessionStorage 存储客户端信息 ,无需请求服务器 。 数据保存在当前会话 ,刷新页面数据不会被清除 ,结束会话(关闭浏览器 、关闭页面 、跳转页面)数据失效。 5M左右,各浏览器的存储空间有差异 。 同页面不同窗口中数据不会共享15 、输入 url 到打开页面 都做了什么事情 ?
输入URL 访问hosts解析 ,如果没有解析访问DNS解析 TCP握手 HTTP请求 HTTP响应返回数据 浏览器解析并渲染页面16 、原生 ajax 的流程 ?
创建xhr var xhr=new XMLHTTPRequest() 侦听通信状态改变的事件 xhr.addEventListener("readystatechange",readyStateChangeHandler); Method 分为 get post put delete等等 Async 异步同步 name和password是用户名和密码 xhr.open(Method,URL,Async,name,password) 发送内容给服务器 xhr.send(内容) function readyStateChangeHandler(e){ 当状态是4时 ,并且响应头成功200时, if(xhr.readyState===4 && xhr.status===200){ 打印返回的消息 console.log(xhr.response) } }17 、如何实现继承 ?
对于 JavaScript 来说 ,继承有两个要点:
复用父构造函数中的代码 复用父原型中的代码第一种实现复用父构造函数中的代码 ,我们可以考虑调用父构造函数并将 this 绑定到子构造函数 。第一种方法:复用父原型中的代码,我们只需改变原型链即可 。将子构造函数的原型对象的 proto 属性指向父构造函数的原型对象 。
第二种实现
使用 new 操作符来替代直接使用 proto 属性来改变原型链 。第三种实现
使用一个空构造函数来作为中介函数 ,这样就不会将构造函数中的属性混到 prototype 中 function A(x,y){ this.x = x this.y = y } A.prototype.run = function(){} // 寄生继承 二者一起使用 function B(x,y){ A.call(this,x,y) // 借用继承 } B.prototype = new A() // 原型继承 // 组合继承 Function.prototype.extends = function(superClass){ function F(){} F.prototype = superClass.prototype if(superClass.prototype.constructor !== superClass){ Object.defineProperty(superClass.prototype,constructor,{value:superClass}) } let proto = this.prototype this.prototype = new F() let names = Reflect.ownKeys(proto) for(let i = 0; i < names.length;i++){ let desc = Object.getOwnPropertyDescriptor(proto,names[i]) Object.defineProperty(this.prototypr,name[i],desc) } this.prototype.super = function(arg){ superClass.apply(this,arg) } this.prototype.supers = superClass.prototype} 第四种实现
es6类的继承extends 。18 、null 和 undefined 的区别 ?
null是一个表示"无"的对象(空对象指针) ,转为数值时为0; undefined是一个表示"无"的原始值 ,转为数值时为NaN 。
拓展: null表示"没有对象" ,即该处不应该有值 。典型用法是: 作为函数的参数 ,表示该函数的参数不是对象 。 作为对象原型链的终点。 undefined表示"缺少值" ,就是此处应该有一个值 ,但是还没有定义 。典型用法是: 变量被声明了 ,但没有赋值时 ,就等于undefined 。 调用函数时,应该提供的参数没有提供 ,该参数等于undefined。 对象没有赋值的属性 ,该属性的值为undefined 。 函数没有返回值时,默认返回undefined 。19 、函数的节流和防抖 ?
节流 节流是指当一个事件触发的时候,为防止事件的连续频繁触发,设置定时器,达到一种一段事件内只触发一次的效果,在当前事件内不会再次触发,当前事件结束以后,再次触发才有效. function thro(cb,wait){ let timeOut return function(){ if(timeOut) return timeOut = setTimeout(function(){ cb() clearTimeout(timeOut) timeOut = null },wait) } } 防抖 防抖是指当一个事件触发的时候, 为防止频繁触发事件, 设置定时器,以达到一种 频繁触发期间不处理, 只有当最后一次连续触发结束以后才处理 function debounce(cb,wait){ let timer return function(){ clearTimeout(timer) timer = setTimeout(()=>cb(),wait) } }20、什么是 Promise ?
Promise 是异步编程的一种解决方案:从语法上讲 ,promise是一个对象 ,从它可以获取异步操作的消息; 从本意上讲,它是承诺 ,承诺它过一段时间会给你一个结果。 promise有三种状态: pending(等待态) ,fulfilled(成功态) ,rejected(失败态);状态一旦改变 ,就不会再变 。创造promise实例后 ,它会立即执行 promise是用来解决两个问题的: 回调地狱 ,代码难以维护 , 常常第一个的函数的输出是第二个函数的输入这种现象 promise可以支持多个并发的请求 ,获取并发请求中的数据 这个promise可以解决异步的问题 ,本身不能说promise是异步的21 、普通函数与箭头函数的区别 ?
普通函数和箭头函数的区别:
1.箭头函数没有prototype(原型),箭头函数没有自己的this,继承的是外层代码块的this 。 2.不可以当做构造函数,也就是说不可以使用new命令 ,否则会报错的 。 3.不可以使用arguments对象 ,该对象在函数体内不存在 。如果要用,可以用 rest 参数代替 。 4.不可以使用yield命令 ,因此箭头函数不能用作 Generator(生成器) 函数 。 5.因为没有this,所以不能使用call 、bind、apply来改变this的指向 。22 、设计模式有哪些, 分别说一说 ?
共23种设计模式 ,介绍其中6种应用较为广泛的模式 。
发布订阅模式:
这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护 。中介者模式 :
观察者模式通过维护一堆列表来管理对象间的多对多关系 ,中介者模式通过统一接口来维护一对多关系 ,且通信者之间不需要知道彼此之间的关系 ,只需要约定好API即可。代理模式 :
为其他对象提供一种代理以控制对这个对象的访问 。
代理模式使得代理对象控制具体对象的引用 。代理几乎可以是任何对象:文件 ,资源 ,内存中的对象 ,或者是一些难以复制的东西。单例模式 :
保证一个类只有一个实例 ,并提供一个访问它的全局访问点(调用一个类 ,任何时候返回的都是同一个实例) 。工厂模式 :
工厂模式定义一个用于创建对象的接口 ,这个接口由子类决定实例化哪一个类 。该模式使一
个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型装饰者模式 : 装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责(方法或属性) 。与继承相比 ,装饰者是一种更轻便灵活的做法 。
23 、Promsie 和 async/await 的区别和使用 ?
区别:
1)函数前面多了一个async关键字 。await关键字只能用在async定义的函数内 。async函数会隐式地返回一个promise ,该promise的reosolve值就是函数return的值 。 2)第1点暗示我们不能在 外层代码中使用await,因为不在async函数内 。使用: 1.async和await是配对使用的 ,await存在于async的内部 。否则会报错 。 2.await表示在这里等待一个promise返回 ,再接下来执行 。 3.await后面跟着的应该是一个promise对象,(也可以不是 ,如果不是接下来也没什么意义了…)24、谈一谈垃圾回收机制 ?
垃圾回收是动态存储管理技术 ,会自动地释放“垃圾‘’(不再被程序引用的对象) ,按照特定的垃圾收集算法来实现资源自动回收的功能。
回收的两种机制 1.标记清除(make-and-sweep) 2.引用计数 垃圾回收器会按照固定的时间间隔周期性的执行 。25 、数组去重 ?
第一种: for(var i=0;i<arr.length;i++){ for(var j=i+1;j<arr.length;){ if(arr[i]===arr[j]) arr.splice(j,1); else j++; // 核心 } } 第二种: var arr1=[]; xt: for(var i=0;i<arr.length;i++){ for(var j=0;j<arr1.length;j++){ if(arr1[j]===arr[i]) continue xt; } arr1.push(arr[i]); } 第三种: var arr1=[]; for(var i=0;i<arr.length;i++){ if(arr1.indexOf(arr[i])<0) arr1.push(arr[i]) } 第四种: var arr1=[]; for(var i=0;i<arr.length;i++){ if(!(~arr1.indexOf(arr[i]))) arr1.push(arr[i]) } 第五种: var arr1=[]; for(var i=0;i<arr.length;i++){ if(!arr1.includes(arr[i])) arr1.push(arr[i]) } 第六种: arr=[1,2,3,1,2,3,1,2,3] new Set(arr);26 、判断对象为空 ?
第一种 使用JSON.stringify()将对象转换为json字符串; JSON.stringify(obj) === {} 第二种 使用for...in循环遍历对象除Symbol以外的所有可枚举属性 ,当对象有属性存在返回false , 否则返回 true 。 const obj = {} function isObjectEmpty(obj){ for(var key in obj){ return false } return true } console.log(isObjectEmpty(obj)) 第三种 Object.getOwnPropertyNames() 方法会返回该对象所有可枚举和不可枚举属性的属性名(不含Symbol 属性)组成的数组。然后再通过判断返回的数组长度是否为零 ,如果为零的话就是空对象 。 Object.getOwnPropertyNames(obj).length === 0 第四种 Object.keys() 是 ES5 新增的一个对象方法 ,该方法返回一个数组 ,包含指定对象自有的可枚举属性(不 含继承的和Symbol属性) 。用此方法只需要判断返回的数组长度是否为零 ,如果为零的话就是空对象。27 、如何用一次循环找到数组中两个最大的值 ?
var arr=[1,4,10,11,11,2,5,7,2,3,4]; var [max,second]=arr[0]>arr[1] ? [arr[0],arr[1]] : [arr[1],arr[0]]; for(var i=2;i<arr.length;i++){ if(arr[i]>max){ second=max; max=arr[i]; }else if(arr[i]<=max && arr[i]>second){ second=arr[i]; } }28 、new 一个对象的过程 ?
1.开辟一个堆内存,创建一个空对象 2.执行构造函数 ,对这个空对象进行构造 3.给这个空对象添加__proto__属性29 、箭头函数为什么不能用 new ?
因为箭头函数没有prototype也没有自己的this指向并且不可以使用arguments 。
30 、如何实现数组的复制 ?
for循环逐一复制; var arr1=[]; for(var i=0;i<arr.length;i++){ if(i in arr) arr1[i]=arr[i] } …方式 var arr1=[...arr]; slice方法 var arr1=arr.slice(); concat方法 var arr1=arr.concat(); map方法 var arr1=arr.map(item=>item); reduce var arr1=arr.reduce((v,t)=>v.push(t),[])31 、http 的理解 ?
HTTP 协议是超文本传输协议 ,是客户端浏览器或其他程序“请求 ”与 Web 服务器响应之间的应用层通信协议 。 HTTPS主要是由HTTP+SSL构建的可进行加密传输 、身份认证的一种安全通信通道 。32 、http 和 https 的区别 ?
1、https协议需要到ca申请证书,一般免费证书较少 ,因而需要一定费用 。 2 、http是超文本传输协议 ,信息是明文传输,https则是具有安全性的ssl加密传输协议 。 3 、http和https使用的是完全不同的连接方式 ,用的端口也不一样 ,前者是80 ,后者是443 。 4、http的连接很简单 ,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输 、身份认证的网络协议 ,比http协议安全 。33 、git 的常用指令有哪些 ?
git branch 分支查看 git branch branch_1 增加分支 git checkout branch 分支切换 git merge branch_1 合并分支(合并前要切换当前分支至master) git branch -d branch_1 删除分支 git remote 查看当前仓库管理的远程仓库信息 git remote show origin 查看指定的远程仓库的详细信息 git push --set-upstream origin branch_1 第一次将本地分支推到远程仓库 git push <远程主机名> <本地分支名>:<远程分支名> 将本地分支推到远程分支 git pull <远程主机名> <远程分支>:<本地分支> 将远程分支拉到本地分支 git branch -d branch_0 删除本地合并后分支 git brench -D branch_0 删除本地未合并分支 it push origin --delete branch_0 删除远程分支 git restore [filename] 进行清除工作区的改变 git tag 查看标签 git tag v1.0.0 打标签 git push origin v1.0.0 将tag同步到远程服务器34、平时是使用 git 指令还是图形化工具 ?
repository:git库相关操作 ,基本意思就是字面意思 。
1)资源管理器中浏览该Git库工作空间文件 ,省去查找路径不断点击鼠标的操作 。 2)启动Git bash工具(命令行工具)。 3)查看当前分支文件状态 ,不包括未提交的信息 。 4)查看某个分支的文件(弹出框中可选择需要查看的版本 、分支或标签) ,跟上一条差不多,用的比较少 ,可能是没有这方面的额需求 。 5)可视化当前分支历史 、可视化所有分支历史:弹出分支操作历史 ,也就是gitk工具,放到
gitk工具中介绍。 edit:用于操作commit时操作信息输入 ,只能操作文字输入部分 ,你没有看错 。常用的快捷键大家都知道,何必要单独做成基本没啥用的 。本来以为对变更的文件进行批量操作 、本来以为可以对未版本跟踪的文件批量删除 、本来 、 、 、 ,都说了是本来。 Branch:新建分支(需要选择其实版本 ,可以根据版本号 、其他分支或标签来选择) 、检出分支(觉得切换分支更合适)、重命名分支 、删除分支 、当前分支Reset操作(会丢弃所有未提交的变更 ,包括工作区和索引区 ,当然了 ,有弹出框提示危险操作) 。35、Promsie.all() 使用过吗, 它是怎么使用的 ?
promise.all()用于一个异步操作需要在几个异步操作完成后再进行时使用 。 promise.all()接受一个promise对象组成的数组参数 ,返回promise对象 。 当数组中所有promise都完成了 ,就执行当前promise对象的then方法 ,如果数组中有一个promise执行失败了 ,就执行当前promise对象的catch方法 。36 、什么是三次握手和四次挥手 ?
三次握手是网络客户端跟网络服务器之间建立连接,并进行通信的过程 。相当于客户端和服务器之间你来我往的3个步骤 。
第一次握手是建立连接 ,客户端发送连接请求报文 ,并传送规定的数据包; 第二次握手是服务器端表示接收到连接请求报文,并回传规定的数据包; 第三次握手是客户端接收到服务器回传的数据包后 ,给服务器端再次发送数据包 。这样就完成了客户端跟服务器的连接和数据传送 。四次挥手表示当前这次连接请求已经结束 ,要断开这次连接 。
第一次挥手是客户端对服务器发起断开请求, 第二次握手是服务器表示收到这次断开请求 , 第三次握手是服务器表示已经断开连接 第四次握手是客户端断开连接。37 、for in 和 for of 循环的区别 ?
`for in` 用于遍历对象的键(`key`) ,`for in`会遍历所有自身的和原型链上的可枚举属性 。如果是数组 ,for in会将数组的索引(index)当做对象的key来遍历 ,其他的object也是一样的 。 `for of`是`es6`引入的语法 ,用于遍历 所有迭代器iterator ,其中包括`HTMLCollection`,`NodeList`,`Array` ,`Map` ,`Set` ,`String`,`TypedArray` ,`arguments`等对象的值(`item`)。38、async/await 怎么抛出错误异常 ?
如果可能出错的代码比较少的时候可以使用try/catch结构来了处理 ,如果可能出错的代码比较多的时候,可以利用async函数返回一个promise对象的原理来处理 ,给async修饰的函数调用后返回的promise对象 ,调用catch方法来处理异常 。39 、 函数式编程和命令式编程的区别 ?
命令式编程(过程式编程) : 专注于 ”如何去做 ”,这样不管 ”做什么 ” ,都会按照你的命令去做 。解决某一问题的具体算法实现。 函数式编程:把运算过程尽量写成一系列嵌套的函数调用 。 函数式编程强调没有 ”副作用 ” ,意味着函数要保持独立 ,所有功能就是返回一个新的值 ,没有其他行为 ,尤其是不得修改外部变量的值 。 所谓 ”副作用 ” ,指的是函数内部与外部交互(最典型的情况 ,就是修改全局变量的值) ,产生运算以外的其他结果 。40 、http 常见的响应状态码 ?
100——客户必须继续发出请求 101——客户要求服务器根据请求转换HTTP协议版本 200——交易成功 201——提示知道新文件的URL 202——接受和处理 、但处理未完成 203——返回信息不确定或不完整 204——请求收到 ,但返回信息为空 205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件 206——服务器已经完成了部分用户的GET请求 300——请求的资源可在多处得到 301——删除请求数据 302——在其他地址发现了请求数据 303——建议客户访问其他URL或访问方式 304——客户端已经执行了GET ,但文件未变化 305——请求的资源必须从服务器指定的地址得到 306——前一版本HTTP中使用的代码 ,现行版本中不再使用 307——申明请求的资源临时性删除 400——错误请求,如语法错误 401——请求授权失败 402——保留有效ChargeTo头响应 403——请求不允许 404——没有发现文件 、查询或URl 405——用户在Request-Line字段定义的方法不允许 406——根据用户发送的Accept拖 ,请求资源不可访问 407——类似401 ,用户必须首先在代理服务器上得到授权 408——客户端没有在用户指定的饿时间内完成请求 409——对当前资源状态,请求不能完成 410——服务器上不再有此资源且无进一步的参考地址 411——服务器拒绝用户定义的Content-Length属性请求 412——一个或多个请求头字段在当前请求中错误 413——请求的资源大于服务器允许的大小 414——请求的资源URL长于服务器允许的长度 415——请求资源不支持请求项目格式 416——请求中包含Range请求头字段 ,在当前请求资源范围内没有range指示值 ,请求也不包含If-Range请求头字段 417——服务器不满足请求Expect头字段指定的期望值 ,如果是代理服务器 ,可能是下一级服务器不能满足请求 500——服务器产生内部错误 501——服务器不支持请求的函数 502——服务器暂时不可用 ,有时是为了防止发生系统过载 503——服务器过载或暂停维修 504——关口过载 ,服务器使用另一个关口或服务来响应用户 ,等待时间设定值较长 505——服务器不支持或拒绝支请求头中指定的HTTP版本41 、 什么是事件流以及事件流的传播机制 ?
事件触发后 ,从开始找目标元素 ,然后执行目标元素的事件,再到离开目标元素的整个过程称之为事件流 。
W3C标准浏览器事件流的传播分为3个阶段:捕获阶段 、目标阶段 、冒泡阶段
捕获阶段指找目标元素的过程 ,这个找的过程 ,是从最大的document对象到html,再到body , 。 。 。直到目标元素 。
找到目标元素后 ,调用执行他绑定事件时对应的处理函数,这个过程被称之为目标阶段 。
当目标元素的事件执行结束后 ,再从目标元素 ,到他的父元素。 。 。body 、html再到document的过程 ,是冒泡阶段。
42 、模块化语法 ? commonJS AMD CMD ES6 Module
commonJS是nodejs自带的一种模块化语法 ,将一个文件看做是一个模块 ,可以将文件中导出的时候 ,被另一个文件导入使用 。导出使用:module.exports导出 。导入使用:require函数导入。
AMD是社区开发的模块化语法 ,需要依赖require.js实现 ,分为定义模块 ,导出数据和导入模块,使用数据 。AMD语法的导入是依赖前置的 ,也就是说 ,需要用到的文件需要在第一次打开页面全部加载完成,造成的后果就是首屏加载很慢 ,后续操作会很流畅 。
CMD是玉伯开发的模块化语法 ,需要依赖sea.js实现,也分为模块定义导出 ,和模块导入使用数据 。CMD语法可以依赖前置 ,也可以按需导入 ,缓解了AMD语法的依赖前置 。
ES6的模块化语法 ,类似于commonJS的语法 ,分为数据导出和数据导入 ,导入导出更加灵活 。
43、 什么是懒加载和预加载 ?
懒加载:懒加载也叫延迟加载 ,延迟加载网络资源或符合某些条件时才加载资源 。常见的就是图片延时加载 。
懒加载的意义:懒加载的主要目的是作为服务器前端的优化 ,减少请求数或延迟请求数 。
懒惰实现方式: 1.第一种是纯粹的延迟加载 ,使用setTimeOut或setInterval进行加载延迟. 2.第二种是条件加载,符合某些条件 ,或触发了某些事件才开始异步下载 。 3.第三种是可视区加载 ,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现 ,一般会在距用户看到某图片前一定距离遍开始加载 ,这样能保证用户拉下时正好能看到图片。预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染 。
两者的行为是相反的 ,一个是提前加载 ,一个是迟缓甚至不加载 。懒加载对服务器前端有一定的缓解压力作用 ,预加载则会增加服务器前端压力。预加载应用如广告弹窗等 。
44 、token 一般存放在哪里 ? 为什么不存放在 cookie 内 ?
token一般放在本地存储中 。token的存在本身只关心请求的安全性 ,而不关心token本身的安全 ,因为token是服务器端生成的 ,可以理解为一种加密技术。但如果存在cookie内的话 ,浏览器的请求默认会自动在请求头中携带cookie ,所以容易受到csrf攻击 。45 、 less 和 sass 的区别 ?
编译环境不一样 ,sass是服务器端处理的,可以用Ruby、node-sass来编译;less需要引入less.js来处理输出 ,也可以使用工具在服务器端处理成css ,也有在线编译的 。 变量定义符不一样,less用的是@ ,而sass用$ 。 sass支持分支语句 ,less不支持44 、浏览器的同源策略机制 ?
同源策略,又称SOP ,全称Same Origin Policy ,是浏览器最基本的安全功能 。站在浏览器的较短看网页 ,如果网络上的接口可以不受限制 、无需授权随意被人调用 ,那将是一个非常严重的混乱场景 。浏览器为了安全有序 ,内部实现了同源策略 。 同源策略 ,指的是浏览器限制当前网页只能访问同源的接口资源 。 所谓同源 ,指当前页面和请求的接口 ,两方必须是同协议、且同域名 、且同端口 。只要有一个不相同 ,则会受到浏览器额约束,不允许请求 。 但当一个项目变的很大的时候 ,将所有内容放在一个网站或一个服务器中会让网站变的臃肿且性能低下 ,所以,在一些场景中 ,我们需要跨过同源策略 ,请求到不同源的接口资源,这种场景叫跨域。 跨域大致有3种方案:jsonp
这种方式是利用浏览器不限制某些标签发送跨域请求 ,例如link 、img 、iframe 、script 。通常请求请求回来的资源要在js中进行处理 ,所以jsonp跨域是利用script标签进行发送 ,且这种请求方式只能是get请求 。
cors
这种方式是让接口资源方面进行授权 ,授权允许访问。在接口资源处添加响应头即可通过浏览器的同源策略 ,响应头具体的键值对如下:
{Access-Control-Allow-Origin: *}proxy
这种方式属于找外援的一种方式 ,浏览器只能限制当前正在打开的web页面发送请求 ,但无法限制服务器端请求接口资源 。所以我们可以将请求发送到自己服务器 ,然后自己服务器去请求目标接口资源 ,最后自己服务器将接口资源返回给当前页面,类似于找外援代替自己请求目标接口资源 。
这种方式通常要对服务器进行代理配置 ,需要对apache服务器 、nginx服务器 、nodejs服务器进行配置。
45 、 浏览器的缓存有哪些 ? 什么时候使用强制缓存 ? 什么时候使用协商缓存 ?
当我们访问同一个页面时 ,请求资源 、数据都是需要一定的耗时,如果可以将一些资源缓存下来 ,那么从第二次访问开始 ,就可以减少加载时间,提高用户体验 ,也能减轻服务器的压力 。 浏览器缓存分为强缓存和协商缓存 ,当存在缓存时 ,客户端第一次向服务器请求数据时 ,客户端会缓存到内存或者硬盘当中 ,当第二次获取相同的资源 ,强缓存和协商缓存的应对方式有所不同 。 强缓存:当客户端第二次向服务器请求相同的资源时 ,不会向服务器发送请求 ,而是直接从内存/硬盘中间读取 。缓存由服务器的响应头里 cache-control 和 expires 两个字段决定 协商缓存:当客户端第二次向服务器请求相同的资源时 ,先向服务器发送请求"询问"该请求的文件缓存在bend与服务器相比是否更改,如果更改 ,则更新文件 ,如果没有就从内存/硬盘中读取 。协商缓存由 last-modified 和 etag两个字段决定46 、 数组方法 forEach 和 map 的区别 ?
forEach和map都是循环遍历数组中的每一项 。forEach() 和 map() 里面每一次执行匿名函数都支持3个参数:数组中的当前项item,当前项的索引index,原始数组input 。匿名函数中的this都是指Window 。只能遍历数组 。 他们的区别是:forEach没有返回值,但map中要有返回值 ,返回处理后的所有新元素组成的数组 。47、 什么是函数作用域 ? 什么是作用域链 ?
作用域就是在代码执行过程中 ,形成一个独立的空间,让空间内的变量不会邪泄露在空间外 ,也让独立空间内的变量函数在独立空间内运行 ,而不会影响到外部的环境。 作用域分为全局作用域和局部作用域 ,也就是本来有一个巨大的空间 ,空间内定义的函数内部 ,就形成了一个独立的小空间 ,全局作用域是最大的作用域 。 但是当独立空间内的数据不能满足需求时 ,是可以从外部获取数据的 ,也就是说这样的独立空间之间是可以有层级关系的 ,外部的空间不可以从内部的空间获取数据,但内部的空间可以 。当子级空间在父级空间中获取数据的时 ,父级空间没有的话 ,父级空间也会到他的父级空间中查找数据,这样形成的链式结构叫作用域链。 当将一个变量当做值使用时 ,会先在当前作用域中查找这个变量的定义和数据 ,如果没有定义的话,就会去父级作用域中查找 ,如果父级作用域中有的话就使用这个值 ,如果父级作用域中也没有的话 ,就通过父级作用域查找他的父级作用域 ,直到找到最大的作用域-全局 ,如果全局也没有就报错 。 当将一个变量当做数据容器存储 ,也就是给变量赋值的时候 ,也要先在自己作用域中查找变量的定义 ,如果没有就在上一级作用域中查找 ,直到全局,如果全局作用域中也没有这个变量的定义 ,就在全局定义这个变量并赋值 。48 、 ES6 中 Set 和 Map 的原理 ?
Set 是无重复值的有序列表。根据 `Object.is()`方法来判断其中的值不相等 ,以保证无重复 。 Set 会自动移除重复的值,因此你可以使用它来过滤数组中的重复值并返回结果 。 Set并不是数组的子类型 ,所以你无法随机访问其中的值 。但你可以使用`has()` 方法来判断某个值是否存在于 Set 中 ,或通过 `size` 属性来查看其中有多少个值 。 Set 类型还拥有`forEach()`方法,用于处理每个值 Map 是有序的键值对 ,其中的键允许是任何类型 。与 Set 相似 ,通过调用 `Object.is()`方法来判断重复的键 ,这意味着能将数值 5 与字符串 "5" 作为两个相对独立的键 。使用`set()` 方法能将任何类型的值关联到某个键上 ,并且该值此后能用 `get()` 方法提取出来 。Map 也拥有一个 `size` 属性与一个 `forEach()` 方法 ,让项目访问更容易 。49 、 0.1 + 0.2 为什么不等于 0.3, 在项目中遇到要怎么处理 ?
计算机内部存储数据使用2进制存储 ,两个数字进行的数学运算 ,首先是将这两个数字以2进制形式 ,存储在计算机内部 ,然后在计算机内部使用两个2进制数字进行计算,最后将计算结果的2进制数字转为10进制展示出来 。 由于10进制的小数在转2进制的时候 ,规则是小数部分乘以2 ,判断是否得到一个整数,如果得到整数 ,转换完成;如果没有得到整数 ,则继续乘以2判断。所以,0.1和0.2在转换2进制的时候 ,其实是一个无限死循环 ,也就是一直乘以2没有得到整数的时候 ,但计算机内部对于无线死循环的数据 ,会根据一个标准保留52位 。也就是说 ,计算机内部在存储0.1和0.2的时候 ,本来就不精准 ,两个不精准的小数在计算后 ,距离精准的结果是有一定误差的 。 项目中碰到这种情况 ,有3种处理方法: 将小数乘以10的倍数,转为整数 ,然后计算 ,计算完成后,再缩小10的倍数 ,例如: var result = ((0.1 * 10) + (0.2 * 10)) / 10 // result === 0.3
使用数字的toFixed方法 ,强制保留小数点后多少位,例: var result = (0.1 + 0.2).toFixed(2) // result === 0.30
自定义数字运算方法 ,当需要进行数学运算的时候 ,不直接进行 ,调用自定义的方法进行 ,例:(加法封装) function add(...args){ var num = args.find(item => { if(item != 0 && !item){ throw new Error("数学运算要使用数字") } }) var arr = args.map(item => { var index = (item+).indexOf(.) if(index >= 0){ return (item+).split(.)[1].length } }) arr = arr.filter(item => item) if(arr.length){ var max = Math.max(...arr) var data = args.map(item => item * Math.pow(10, max)) var data.reduce((a, b) => a + b) / Math.pow(10, max) }else{ var data = args return data.reduce((a, b) => a + b) } } // 调用使用: var num1 = add(0.1, 0.2) console.log(num1); // 0.3 var num2 = add(1, 2) console.log(num2); // 3 var num3 = add(1, 2.1) console.log(num3); // 3.1
50、 什么是模块化思想 ?
就是JS中将不同功能的代码封装在不同的文件中, 再互相引用时不会发生命名冲突的一种思想, 大多数情况下, 一个文件就是一个模块 模块化的实现 ,有多种方案:CommonJS:
CommonJS是nodejs中使用的模块化规范
在 nodejs 应用中每个文件就是一个模块 ,拥有自己的作用域 ,文件中的变量 、函数都是私有的 ,与其他文件相隔离。模块导出:module.exports=数据 ,模块导入:require(模块文件路径)ES6的模块化:
模块功能主要由两个命令构成:export和import 。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能 。
一个模块就是一个独立的文件。该文件内部的所有变量 ,外部无法获取 。如果你希望外部能够读取模块内部的某个变量 ,就必须使用export关键字输出该变量 。下面是一个 JS 文件,里面使用export命令输出变量 。
AMD (Asynchronous Module Definition):
特点: 提倡依赖前置 ,在定义模块的时候就要声明其依赖的模块:导入模块require([module],callback);定义模块:define(模块名称, 函数) 。
CMD (Common Module Definition):
CMD规范是国内SeaJS的推广过程中产生的 。提倡就近依赖(按需加载) ,在用到某个模块的时候再去require 。定义模块:define(function (require, exports, module) {}),使用模块:seajs.use()
51 、 说说怎么用js 写无缝轮播图
将所有需要轮播的内容动态复制一份 ,放在原本的容器中 ,加定时器让整个容器中的内容滚动轮播 ,当内容轮播到left值为-原本的内容宽度时 ,快速将内容切换到left值为0的状态 。52、 JS 如何实现多线程 ?
我们都知道JS是一种单线程语言 ,即使是一些异步的事件也是在JS的主线程上运行的(具体是怎么运行的 ,可以看我另一篇博客JS代码运行机制) 。像setTimeout 、ajax的异步请求 ,或者是dom元素的一些事件 ,都是在JS主线程执行的 ,这些操作并没有在浏览器中开辟新的线程去执行,而是当这些异步操作被操作时或者是被触发时才进入事件队列 ,然后在JS主线程中开始运行 。 首先说一下浏览器的线程 ,浏览器中主要的线程包括,UI渲染线程 ,JS主线程 ,GUI事件触发线程,http请求线程。 JS作为脚本语言 ,它的主要用途是与用户互动 ,以及操作DOM 。这决定了它只能是单线程 ,否则会带来很复杂的同步问题 。(这里这些问题我们不做研究) 但是单线程的语言 ,有一个很致命的确定。如果说一个脚本语言在执行时 ,其中某一块的功能在执行时耗费了大量的时间 ,那么就会造成阻塞 。这样的项目 ,用户体验是非常差的 ,所以这种现象在项目的开发过程中是不允许存在的 。 其实JS为我们提供了一个Worker的类 ,它的作用就是为了解决这种阻塞的现象。当我们使用这个类的时候,它就会向浏览器申请一个新的线程 。这个线程就用来单独执行一个js文件 。 var worker = new Worker(js文件路径); 那么这个语句就会申请一个线程用来执行这个js文件 。这样也就实现了js的多线程 。53 、 闭包的使用场景 ?
一个函数被当作值返回时 ,也就相当于返回了一个通道 ,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来 ,数据结构中的值在外层函数执行时创建 ,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去 ,这些值得以保存下来 。而且无法直接访问 ,必须通过返回的函数 。这也就是私有性 。 本来执行过程和词法作用域是封闭的 ,这种返回的函数就好比是一个虫洞 ,开了挂 。 闭包的形成很简单 ,在执行过程完毕后 ,返回函数 ,或者将函数得以保留下来 ,即形成闭包 。 防抖: function debounce(fn, interval) { let timer = null; // 定时器 return function() { // 清除上一次的定时器 clearTimeout(timer); // 拿到当前的函数作用域 let _this = this; // 拿到当前函数的参数数组 let args = Array.prototype.slice.call(arguments, 0); // 开启倒计时定时器 timer = setTimeout(function() { // 通过apply传递当前函数this ,以及参数 fn.apply(_this, args); // 默认300ms执行 }, interval || 300) } } 节流: function throttle(fn, interval) { let timer = null; // 定时器 let firstTime = true; // 判断是否是第一次执行 // 利用闭包 return function() { // 拿到函数的参数数组 let args = Array.prototype.slice.call(arguments, 0); // 拿到当前的函数作用域 let _this = this; // 如果是第一次执行的话,需要立即执行该函数 if(firstTime) { // 通过apply ,绑定当前函数的作用域以及传递参数 fn.apply(_this, args); // 修改标识为null ,释放内存 firstTime = null; } // 如果当前有正在等待执行的函数则直接返回 if(timer) return; // 开启一个倒计时定时器 timer = setTimeout(function() { // 通过apply,绑定当前函数的作用域以及传递参数 fn.apply(_this, args); // 清除之前的定时器 timer = null; // 默认300ms执行一次 }, interval || 300) } } 迭代器: var arr =[aa,bb,cc]; function incre(arr){ var i=0; return function(){ //这个函数每次被执行都返回数组arr中 i下标对应的元素 return arr[i++] || 数组值已经遍历完; } } var next = incre(arr); console.log(next());//aa console.log(next());//bb console.log(next());//cc console.log(next());//数组值已经遍历完 缓存: var fn=(function(){ var cache={};//缓存对象 var calc=function(arr){//计算函数 var sum=0; //求和 for(var i=0;i<arr.length;i++){ sum+=arr[i]; } return sum; } return function(){ var args = Array.prototype.slice.call(arguments,0);//arguments转换成数组 var key=args.join(",");//将args用逗号连接成字符串 var result , tSum = cache[key]; if(tSum){//如果缓存有 console.log(从缓存中取:,cache)//打印方便查看 result = tSum; }else{ //重新计算 ,并存入缓存同时赋值给result result = cache[key]=calc(args); console.log(存入缓存:,cache)//打印方便查看 } return result; } })(); fn(1,2,3,4,5); fn(1,2,3,4,5); fn(1,2,3,4,5,6); fn(1,2,3,4,5,8); fn(1,2,3,4,5,6); getter和setter: function fn(){ var name=hello setName=function(n){ name = n; } getName=function(){ return name; } //将setName ,getName作为对象的属性返回 return { setName:setName, getName:getName } } var fn1 = fn();//返回对象,属性setName和getName是两个函数 console.log(fn1.getName());//getter fn1.setName(world);//setter修改闭包里面的name console.log(fn1.getName());//getter 柯里化: function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber(test1) // true hasNumber(testtest) /%2创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!