面试题总结-JS

文章目录

    • 一、JS 系列
      • 1、原型、原型链
      • 2、闭包
      • 3、this指向
      • 4、call、 apply、 bind 的作用与区别?
      • 5、数组扁平化
      • 6、var、let、const 区别
      • 7、对称加密和不对称加密的区别
      • 8、js 的栈和堆
      • 9、对象的深拷贝和浅拷贝
      • 10、浏览器的事件循环机制
      • 11、宏任务和微任务
      • 12、script 标签上的属性
      • 13、浏览器事件触发机制
      • 14、事件委托原理,为什么用事件委托?
      • 15、判断两个对象相等
      • 16、前端跨域问题
      • 17、普通函数和箭头函数区别
      • 18、作用域和作用域链
      • 19、高阶函数
      • 20、防抖和节流
      • 21、数组、字符串和对象方法
      • 22、垃圾回收机制
      • 23、typeof 返回那些数据类型
      • 24、new运算符执行过程
      • 29、ES5 和 ES6 继承区别
      • 30、js 实现异步方式
      • 31、js 封装方法控制最大并发量
      • 32、计时器内容执行超过计时器时间怎么办?
      • 33、promise理解
      • 34、promise.all 和 promise.race、promise.allSettled、promise.any
      • 35、promise.all 是同步还是异步、如何保证入参和出参顺序一致、中间失败后面方法还会执行吗?
      • 36、对象去重
      • 37、数组去重
      • 38、前端工程化理解
      • 39、判断对象是否有某个属性?
      • 40、TS的?、??、!、!!
      • 41、不使用第三个变量,让两个变量互换值
      • 42、类数组和数组的区别,如何相互转换
      • 43、阅读源码给你带来什么
      • 44、那些情况会引起内存泄漏
      • 45、长列表优化
      • 46、模块化的理解
      • 47、web网络协议
      • 48、set、map结构
      • 49、forEach,map 可以跳出循环吗
      • 50、设计模式
      • 51、变量提升和函数提升、相关题型
      • 52、class特性
      • 53、code-review的标准
      • 54、高并发对前端的影响
      • 55、移动端适配(rem和vw区别)
      • 56、oo和js 函数编程有什么不同
      • 57、实现sleep函数,使用async怎么实现
      • 58、浏览器跨域是通过什么判断的
      • 59、reduce如何使用,空数组会导致那些问题
      • 60、声明函数的方式
      • 61、数据类型
      • 62、js 的隐式转换
      • 63、js和ts区别
      • 64、手写new
      • 65、async 、await的原理
      • 66、es6以及新版本
      • 67、手写bind
      • 68、定时器加循环
      • 69、操作对象的方法
      • 71、移动端适配方案
      • 73、BOM 对象
      • 74、数据检测方法
      • 75、手写 instanceof
      • 76、js 包装类型
      • 77、Object.is() 与比较操作符 `“=== ” 、 “==”` 的区别?
      • 78、如何获取一个安全的 undefined 值
      • 79、js 延迟加载的方式
      • 80、JSON的理解
      • 81、Ajax原理
      • 82、XML和JSON的区别
      • 83、undefined 和 null区别
      • 84、 JS 的遍历方式,以及几种方式的比较
      • 85、数组乱序
      • 86、window.onload和$(document).ready
      • 87、ajax、axios、fetch区别
      • 88、JavaScript如何实现一个类,怎么实例化这个类
      • 89、JavaScript 中,调用函数有哪几种方式
      • 90、有四个操作会忽略enumerable为false的属性(不可枚举)
      • 91、原型链判断
      • 其他

一、JS 系列

1、原型、原型链

1、原型的本质是对象;
2、对象都是通过构造函数 new 函数() 出来的,所以的函数都有原型属性 prototype,prototype 里面有一个 constructor 属性指向函数本身;
3、所有的对象都有一个隐式原型 proto ,指向原型;

由于原型本身也是一个对象,所有它也有一个 proto 属性指向它的原型;当我们查找实例对象的属性时,如果找不到就会到与对象关联的原型上查找,如果还找不到,就会到原型的原型上查找,从而形成一个链条叫:原型链

特殊情况:Object 的 proto 指向 null;Function 的 proto 指向 Function 的原型;
面试题总结-JS_第1张图片

2、闭包

定义:闭包是指能够访问另一个函数作用域中的变量的一个函数。 在js中,只有函数内部的子函数才能访问局部变量, 所以闭包可以理解成 “定义在一个函数内部的函数”;

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
     return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量1"

优点:
1、正常的函数,在执行完之后,函数里面声明的变量会被垃圾回收机制处理掉,但是形成闭包的函数在执行之后,不会被回收,依旧存在内存中,延长了变量的生命周期;
2、避免定义全局变量所造成的污染

缺点:因为变量不会被回收,所以内存中一直存在,耗费内存;解决方法是在使用完之后手动将变量回收,赋值为null;

常见场景:
1、柯里化函数(避免频繁地调用具有相同参数的函数,可以将一个多参数的函数转化为一个单参数的函数)也就是高阶函数
2、实现变量/方法的私有化;
3、计数器、延迟调用、回调函数等;
4、防抖、节流函数;匿名自执行函数;
5、结果缓存(循环加定时器,缓存每次循环结果);
参考:https://blog.csdn.net/yiyueqinghui/article/details/96453390

3、this指向

1、全局环境中,this 都是指向全局对象 window;
2、普通函数中的 this 指向 window 对象, 严格模式下为 undefiend;
3、当函数作为构造函数时,它的 this 会被绑定到实例对象上;
4、对象调用,那个对象调用 this 就指向那个对象;
5、箭头函数里面,没有 this;this 继承外面的环境;
6、定时器 / 计时器 this 指向 window;

4、call、 apply、 bind 的作用与区别?

作用:改变他 this 指向,第一个参数都是 this 指向的对象;

区别:
1、call 和 apply 都可以对函数进行直接调用,bind 方法返回的是一个函数,需要手动调用函数;
2、call 和 bind 的参数是 参数列表逐个传入,而 apply 的参数必须为数组形式;
参考:https://blog.csdn.net/weixin_43299180/article/details/115510252?spm=1001.2014.3001.5502

5、数组扁平化

1、toString 或者 join 方法可以将高维数组变成字符串,然后用 split 方法分割成一维数组;
2、递归遍历数组的每一项,如果是数组就继续遍历并且用 concat 方法拼接数组,如果不是就push到数组中;
3、ES6 提供的扩展运算符可以将二维数组变成一维数组,我们可以遍历数组,如果 arr 里面还有数组就用一次扩展运算符,直到没有为止;
4、ES6 提供了 flat 方法会按照一个可指定的深度(Infinity)递归遍历数组;
参考:https://blog.csdn.net/weixin_43299180/article/details/113545537?spm=1001.2014.3001.5502

6、var、let、const 区别

1、var 声明的变量有声明提升的特性,而 let、const 没有;
2、var 声明的变量会挂载到 windows 对象上,所以使用 var 声明的是全局变量,而 let 和 const 声明的变量是局部变量, 块级作用域外不能访问;
3、同一作用域下 let 和 const 不能重复声明同名变量,而 var 可以;
4、const 声明的是常量,必须赋初值,一旦声明不能再次赋值修改,如果声明的是复合类型数据,可以修改其属性;

7、对称加密和不对称加密的区别

区别:
对称加密
1、加密解密都使用同一个秘钥;
2、用于网络传输数据的时候第三方很容易获取到秘钥,不安全;
不对称加密
1、加密和解密所使用的不是同一个密钥;
2、只有公钥被公开,私钥在服务器中,这样比较安全
3、可以用于数字签名和数字鉴别

使用:
对称加密
客户端访问服务器时,服务器生成一个秘钥并且把秘钥返回给客户端,然后客户端和服务器通过这个秘钥进行通讯;
不对称加密
服务器产生两个秘钥(公钥、私钥),服务器将公钥发送给客户端,客户端用公钥加密传输数据给服务器,服务器用私钥解密;(但是服务器用公钥加密数据传输给客户端,客户端没法解密)
对称加密和不对称加密结合
服务器产生两个秘钥(公钥key1、私钥key2),服务器将公钥发送给客户端,客户端生成一个对称的秘钥 key3,并通过 key1 传递给服务器;服务器通过 key2 解密得到 key3,后面直接通过 key3 来通讯;(由于第三方可以获取到 key1 ,它可以生成自己的对称秘钥 key4 ,来重新加密篡改 key3)
CA证书颁发机构
引入第三方机构,服务器把钱、自己的公钥和域名发给 CA,然后 CA 会给服务器颁发证书,由于是通过 CA 的私钥来加密的,所以也需要 CA 的公钥来解密,而且无法重新加密伪造;
服务器给客户端证书,客户端通过证书获取到服务器的公钥key1和证书签名,数据传输时服务器会验证签名;

常见加密方式:
MD5:MD5 是一种被广泛使用的线性散列算法,可以产生出一个 128 位(16字节)的散列值(hash value),用于确保信息传输完整的一致性。且 MD5 加密之后产生的是一个固定长度(32 位或 64 位)的数据
base64:Base64 是一种使用 64 位字符来表示任意二进制数据的方法。base64 是一种编码方式而不是加密算法,只是看上去像是加密而已
sha1:https://blog.csdn.net/weixin_38404899/article/details/100115184
DES/AES:https://www.cnblogs.com/liuzhongkun/p/16189433.html
RSA:https://www.cnblogs.com/liuzhongkun/p/16189411.html

可以使用加密库:CryptoJS

8、js 的栈和堆

在 JS 中每一个数据都需要一个内存空间,这个空间被分为两种:栈内存(stack)、堆内存(heap)。
1、栈

  • 申请变量时自动分配相对固定大小的内存空间,并由系统自动释放,这样内存可以及时得到回收,相对堆更容易管理内存空间;
  • 基础数据类型都是存储在栈内存里面的.,每一个数据占据的内存空间大小是确定的(引用类型的地址也存在栈里);
  • js可以直接访问栈里存储的数据;
  • 栈是线性结构,先进后出,便于管理;

2、堆

  • 程序员分配和释放动态分配内存,内存大小不一,也不会自动释放.,只有栈里面的引用结束才会释放;
  • 引用内存数据存储在堆中,但是引用类型的数据地址指针是存储在栈里面的;
  • 我们要访问引用类型需要现在栈里面获取对象地址,然后根据地址找到堆里面对应的数据,不可以直接访问堆里面的数据;
  • 杂乱无序,方便存储和开辟内存空间;

注意:堆存储比较慢,为什么还需要堆存储呢?
1、存在栈中的数据大小必须是确定的,不能适应可变大小和数目的动态数据储存;
2、当函数结束后其栈空间立即被释放,里面的变量数据无法保留;如果要保留,让函数外面的继续使用(比如闭包),必须将变量存入堆;

3、代码空间
还有一种代码空间,主要存储可执行代码;js 在解析遇到闭包时会在 堆 里面创建一个对象用来保存闭包中的变量;

9、对象的深拷贝和浅拷贝

浅拷贝: 只是将数据中所有的数据引用下来,数据的地址是不变的,拷贝之后的数据修改之后,也会影响到原数据的中的对象数据
深拷贝: 将数据中所有的数据拷贝下来,对拷贝之后的数据进行修改不会影响到原数据

浅拷贝方法:

  • 赋值运算
  • 扩展运算符 (…)
  • Object.assign( target, …sources)
  • concat、 解构赋值等

深拷贝方法:

  • 递归
  • 函数库 lodash 的 _.cloneDeep 方法
  • JSON.parse( JSON.stringify( a ) ):这个方法无法转化函数、undefined、symbol。
//递归实现深拷贝
function deepClone(obj){
	if(!obj || typeof object !== 'object') return;
	let newObj = Array.isArray(obj)?[]:{};
	for(let key in obj){
		//判断对象中是否有该键值
		if(object.hasOwnProperty(key)){
          newObj[key] = typeof object[key] === "object"  ?  deepCopy(object[key]) : object[key];
        }
	}
	return newObj;
}

1、判断对象是否存在或者是不是对象,不是直接返回;
2、根据对象类型定义变量是数组还是对象;
3、循环对象每一个属性,如果是对象则递归赋值,不是则直接赋值;

10、浏览器的事件循环机制

1、js 是单线程的:因为 js 主要用途是与用户互动和操作 dom 的,所以它必须是单线程的,防止两个线程同时操作同一个 dom 浏览器无法知道以哪一个为准。

2、任务队列:

  • 单线程就意味着所有任务需要排队,前一个任务结束,才会执行后一个任务;如果前一个任务耗时很长,后一个任务就不得不一直等着。
  • js 把任务分成两种,一种是同步任务,另一种是异步任务。同步任务在主线程上排队执行;异步任务不进入主线程、而进入"任务队列"(task queue),只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

3、js 运行机制:

  • 1、所有同步任务在主线程上执行;
  • 2、主线程之外还有一个任务队列,异步任务有了运行结果就在任务队列里面放置一个事件;
  • 3、同步任务执行完毕之后,系统会读取任务队列里存放发事件,把对应的异步任务推到主线程开始执行;
  • 4、主线程任务执行完毕之后,继续从任务队列里面读取事件;

只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复,整个的这种运行机制又称为Event Loop(事件循环)。

11、宏任务和微任务

JS 是单线程的,任务按顺序一个一个执行,避免一个任务消耗时间太长后面任务只能等待;所以将 JS 分为同步任务和异步任务,而异步任务中又分为宏任务和微任务两种;

1、宏任务由宿主(浏览器、node)发起的;微任务由 JS 引擎发起;
2、宏任务(setTimeout、setInterval、postMessage、script 代码、 I/O、用户交互操作、UI渲染、setImmediate(node)),微任务(promise.then、process.nextTick(node)、MutaionObserver);
3、同一层级,先执行微任务,在执行宏任务(微任务不一定就比宏任务快);

**问题:**为什么微任务会先于宏任务执行?
当我们的主线程的代码执行完毕之后,在 Event Loop 执行之前,首先会尝试 DOM 渲染,微任务是在 DOM 渲染之前执行,DOM 渲染完成了之后,会执行宏任务;

12、script 标签上的属性

script 的下载和执行会阻断 HTML 的解析,可以加上 defer 和 async 来让 script 变成异步加载,不妨碍页面上其他操作;

  • async:加载完之后立即执行;
  • async:下载是并行的,但是执行则不是按照页面中的脚本先后顺序,那个先下载完那个就先执行;
  • async:脚本的执行会阻止文档的解析渲染;
  • async:脚本的下载和执行不计入DOMContentLoaded事件统计
  • defer:延迟到文档完全被解析和显示之后再执行,只对外部脚本文件有效;
  • defer:下载是并行的,并按照顺序执行,执行完毕后会触发DOMContentLoaded事件;

13、浏览器事件触发机制

1、事件:事件是用户操作页面时发生的交互动作,比如click/move等,事件除了用户触发的动作外,还可以是文档加载、窗口滚动和大小调整。事件被封装为一个event对象,包含了事件的属性和方法;

2、浏览器有三种事件模型:DOM0级、DOM2级、IE事件模型;

  • DOM0级事件模型:这种模型不会传播,没有事件流的概念,可以在网页中直接定义监听函数,也可以通过js属性来指定监听函数,直接在dom对象上注册事件名字;
  • IE事件模型:该事件模型共有两个过程:事件触发和事件冒泡;事件处理会首先执行目标元素绑定的监听事件,然后从目标元素冒泡到document,依次检查经过的节点是否绑定事件监听函数,如果有则执行;
  • DOM2级事件模型:事件有三个过程,事件捕获、事件触发和事件冒泡;

3、事件触发过程(事件流):

  • window往事件触发处传播,遇到注册的捕获事件会触发(捕获阶段)
  • 触发注册事件
  • 触发处往window处传播,遇到注册的冒泡事件会触发(冒泡阶段)

4、阻止事件冒泡

  • 普通浏览器:event.stopPropagation()
  • IE浏览器:event.cancelBubble = true

5、DOM0 和 DOM2 的区别
DOM0:

1、只能注册一次,如果注册多次,后面的会覆盖前面的
2、使用html属性的方式绑定,如果要调用这个函数,这个函数在js中就要处于全局作用域
3、删除事件绑定设置为空或者null:obj.onclick = function(){};obj.onclick = null;

DOM2:

1、可以给同一个事件注册多次,而且会依次触发
2、事件监听器 addEventListener、removeEventListener,匿名添加的事件处理程序无法移除

6、target.addEventListener(type, listener, useCapture) :默认在冒泡阶段触发,想在捕获阶段触发修改第三个参数useCapture :true;

14、事件委托原理,为什么用事件委托?

原理:利用事件冒泡原理将触发子元素的事件绑定到父元素上,利用e.target获取子元素实现事件触发;
用处:由于对新生成的子元素无法绑定事件 这个时候就可以用事件委托的方法实现动态添加的新元素的触发事件;或者列表元素很多每一个列表都要事件处理,给每一个列表都绑定事件就很麻烦,这个时候就可以使用事件委托在父元素上;

//ul  li 
ul.onclick = function(e){
	e = e || window.event
	 console.log(e.target)
}

点击的是 li ,事件会依次一级一级往上调用父级元素的同名事件,然后触发这段代码,通过 e.target 找到被点击的子节点;

15、判断两个对象相等

1、通过JSON.stringify(obj) 来判断两个对象转后的字符串是否相等
优点:用法简单,对于顺序相同的两个对象可以快速进行比较得到结果
缺点:当两个对比的对象中key的顺序不是完全相同时会比较出错

2、递归判断
判断两个对象是否是对象;然后判断两个对象属性长度是否一致;最后递归比较对象属性是否相等

function diff(object1, object2) {
	var o1 = object1 instanceof Object;
    var o2 = object2 instanceof Object;
    // 判断是不是对象
    if (!o1 || !o2)  return false;
	var o1keys = Object.keys(object1);
	var o2keys = Object.keys(object2);
	//两个对象属性长度是否一致
	if (o2keys.length !== o1keys.length) return false;
	//属性是否相等
	for (var o in obj1) { // 遍历对象 fon in 循环 o 为 对象的属性名  
      var t1 = obj1[o] instanceof Object;
      var t2 = obj2[o] instanceof Object;
      if (t1 && t2) {
        return diff(obj1[o], obj2[o]);
      } else if (obj1[o] !== obj2[o]) {
        return false;
      }
    }
	return true;
}

3、ES6
Object.entries:返回一个给定对象自身可枚举属性的键值对数组

Object.entries(object1).toString() === Object.entries(object2).toString();

16、前端跨域问题

1、JSONP:利用

你可能感兴趣的:(javascript)