HTML和CSS篇: 2024高频前端面试题 HTML 和 CSS 篇-CSDN博客
Vue2 和 Vue3 篇: 2024高频前端面试题 Vue2 和 Vue3 篇-CSDN博客
一. JavaScript篇
1. 数据类型有哪些
1) 基本数据类型
数值(Number)、字符串(String)、布尔值(Boolean)、Undefined、Null、Symbol、BigInt
可能问:Symbol、BigInt 的使用场景
2) 引用数据类型
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)
3) 存储区别
基本数据类型存储在栈中
引用类型的对象存储于堆中
2. 检测数据类型有哪些方法
1)typeof
除了对象、数组、null检测为object,其他可以正确检测其类型
console.log(typeof 2); // number console.log(typeof true); // boolean console.log(typeof 'str'); // string console.log(typeof function(){}); // function console.log(typeof undefined); // undefined console.log(typeof {}); // object console.log(typeof []); // object console.log(typeof null); // object
2)instanceof
只能正确判断引用数据类型,而不能判断基本数据类型 (返回的是布尔值)
console.log(2 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true
3)constructor
都可以正确判断其类型 (判断不了null、undefined)
console.log((2).constructor === Number); // true console.log((true).constructor === Boolean); // true console.log(('str').constructor === String); // true console.log(([]).constructor === Array); // true console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true
4)Object.prototype.toString.call ( obj )
使用 Object 对象的原型方法 toString 来判断数据类型
let a = Object.prototype.toString; console.log(a.call(2)); console.log(a.call(true)); console.log(a.call('str')); console.log(a.call([])); console.log(a.call(function(){})); console.log(a.call({})); console.log(a.call(undefined)); console.log(a.call(null));
3. 操作数组会改变和不会改变原数组的方法有哪些
1) 改变原数组
添加元素类(返回新的长度):
push( ) 把元素添加到数组尾部
unshift( ) 在数组头部添加元素
删除元素类(返回的是被删除的元素):
pop( ) 移除数组最后一个元素
shift( ) 删除数组第一个元素
颠倒顺序:
reverse( ) 在原数组中颠倒元素的顺序
插入、删除、替换数组元素(返回被删除的数组):
splice(a, b, c…n)
a 代表要操作数组位置的索引值,必填
b 代表要删除元素的个数,必须是数字,可以是0,如果没填就是删除从a到数组的结尾
c…n 代表要添加到数组中的新值
排序:
sort( ) 对数组元素进行排序
2) 不改变原数组
concat() 连接两个或更多数组,返回结果
slice() 截取一段数组,返回新数组
join() 把数组的所有元素放到一个字符串
toString() 把数组转成字符串
indexOf() 搜索数组中的元素,并返回他所在的位置
filter() 挑选数组中符合条件的并返回符合要求的数组
every() 检测数组中每个元素是否都符合要求
some() 检测数组中是否有元素符合要求
4. 判断数组的方式有哪些方法
1)通过 Object.prototype.toString.call ( obj ) 判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array' //true Object.prototype.toString.call(obj).indexOf('Array') > -1 //true
2)instanceOf
console.log(arr instanceOf Array) //true
3)constructor
arr.constructor.toString( arr ).indexOf('Array') > -1 //true
4)通过ES6的 Array.isArray( arr ) 做判断
Array.isArray( arr ) //true
5)通过原型链做判断
arr.__proto__ === Array.prototype //true
6)通过Array.prototype.isPrototypeOf( obj )
Array.prototype.isPrototypeOf( obj ) //true
5. 遍历数组的方法有哪些
1)for循环 2)for…in 3)for…of 4)forEach
for in 和 for of 都可以循环数组,for in 输出的是数组的索引,而for of 输出的是数组的每一项的值
6. 遍历对象的方法有哪些
1)Object.keys( obj ) 2)for…in 3)Object.getOwnPropertyNames( obj )
for in遍历的是数组的索引,对象的属性,以及原型链上的属性
7. forEach和Map的区别
1)forEach 返回值为 undefined,会修改原数组,map 有返回值,会返回新的数组,不会修改原数组
2)map 返回的是新数组,可以进行链式调用,而 forEach 不能
3)map 的执行速度比forEach的快
8. 数组去重有哪些方法
1)利用ES6的Set去重,适配范围广,效率一般,书写简单
function unique(arr) { return [...new Set(arr)] }
2)任意数组去重,适配范围广,效率低
function unique(arr) { var result = []; // 结果数组 for (var i = 0; i < arr.length; i++) { if (!result.includes(arr[i])) { result.push(arr[i]); } } return result; }
8. null 和 undefined 的区别
null 和 undefined不能通过 == 来判断。
undefined
这个变量从根本上就没有定义
一般变量声明了但还没有定义的时候会返回 undefined,转化数值时为NaN
隐藏式 空值
null
这个值虽然定义了,但它并未指向任何内存中的对象
主要用于给一些可能会返回对象的变量赋值,作为初始化 ,转化数值时为 0
声明式 空值
undefined==null 返回 true
9. “ ===”、“ ==” 的区别?
==:
如果操作数相等,则会返回 true
两个都为简单类型,字符串和布尔值都会转换成数值,再比较
简单类型与引用类型比较,对象转化成其原始类型的值,再比较
两个都为引用类型,则比较它们是否指向同一个对象
null 和 undefined 相等
存在 NaN 则返回 false
===:只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型
区别:
相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
9. JS 的作用域类型
在 JavaScript 里面,作用域一共有 4 种:全局作用域,局部作用域、函数作用域以及 eval 作用域。
全局作用域:这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
局部作用域:当使用 let 或者 const 声明变量时,这些变量在一对花括号中存在局部作用域,只能够在花括号内部进行访问使用。
函数作用域:当进入到一个函数的时候,就会产生一个函数作用域。函数作用域里面所声明的变量只在函数中提供访问使用。
eval 作用域:当调用 eval( ) 函数的时候,就会产生一个 eval 作用域。
10. 对作用域链的理解
一般情况使用的变量取值是在当前执行环境的作用域中查找,如果当前作用域没有查到这个值,就会向上级作用域查找,直到查找到全局作用域,这么一个查找的过程我们叫做作用域链
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到
window
对象即被终止,作用域链向下访问变量是不被允许的简单的说,
作用域就是变量与函数的可访问范围
,即作用域控制着变量与函数的可见性和生命周期
11. 原型,原型链 ? 有什么特点?
1)原型:
显式原型:
每一个类(构造函数)都有一个显示原型prototype(本质就是个对象)
隐式原型:
每一个对象都有一个隐式原型__proto__
显式原型与隐式原型的关系:
类显式原型的prototype等于其创建的实例的隐式原型__proto__
作用:
1.数据共享 节约内存内存空间
2.实现继承
2)原型链:
当在实例化的对象中访问一个属性时,首先会在该对象内部(自身属性)寻找,如找不到,则会向其__proto__指向的原型中寻找,如仍找不到,则继续向原型中__proto__指向的上级原型中寻找,直至找到或Object.prototype.__proto__为止(值为null),这种链状过程即为原型链。
3)原型特点:
JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
11. JS 中继承实现的几种方式
原型链继承
借用构造函数继承
组合模式(又被称之为伪经典模式)
寄生组合式继承(圣杯模式)
12. 对闭包的理解
1)原理:
闭包就是一个有权访问另外一个函数作用域中变量的函数。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。一般就是一个函数A,return其内部的函数B, 被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个函数B的变量背包,这个变量背包在A函数外部只能通过B函数访问,并且函数A执行结束后这个变量背包也不会被销毁,所以垃圾回收机制不会将当前变量回收掉的,有可能会带来内存损耗( 解决:闭包不在使用时,要及时释放。将引用内层函数对象的变量赋值为null)。
2)为什么使用闭包
因为局部变量无法长久保存和共享,而全局变量会造成变量污染,所以需要一种机制既可以长久保存又不会全局污染,因而闭包就产生了,好处就是可以设计私有的方法和变量。
3)场景
setTimeout
原生的setTimeout传递的第一个函数不能传参,可以通过闭包的形式实现传参
function fun1(a) { return function fun2() { conlog.log(a) } } let fun = fun1(1) setTimeout(fun, 1000) // 输出1
防抖&节流
封装私有变量
// 创建计时器 function fun1() { let sum = 0 let obj = { inc: function() { sum++ return sum } } return obj } let fun = fun1() console.log(fun.inc()) // 1 console.log(fun.inc()) // 2 console.log(fun.inc()) // 3
12. 什么是javascript内存泄漏
简单理解:无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃。
常见的内存泄漏:
- 意外的全局变量(通常是变量未被定义或者胡乱引用了全局变量)
- 计时器(启动循环定时器后不清理)
- 闭包
- 事件监听未被移除
13. 宏任务 与 微任务
宏任务(macro - task)和微任务(micro - task)都是异步任务,宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。常见的有:
1) 宏任务:
script脚本的执行(全局任务)、setTimeout 、setInterval 、setImmediate、MessageChannel 、postMessage 、I/O 操作、UI 渲染(rendering)
2) 微任务:
promise 的回调( Promise.[ then/ catch/ finally ] )、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver(浏览器环境)
14. Js 事件循环的理解
前提: JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
异步任务分为微任务和宏任务:
微任务: 一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
- Promise
- MutaionObserver 监听dom发生改变的
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务: 宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
执行顺序:
- 先执行同步代码,
- 遇到异步宏任务则将异步宏任务放入宏任务队列中,
- 遇到异步微任务则将异步微任务放入微任务队列中,
- 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
- 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
- 一直循环直至所有任务执行完毕。
15. JS 延时加载的方式有哪些
async:js脚本下载和html解析是同步的,执行js脚本的时候html是停止解析的,不能保证加载的顺序(谁先加载完谁先执行)
defer:js脚本下载和html解析是同步的,等html全部解析完,才去执行js代码,按照顺序执行js脚本
动态创建DOM方式:
动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本( 即document.createElement(‘script’)
让 js 最后加载 :将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行
使用setTimeout延迟方法:设置一个定时器来延迟加载js脚本文件
16. 深拷贝与浅拷贝
1) 深拷贝:
深拷贝直接拷贝对象到一块新的内存区域,然后把新对象的指针指向这块内存,新对象指向了新的地址
a) Json.parse 和 Json.stringfy 相互转换
//_tmp和result是相互独立的,没有任何联系,有各自的存储空间。 let deepClone = function (obj) { let _tmp = JSON.stringify(obj);//将对象转换为json字符串形式 let result = JSON.parse(_tmp);//将转换而来的字符串转换为原生js对象 return result; }; let obj1 = { gita: { age: 20, class: 1502 } }; let test = deepClone(obj1); console.log(test); //test:{gita:{age:20, class:1502}} test.gita.age = 21; console.log(test); //test:{gita:{age:21, class:1502}} console.log(obj1); //obj1:{gita:{age:20, class:1502}} age 不被改变了
缺陷:
1、如果存在时间对象,时间对象变成了字符串。
2、如果有RegExp、Error 对象,则序列化的结果将只得到空对象。
3、如果有函数,undefined,则序列化的结果会把函数, undefined 丢失。
4、如果有NaN、Infinity 和-Infinity,则序列化的结果会变成null。
5、JSON.stringify() 只能序列化对象可枚举的自有属性。如果obj 中的对象是有构造函数生成的, 则使用JSON.parse( JSON.stringify(obj) ) 深拷贝后,会丢弃对象的constructor。
6、如果对象中存在循环引用的情况也无法正确实现深拷贝。
b) 递归方法
function deepClone(obj) { let newObj = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === 'object') { for(let key in obj){ if (obj[key] && typeof obj[key] === 'object'){ //判断对象的这条属性是否为对象 newObj[key] = deepClone(obj[key]); //若是对象进行嵌套调用 }else{ newObj[key] = obj[key] } } return newObj; //返回深度克隆后的对象 }else { return obj } } // 原对象 var obj = {a:1,b:2}; // 创建深拷贝对象 var OBJ = deepClone(obj);
2) 浅拷贝:
浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝,复制的对象和原对象都指向同一个地址
a) Object.assign
let outObj = { inObj: {a: 1, b: 2} } let newObj = Object.assign( {}, outObj ) newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}} <!-- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。 然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。 -->
b) 扩展运算符
let outObj = { inObj: {a: 1, b: 2} } let newObj = {...outObj} newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}} <!-- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。 它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。 -->
17. 事件模型
js中事件执行的整个过程称之为事件流,分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
事件捕获(capturing):
当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标(target):
当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件冒泡(bubbling):
从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。
所有的事件类型都会经历事件捕获但是只有部分事件会经历事件冒泡阶段,例如submit事件就不会被冒泡。
DOM事件流:同时支持两种事件模型:捕获型事件和冒泡型事件
阻止冒泡:
- IE9+,其他主流浏览器 event.stopPropagation();
- 火狐未实现 event.cancelBubble = true;
- 不建议滥用,jq 中可以同时阻止冒泡和默认事件 return false;
阻止默认事件:
- 全支持 event.preventDefault();
- 该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。 event.returnValue=false;
- 不建议滥用,jq 中可以同时阻止冒泡和默认事件 return false;
18. 什么是事件代理
事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。
顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
使用事件代理的好处是可以提高性能。
可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
可以实现当新增子对象时无需再次对其绑定
19. Ajax原理,ajax优缺点?
Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。
XMLHttpRequest是ajax的核心机制
优点:
通过异步模式,提升了用户体验.
优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
Ajax可以实现动态不刷新(局部刷新)
缺点:
安全问题 AJAX暴露了与服务器交互的细节。
对搜索引擎的支持比较弱。
不容易调试。
20. ajax过程?
- 创建XMLHttpRequest对象,也就是创建一个异步调用对象.
- 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
- 设置响应HTTP请求状态变化的函数.
- 发送HTTP请求.
- 获取异步调用返回的数据.
- 使用JavaScript和DOM实现局部刷新.
20. ajax解决浏览器缓存问题
ajax形成的原因就是让我们在不刷新页面的情况下就能获取数据,也可以在不刷新页
面的情况下来提交数据。这样就很有利于用户的体验。当ajax异步获取数据后,其实
会在内存中缓存一份该数据,所以当我们不刷新的时候,内存中就永远存在该数据。
导致如果我们请求的url不变的情况下,可能就直接向内存中请求数据,所以数据可能
就不会发生改变。
下面主要有以下几种解决方案。
1、在XMLHttpRequest对象发送请企之前,可以设置请求头。 ajaxObject.setRequestHeader("If-motified-since", 0)。 2、在XMLHttpFRequest对象发送之前,可以设置请求头。 ajaxObject.setRequestHeader("Cache-control", "no-cache") 3、可以在url后面加上一个随机数 "fresh=" + Math.random() 4、可以在url后面加上时间戳 "nowtime" + new Date().getTime() 5、如果使用的是jquery,可以这样设置$.ajaxSetup({"cache": false})
21. 用js递归的方式写1到100求和?
function add(num1, num2) { const num = num1 + num2; if(num2 === 100) { return num; } else { return add(num, num2 + 1) } } var sum = add(1, 2);
22. 说说 Javascript 数字精度丢失的问题,如何解决?
例子:0.1+0.2===0.3 =>false 涉及IEE754标准
问题原因:
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差解决:
使用 toFixed() 方法:将浮点数转化为一个指定位数小数的字符串形式
使用第三方库,Math.js、BigDecimal.js
23. 如何判断一个对象为空对象
通过
Object.keys(obj)
方法获取对象的所有属性名,并判断属性数量是否为 0 来实现let obj = {'name':'zs'} Object.keys(obj).length //1 let objs = {} Object.keys(objs).length //0
24. DOM常见的操作有哪些
创建节点
createElement 创建新元素,接受一个参数,即要创建元素的标签名
查询节点
querySelector 传入任何有效的css 选择器,即可选中单个 DOM元素(首个) 如果页面上没有指定的元素时,返回 null
querySelectorAll 返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表
更新节点
innerHTML 不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
innerText
style dom对象.style.样式属性 = ‘’
添加节点
innerHTML
appendChild 把一个子节点添加到父节点的最后一个子节点
insertBefore(新dom,指定dom对象) 把子节点插入到指定的位置的前面
setAttribute 在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值
删除节点
removeChild 拿到父节点,要删除的节点dom对象。父.removeChild(子)
25. 说说你对BOM的理解,常见的BOM对象你了解哪些?
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象 。
浏览器的全部内容可以看成DOM,整个浏览器可以看成BOM。
BOM对象:
- window: Bom的核心对象是window,它表示浏览器的一个实例 。 在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象
- location:获取url地址信息
- navigator: 对象主要用来获取浏览器的属性,区分浏览器类型
- screen: 保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度
- history: 主要用来操作浏览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转
26. BOM和DOM区别?
BOM(浏览器对象):
与浏览器交互的方法和对象
- BOM是浏览器对象模型,它指的是将浏览器当作一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口
- BOM的核心是window,而window对象具有双重角色,它既是js访问浏览器窗口的一个接口,又是一个全局对象(Global)
- 这就意味着网页中定义的任何对象、变量和函数,都会作为全局对象的一个属性或者方法存在
DOM(文档对象模型):
处理网页内容的方法和接
DOM是文档对象模型,它指的是把文档当作一个对象来对待,这个对象主要定义了处理网页的内容和接口
27. javascript的内存(垃圾)回收机制
- 垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
标记清除方法(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数方法(reference counting)
- 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是 1;
- 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1;
- 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减 1;
- 当引用次数变成 0 时,说明没办法访问这个值了;
- 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
28. javascript 代码中的"use strict";是什么意思?说说严格模式的限制
- use strict是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为
- 限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性
- 不能使用with语句
- 禁止this指向window
二. ES6篇
1. 新增语法
1.可定义常量的 const 和 块级变量 let 关键字 2.模板字符串 ` ` 3.展开运算符 … 4.块级作用域 { } 5.箭头函数 ()=> {} 6.属性简写 { a: a} ==> { a } 7.解构赋值 let [a, b, c] = [1, 2, 3]; let obj = { b: 10 } const { b } = obj 8.新增了promise 9.新增了模块化 10.新增Set、Map数据结构
2. 暂时性死区
在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
3. var、let、const 区别
Var:作用域为全局作用域和函数作用域,没有块级作用域,支持变量提升
Let:作用域为块级作用域,不支持变量提升,不允许重复声明,存在暂时性死区
Const:作用域为块级作用域,不支持变量提升,不允许重复声明,存在暂时性死区,(一旦声明一个变量就不能改变,否则报错)
4. 对箭头函数的理解
箭头函数是ES6中的提出来的,它不同于传统JavaScript中的函数,它不可以使用arguments参数,没有prototype显式原型属性,也没有属于自己的
this
指向,它所谓的this
是捕获其所在上下文的this
值作为自己的this
值,并且由于没有属于自己的this
,所以是不会被new调用的
5. 对this对象的理解
this
是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
6. this指向一般有几种情况
1) 函数调用模式,全局作用域或者普通函数(包括普通函数、定时器函数、立即执行函数)自执行中this指向全局对象window
函数的形式调用时,this指向window
//全局作用域
console.log(this);//Window
//普通函数
function fn(){
console.log(this); //Window
}
fn(); //函数加括号调用叫函数自执行,函数自执行时,内部的this指向顶层对象/window
2) 方法调用模式,以方法的形式调用时,this 指向调用方法的那个对象
//以对象字面量的方式创建一个对象
var obj={
name:'terry',
sayName: function(){
console.log(this);
} //将函数作为一个对象属性的值来调用(即以方法的形式调用)
}
obj.sayName(); // ===>此时会输出obj这个对象
3) 构造器调用模式,以构造函数的形式调用时,this 指向实例对象
// 构造一个存放人信息的函数
function Person(name,age,gender){
this.name=name,
this.age=age,
this.gender=gender,
this.sayName=function(){
alert(this.name)
}
};
var per = new Person('terry',18,'女') //定义一个新的对象,将函数赋值给它
per.sayName() //此时会输出'terry',
4) apply 、 call 和 bind 调用模式,使用 call 、bind和 apply 调用时,this 指向指定的那个对象
//创建一个函数
function fun(a,b){
console.log('a='+a);
console.log('b='+b)
console.log(this.name) //这里大家也可以打印console.log(this),最后输出的是obj这个对象;
//this.name输出的是obj这个对象的name属性的值。
}
//创建一个对象
var obj={
name:'obj',
};
//用对象来调用函数fun,并传入实参call()
fun.call(obj,1,2); //输出的结果a=1 b=2 obj
//apply() 方法
fun.apply(obj,[1,2]); //输出的结果也是a=1 b=2 obj
7. call、apply、bind 的用法以及区别
相同点:
三个方法都是挂载在Function对象原型上的方法,因此调用他们的对象必须是一个函数,这三个方法都是用来改变绑定函数执行时 this 的指向,同时支持传参
不同点:
call、apply的区别:接受参数的方式不一样,apply的第二个参数接受的数组
bind 返回值是一个函数,不会立即执行,需要调用,传参和 call 一样。而 apply、call 立即执行
1) call:
call函数主要接受2种参数,第一是this指向的转移对象,如果不传参数,或者第一个参数是null或nudefined,this都指向window(非严格模式)。接下来的参数则是函数可能会遇到的可选参数。
const A = { name : 'apple', showName: function(param){ console.log(this.name,param) // pear ppp } } let pear = {name: 'pear'}; A.showName.call(pear,'ppp')
2) apply:
apply的用法其实和call基本没啥区别,主要的区别在于对参数的传递,call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数。
const A = { name : 'apple', showName: function(param1,param2,param3){ console.log(this.name,param1,param2,param3) } } let pear = {name: 'pear'}; A.showName.apply(pear,['ppp','kkk','sss']); // pear ppp kkk sss
3) bind:
bind相比于call而言,并不是马上调用函数和直接改变函数的上下文,而是返回改变上下文之后的函数。就是不会立即执行,需要调用函数。
const A = { name : 'apple', showName: function(param1){ console.log(this.name,param1) } } let pear = {name: 'pear'} const method = A.showName.bind(pear,'aa'); //method(); // undefined
8. new操作符的实现步骤
- 创建一个新的空对象
- 将构造函数的作用域赋给新对象(也就是将新对象的 proto 属性指向构造函数的 prototype 属性)
- 构造函数中的this指向新对象,执行构造函数中的代码(也就是为这个对象添加属性和方法)
- 返回新的对象。判断函数的返回值类型,如果是值类型,返回创建的空对象。如果是引用类型,就返回这个引用类型的对象。
//具体实现
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
9. async/await的理解
async/await其实是
Generator
的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的,async用来声明一个函数是异步的,而await是等待这个异步方法执行完毕
1)async
async 函数返回的是一个 Promise 对象,如果在函数中直接return一个直接量,async会把这个直接量通过promise.resolve封装成一个promise对象
async function test() { return "test" } let res = test() console.log(res);
2)await
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
function time() { return new Promise((resolve)=> { setTimeout(()=> { resolve('await') },3000) }) } async function test() { let res = await time() console.log(res) // 输出 await }
10. 对 promise 的理解
1) 原理:
promise是ES6提供的一个构造函数,主要解决了异步多层嵌套回调(地狱回调)的问题,让代码的可读性更高,更容易维护。
可以使用promise构造函数new一个实例,promise构造函数接受一个函数作为参数,这个函数又可以接收两个参数,分别是‘resolve’和‘reject’,‘resolve’将promise由等待pending变为
Fulfilled
成功态,‘reject’则将状态由等待pending变成Rejected
失败态。一旦状态改变,就不会再变。
实例创建完成后,如果实例状态发生改变的话,会触发then()的回调函数,then()也是可以接收两个参数,第一个参数是Fulfilled
状态的回调函数,第二个参数是Rejected
状态的回调函数。如果写了catch()方法,那么then()方法可以不接收第二个参数,因为
Rejected
失败态的话会被catch捕捉到,调用catch的方法,而是Fulfilled
成功态的话,只执行then()的第一个参数的回调函数。then和catch最终返回的也是一个promise,所以可以链式调用。then方法和catch方法只要不报错,返回的都是一个fulfilled状态的promise对象。
2)特点:
- 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:
Pending:/*初始态*/ Fulfilled:/*成功态*/ Rejected:/*失败态*/
- promise对象的状态改变,只能有两个可能, 从pending变为resolved 或者 从pending变成rejected,一旦状态改变,就不会在变。
- resolve方法的参数是then中回调函数的参数,reject方法种的参数是catch中的参数
- then方法和catch方法只要不报错,返回的都是一个fulfilled状态的promise
3)方法:
then()
是实例状态发生改变时的回调函数,第一个参数是
resolved
状态的回调函数,第二个参数是rejected
状态的回调函数catch()
用于指定发生错误时的回调函数
finally()
用于指定不管 Promise 对象最后状态如何,都会执行的操作
all()
接收一个数组参数,数组中的元素都是Promise对象。该方法返回一个新的Promise对象,若数组中所有的Promise状态都是成功,则返回的Promise也成功;若数组中至少有一个Promise对象失败,则返回的Promise也失败。
race()
效果和all类似,使用方法也一样。区别在于,race方法返回的Promise的状态和数组中最先完成状态改变的Promise的状态一致。
11. setTimeout、Promise、Async/Await 的区别
事件循环中分为宏任务队列和微任务队列。
- setTimeout 的回调函数放到宏任务队列里,等到执行栈清空以后执行;
- promise.then 里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
- async 函数表示函数里面可能会有异步方法,await 后面跟一个表达式,async 方法执行时,遇到 await 会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
三. 网络知识篇
1. 在浏览器中输入URL并按下回车后会发生什么
用户是第一次访问时,在用户输入URL之后,浏览器首先会进行 DNS 解析,将用户输入的域名解析成 web 服务器的 IP 地址,之后会通过该 IP 地址与服务器进行三次握手建立TCP连接
连接完成之后通过 HTTP 协议发送客户端的请求,在服务器接收到该请求之后,会根据该请求体中的内容决定如何获取目标文件,并且将目标文件返回给客户端;浏览器在收到文件之后,首先会通过解析HTML文件为 DOM 树,再CSS 解析生成 CSS 规则树,结合 DOM 树和 CSS 规则树,生成渲染树,根据渲染树的布局在页面上显示网页
最后客户端和服务器通过四次挥手断开连接,然后将解析的IP存在本地,第二次访问时,会先去读取浏览器的缓存。
2. TCP的三次握手和四次断开
第一次握手:客户端给服务器发送一个请求包,请求访问。
第二次握手:服务器收到请求包,确认客户端的请求,再回发一个包表示同意访问。
第三次握手:客户端收到服务器的包,再给服务器发一个确认包 ACK,发送成功后开始访问。
其中:SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段。
四次挥手,断开连接。
三次握手的作用
1.确认双方的接受能力,发送能力是否正常
2.指定自己的初始化序列号,为后面的可靠传送做准备
3.如果是HTTPS协议的话,三次握手这个过程还会进行数字证书的验证以及加密密钥的生成
TCP为什么要三次握手呢?
防止重复连接
同步初始化序列化
当然 TCP 连接还可以四次握手,甚至是五次握手,也能实现 TCP 连接的稳定性,但三次握手是最节省资源的连接方式,因此 TCP 连接应该为三次握手。
3. http 和 https区别
1、HTTPS协议需要到CA申请证书,一般免费的证书比较少,因而需要一定费用。
2、HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。
3、HTTP和HTTPS使用的是完全不同的链接方式,用的端口也不一样,前者是80端口,后者是443端口。
4、HTTP的链接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。
四. 其他
1. 图片的懒加载和预加载
懒加载:
图片的懒加载是等图片在用户要看到时才进行加载 。
与预加载相反 。 预加载可以说是勤劳,先做完然后万事大吉 。 而懒加载却是需要我再做 , 不要我就准备着。
简而言之 懒加载就是 先显示再加载
懒加载的缺点:
- 需要监听图片是否显示,比较耗游览器性能。
- 图片是显示时才去加载。如果网络不太好可能会有一段时间是空白
预加载:
提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时可以立马显示出来,以达到在预览的过程中,无需等待直接预览的良好体验。
简而言之 : 就是需要显示前先加载
let img = new Image(); img.src = url // 图片地址
预加载的缺点
预加载会占用较多的后台资源,因为可能一次性加载较多的图片
预加载需要比较长的时间 ,一般是利用用户进行其他操作时进行。(如漫画是在用户看上一个图片时进行预加载 ) 。 或者是在等待的这段时间显示其他。(如显示进度条。)
2. 鼠标事件(mouseover和mouseenter)
- mouseover经过自身盒子触发,经过子盒子也触发,用于冒泡特性
- mouseenter只经过自身盒子触发,没有冒泡特性
3. 如何给一个按钮绑定两个点击事件
<button id="btn">点击</button> <script> //通过事件监听 绑定多个事件 let btn = document.getElementById('btn') btn.addEventListener('click', one) btn.addEventListener('click', two) function one() { alert('第一个') } function two() { alert('第二个') } </script>
4. Javascript创建函数的三种方式
1)命名函数创建
fn(); //可以执行 function fn(){ console.log(Hello world"); }
2)匿名函数
fn(); //报错 var fn = function(){ console.log("Hello world"); } //自执行函数:匿名函数的自我调用 (function(){ console.log("Hello world"); })();
3)构造函数定义函数
//构造函数定义函数 //执行速度慢 var fn = new Function("x","y","console.log(x+y)"); fn(2,5);
5. js中类数组(伪数组)转化为真的数组
区别:
相同点:都具有length属性和索引元素
不同点:类数组没有任何Array属性和Array方法(比如push)方法:
1)使用拓展运算符(ES6)
let arr = [...obj];
2)Array.from(arr)
let arr = Array.from(obj);
3)for...of 循环
let arr = []; for(let item of obj){ arr.push(item); }
6. JS中 setTimout 和 setInterval 的区别
setTimeout()是 延时器,setInterval()是定时器。
7. 强缓存和协商缓存
强缓存优先级 > 协商缓存优先级
强缓存:
- 使用强缓存策略时,在有效时间内,不再向服务器发起请求,直接从浏览器本地缓存中获取资源
- 强缓存相关字段有 :
- expires:date(过期日期)
- cache-control: max-age=3600(当前时间+3600秒内不与服务器请求新的数据资源)
- cache-control:no-cache (不使用本地缓存)
- cache-control:no-store (直接禁止浏览器缓存数据)
- Cache-Control优先级高 于Expires【在有效时间内命中强缓存】
协商缓存:
- 协商缓存就是强缓存失效后,浏览器携带缓存标识 向服务器发起请求,如果资源没有发生修改,则返回 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源
- 协商缓存相关字段Last-Modified(这个资源在服务器上的最后修改时间)、Etag(服务器响应请求时,返回当前资源的唯一标识 (由服务器生成),只要资源有变化,Etag就会重新生成) (ETag优先级高 于Last-Modified)z`
**8. 防抖&节流
防抖:n秒后在执行该事件,若在n秒内被重复触发,则重新计时。用户高频事件完了,再进行事件操作。
节流:n秒内只运行一次,若在n 秒内重复触发,只有一次生效。
防抖应用场景:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测onchange /oninput事件
- 窗口大小计算。只需窗口调整完成后,计算窗口大小。防止重复渲染
节流应用场景:
- 懒加载、滚动加载、加载更多或监听滚动条位置
- 防止高频点击提交,防止表单重复提交
防抖代码实现:
<button id="btn">点击节流</button> <script> function fun() { console.log("点击"); } var btn = document.getElementById('btn'); btn.addEventListener('click', debounce(fun, 2000)); function debounce(fun,time) { let timer = null; return function() { if(timer) { clearTimeout(timer); } timer = setTimeout(() => { fun.apply(this) }, time) } } </script>
节流代码实现:
<button id="btn">点击防抖</button> <script> function fun() { console.log("点击"); } var btn = document.getElementById('btn'); btn.addEventListener('click', debounce(fun, 3000)); function debounce(fun,delay) { let open = true; return function() { if(open) { open = false; setTimeout(() => { console.log("定时器"); fun.apply(this); open = true; },delay) } } } </script>