当前位置:   article > 正文

面试题总结-JS_js面试

js面试

文章目录

一、JS 系列

1、原型、原型链

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

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

特殊情况:Object 的 proto 指向 null;Function 的 proto 指向 Function 的原型;
在这里插入图片描述

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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

优点:
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
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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)
}
  • 1
  • 2
  • 3
  • 4
  • 5

点击的是 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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

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

16、前端跨域问题

1、JSONP:利用 <script> 标签的 src 属性没有跨域限制;
2、CORS:CORS 是 W3C标准,全称是 “跨域资源共享” (Cross-Origin Resource Sharing),它允许浏览器跨域像服务器发送 XMLHttpRequest 请求;是跨域 AJAX 请求的根本解决方法;
3、nginx 代理跨域:实质和 CORS 跨域原理一样,通过配置文件设置请求响应头 Access-Control-Allow-Origin…等字段;
4、node 中间件代理跨域:原理大致与 nginx 相同,都是通过启一个代理服务器,实现数据的转发;
5、document.domain + iframs 跨域:因为浏览器是通过 document.domain 属性来检查两个页面是否同源,因此可以通过 js 设置相同的 document.domain 来实现同域;
7、window.postMessage:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一;
8、WebSocket :WebSocket 协议是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯。

17、普通函数和箭头函数区别

1、箭头函数比普通函数更加简洁(参数、函数体返回值);
2、箭头函数没有自己的 this:从自己作用域的上一层继承 this;
3、箭头函数继承来的this指向永远不会改变:call()、apply()、bind() 等方法不能改变箭头函数中 this 的指向;
4、箭头函数没有 prototype,但是有 proto 属性,所以箭头函数本身是存在原型链的,它也有自己的构造函数,但是原型链到箭头函数就停止了,所以不能作为构造函数;
5、箭头函数没有自己的arguments:在箭头函数中访问arguments实际上获得的是它外层函数的arguments值;

var id = 'sss'
let obj = {
	let id = 'xxx',
	a:()=>{
		console.log(this.id)
	},
	b:function(){
		console.log(this.id)
	}
}
obj.a() //sss
obj.b() //xxx
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

a 是箭头函数,箭头函数的 this 是定义时所处的对象,即它的父级作用域也就是window(注意这里说的是作用域,对象的{}不属于块级作用域);
b 是普通函数,指向它的直接调用者 obj;

注意:如果用 let 来定义 obj 外面的 id ,则无法在 window 上查找到 id 这个对象,需要 var 来声明这个变量;

18、作用域和作用域链

1、作用域:一个变量可以使用的范围;

  • 全局作用域:不在函数内部作用域和块级内部作用域外的作用域
  • 函数作用域:在函数内部的作用域
  • 块级作用域:在花括号{}内部的作用域

注意:对象的{}不属于块级作用域,像for(){},if(){},else{},try{},cath(){}等等的花括号才是块级作用域;对象的{}的作用域是什么作用域取决于对象所处的作用域,比如对象在全局作域 下定义的,那么对象的{}的作用域就是全局作用域;

2、作用域链:
内部作用域访问外部作用域的变量,采取的是链式查找的方式来决定取哪个值,采取就近原则的方式向上一级一级的作用域来查找变量值,最顶级是全局作用域,如果到全局作用域也没找值,那么就会报错;

19、高阶函数

定义:一个函数就接收另一个函数作为参数,这种函数就称之为高阶函数

//f 是一个函数
	function add(x, y, f) {
		return f(x) + f(y);
	}
	add(-5, 6, Math.abs); // 11
  • 1
  • 2
  • 3
  • 4
  • 5

常见使用:map、filter、reduce、防抖、节流

20、防抖和节流

1、防抖:短时间内大量触发事件只执行一次
在第一次触发事件时,不立即执行函数,而是给出一个期限值比如 200ms,然后 200ms 内没有再次触发滚动事件,那么就执行函数;在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时;常用于联想输入框、按钮大量点击;

function debounce = (fn, delay) => {
	var delay = delay || 500;
	var timer;
	return function() {
		var th = this;
		var args = arguments;
		if(timer) {
			clearTimeout(timer);
		}
		timer = setTimeout(function() {
			timer = null;
			fn.apply(th, args);
		}, delay);
	};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2、节流:
每隔一段时间后执行一次,降低频率;常用于页面滚动、缩放

function throttle = (fn, interval) => {
	var timer;
	var interval = interval || 500;
	let valid = true;
	return function() {
		var th = this;
		var args = arguments;
		if(valid){
			clearTimeout(timer);
			timer = setTimeout(function() {
				fn.apply(th, args);
				valid  = true;
			}, interval);
			valid  = false;
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

21、数组、字符串和对象方法

1、数组

  • push:向数组末尾添加一个或多个元素,改变原数组;
  • pop:删除数组中的最后一位元素,并返回数组,改变原数组;
  • shift:删除数组中的第一个元素,并返回数组,改变原数组;
  • unshift:向数组前添加一个或多个元素,并返回数组,改变原数组;
  • splice:实现数组的增、删、改,改变原数组;
  • reverse:颠倒数组中元素的顺序,改变原数组;
  • slice:返回从原数组中指定开始下表到结束下表之间(不包括结束位置)的项组成的新数组,原数组不变
  • map:数组中的每项数据都添加一个方法,结果作为一个新的数组返回,原数组不变
  • froEach:给数组的每一项数据循环添加一个方法,和map不同的是,forEach 改变原数组,没有返回值;
  • filter:给数组方法中传入一个判断条件,将满足条件的值筛选出去变成一个新的数组返回,原数组不变
  • every:遍历所有元素进行判断返回一个布尔值,如果所有元素返回都是true,则返回true,否则返回 false;
  • some:和 every类似,唯一区别就是some遍历数组中的每一项,若其中一项为true,则返回值是true;
  • reduce:为数组中的每一个元素依次执行回调函数,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。可以用来写求和求积;
  • isArray:来判断一个对象是不是数组,是的话返回true,不是返回false;
  • concat:一个可以将多个数组合并成一个数组的方法;
  • toString:用来将数组转变为字符串格式;
  • join:将数组转换为字符串,但和toString不同的是join可以设置元素之间的间隔;
  • includes:用来判断数组中是否有传入的元素,有的话返回true,没有的话返回false;
  • indexOf:从数组的开头向后查找,接受两个参数,要查找的项和查找起点的位置索引;
  • sort:按指定的参数对数组进行排序,返回的值是经过排序后的数组,改变原数组;

会改变原数组的:push、pop、shift、unshift、splice、reserve、sort;

2、字符串

  • concat:拼接字符串,返回新字符串;
  • slice:截取字符串,返回新字符串;
  • substring:此方法和slice方法功能相同都是提取一个字符串,并返回提取到的字符串;
  • trim:删除一个字符串两端的空白字符,并返回删除后的新字符串;
  • toLowerCase:字符串值转为小写形式,返回新字符串;
  • toUpperCase:字符串值转为大写形式,返回新字符串;
  • replace:可以将一个替换值替换字符串的一部分,返回替换后的新字符串;
  • split:可以使用一个指定的分隔符来将字符串拆分成数组,返回一个数组;
  • charAt:从一个字符串中返回指定的字符;
  • includes:判断字符串中是否包含指定字符,包含则返回true,不包含则返回false;
  • indexOf:判断字符串中是否包含指定字符,如果包含则返回该字符索引的位置(查找到了立即返回),如果不包含则返回-1;
  • lastIndexOf:用法和indexOf基本相同,区别是lastIndexOf()是从后往前查找;
  • search:使用正则表达式查找指定字符串,如果找到则返回首次匹配成功的索引,没有找到则返回-1;
  • match:返回一个字符串匹配正则表达式的结果,如果未设置全局匹配,则会返回第一个完整匹配及其相关的捕获组;

3、对象

  • keys:返回一个由给定对象的自身可枚举属性组成的数组;
  • values:返回一个由给定对象自己的所有可枚举属性值的数组;
  • entries:返回一个由一个给定对象的键值对组成的数组;
  • assign:将对象组合在一起;
  • freeze:冻结对象,防止修改现有的对象属性或者向对象添加新的属性和值,只能冻结一层;
  • seal:停止将任何新属性添加到对象,但仍允许更改现有属性;

22、垃圾回收机制

垃圾回收:js 代码在运行时,需要分配内存空间来存储变量和值,当变量不参与运行时就需要系统来回收掉被占用的内存空间;

回收机制:
1、js 具有自动回收机制 ,定期对不在使用的对象或者变量占用的内存空间进行回收;
2、js 有两种变量:全局、局部;全局变量会持续到页面卸载,局部变量在函数中,生命周期是从函数执行开始,到函数执行结束;但是闭包这种情况则不会被回收;

回收方式:
1、标记清除
变量进入运行环境时会被标记“进入”,这个时候不会被清除,离开环境后会被再次标记“离开”,这个时候再次标记的变量就会被释放掉内存;

2、引用计数
跟踪每一个值被引用次数,当被引用次数为 0 时,则变量在下次垃圾回收时被回收掉;这种方法会出现循环引用的问题,就是两个对象互相引用,这样垃圾回收就不会清除这两个变量;

减少垃圾回收:
1、数组优化:清空数组时,将数组长度设为 0 ;
2、函数优化:循环中的表达式,能复用的尽量放在函数外面;
3、对象优化:尽量复用,不再使用的设置为 null;

23、typeof 返回那些数据类型

string、number、boolean、undefined、object、function、Symbol

//string
typeof("abc");

//number
typeof(NaN)
typeof(Infinity)

//boolean
typeof(true);
typeof(false);

//undefined
typeof(undefined);
typeof(a);//不存在的变量

//object
//对象,数组,null返回object
typeof(null);
typeof(window);

//function
typeof(Array);
typeof(Date);

//Symbol
typeof Symbol()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

24、new运算符执行过程

  • 生成新对象
  • 链接到原型:obj.proto = Object.prototype(this指向这个新对象)
  • 绑定this:apply(执行代码给this赋值)
  • 返回新对象

29、ES5 和 ES6 继承区别

继承:拥有另一个对象的方法和属性
ES5:原型链继承、构造函数继承、组合继承、寄生式组合继承;

1、原型链继承:
原理:是直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承;

缺点:对个子类继承之后,父类引用类型的变量修改会影响所有子类;创建子实例时无法向父类传参;

2、构造函数继承:
原理:在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参

缺点:无法继承父类原型链上的属性和方法;

3、组合继承:结合上面的两种方法;

缺点:每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法;

4、寄生式组合继承:将指向父类实例改为指向父类原型, 减去一次构造函数的执行

参考:https://blog.csdn.net/YANGMENGZHENG/article/details/122474039

ES6:基于class 和 extends,子类通过执行 super 方法继承父类的 this 对象。

区别
ES5:先创建子类的实例对象,然后再将父类的方法添加到this上;
ES6:先创建父类的实例对象this,然后再用子类的构造函数修改this;

ES6的优点:

  • 解决代码的复用
  • 使用extends关键字实现继承
  • 子类可以继承父类中所有的方法和属性
  • 子类只能继承一个父类(单继承),一个父类可以有多个子类
  • 子类的构造方法中必须有super()来指定调用父类的构造方法,并且位于子类构造方法中的第一行
  • 子类中如果有与父类相同的方法和属性,将会优先使用子类的(覆盖)

30、js 实现异步方式

1、回调函数
两个函数,f1、f2,将f2作为回调函数传入f1;在 f1 中调用回调函数;

function f1(callback) {
  setTimeout(function () {
     callback();
  },1000);
}
f1(f2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

缺点:高耦合,结构混乱,回调函数不能使用 try catch 捕获错误,不能直接 return;而且每个任务只能指定一个回调函数;
2、事件监听
采用事件驱动模式。任务的执行不取决于代码的顺序,而取决与某个事件的发生

	//执行完成之后,立即触发done事件,从而开始执行f2.
	f1.on('done', f2);
	function f1(){
  setTimeout(function () {
      // f1的任务代码
      f1.trigger('done');
    }, 1000);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

缺点:整个程序都要变成事件驱动型,运动流程会变得很不清晰;
3、发布、订阅
我们假定,存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subcribe)这个信号,从而知道什么时候自己可以开始执行

	//f2向信号中心Jquery订阅done信号
	jQuery.subscribe("done", f2);
	function f1(){
    setTimeout(function () {
      // f1的任务代码
		   //发布done信号
      jQuery.publish("done");
    }, 1000);
  }
  //f2执行完成后,取消订阅
  jQuery.unsubscribe("done", f2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4、Promises对象 then、catch、finally
每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数;

5、生成器函数 Generator/yield
yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
每个yield返回的是{value:yield返回的值,done:true/false(执行状态)}

6、async、await
async是“异步”的意思,而 await 是等待的意思;

async函数返回的是一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

参考:https://blog.csdn.net/w17624003437/article/details/125879097

31、js 封装方法控制最大并发量

利用 while 循环,将首次执行的方法现在在 limit ,然后执行任务用 promise 包裹起来实现一个异步,_runList 缓存执行结果,每次任务执行完成之后,立刻调用 addTask 增加执行任务;

class TaskQueue{
		    constructor(list,limit){
		        this._runList = [];
		        this._taskList = [].concat(list);
		        this.limit = limit;
		        this.runTask();
		    }
		    runTask(){
				//控制一次请求数量
		        while(this.limit --){
		            this.addTask()
		        }
		    }
		    addTask(){
		        this.proFn(this._taskList.shift()).then((res)=>{
					//缓存处理结果
		            this._runList.push(res);
		            if(this._taskList.length !== 0){
						//一个方法执行完成之后,立即在加一个任务进来
		                this.addTask(this._taskList);
		            }
		        })
		    }
		    //任务执行
		    proFn(value){
		        return new Promise((resolve,reject)=>{
		            setTimeout(()=>{
		                resolve(value);
		            },1000)
		        })
		    }
		};
		let task = [1,3,4,2,5,6,7];
		new TaskQueue(task,2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

32、计时器内容执行超过计时器时间怎么办?

1、动态计算时差
在定时器开始前和在运行时动态获取当前时间戳,在设置下一次定时时长时,在期望值的基础上减去当前差值,以获取相对精确的定时器运行效果;
此方法仅能消除setInterval长时间运行造成的误差,或者setTimeout循环长时间运行的累计误差,无法对当个定时器消除执行的延迟;

2、web worker
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢;

参考:https://www.cnblogs.com/shenjp/p/15774116.html

33、promise理解

1、定义

  • Promise 是异步编程的一种解决方法,比传统的回调函数和事件监听更合理;它是由社区提出和实现经由 ES6 将其写进语言标准,并在原生提供了 Promise 对象;
  • Promise 可以理解是一个容器,里面保存着某个将来才会结束的事件(异步操作)的结果;从语法上说,Promise 是一个对象通过它可以获取异步操作的消息;Promise 提供了统一的 API ,各种异步操作都可以用同样的方法进行处理。

2、特点

  • Promise 对象代表一个异步操作,对象的状态不受外界影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败);
  • 状态不可逆:Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从 pending 变为 rejected;如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果;这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的;
  • 构造函数 Promise 必须接收一个函数(handle)作为参数,函数又包含 resolve 和 reject ;
  • then 、catch 支持链式调用;

3、缺点

  • Promise 无法取消:一旦新建就会执行,无法中途取消;
  • 如果不设置回调函数,Promise 内部抛出的错误不会反应到外部;
  • 当处于 pendding 状态时,无法知道目前进展到哪一个阶段;

4、场景

  • 有效的解决 js 异步回调地狱问题;
  • 将业务逻辑与数据处理分隔开使代码更优雅,方便阅读,更有利于代码维护;

5、注意

1、promise里面是同步任务,需要调用回调函数才会执行then、catch;
2、微任务执行先于宏任务;
3、promise的状态只能改变一次,所以在调用回调时要么是成功,要么是失败;
4、promise的链式调用,从对应的返回状态开始(then、catch),后面的依次执行,后面拿到的是上一个返回的结果;
5、链式调用中,执行完then方法后,后续的then才会被推入微任务队列中;
6、async、await 相当于一个 promise,await 下面的相当于是 promise.then 里面的代码;

34、promise.all 和 promise.race、promise.allSettled、promise.any

1、all
只有所有的 Promise 都成功了,才会执行 resolve 并返回是所有成功的返回值数组,状态为成功状态;
如果有一个 Promise 失败了,则会直接执行 reject 返回失败的返回值,状态为失败状态;
有多个 Promise 失败,则返回第一个失败的返回值;

2、allSettled
所有的 Promise 都执行完成之后,不管成功还是失败,返回全部 Promise 数组,可以拿到每一个 Promise 的状态;

3、race
谁第一个完成就返回谁的结果,如果第一个是失败这个 Promise 就失败,如果第一个是成功就是成功;

4、any
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态并且返回;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

35、promise.all 是同步还是异步、如何保证入参和出参顺序一致、中间失败后面方法还会执行吗?

1、如果传入的可迭代对象是空的,就是同步;其他情况都是异步;

2、遍历入参时会带上下标 index,然后把返回值通过 index 下标放到出参数组中;

3、所有的promise都会执行, 只是有任意一个失败 就会进入 Promise.all().catch 方法。 但是这个并不会阻止传的promise的执行

36、对象去重

1、reduce去重
通过数组的reduce()方法对数组的对象从左到右进行处理,定义一个空对象obj{};
如果数组当前对象的name不在obj{},就将当前对象的name加入到空对象,并将当前对象push到积累变量total数组里;
如果当前对象的name存在obj{},则跳过push操作。

//reduce第一个参数是遍历需要执行的函数,第二个参数是item的初始值
var arr = [{"id":1,"name":"张三"},{"id":2,"name":"李四"},{"id":3,"name":"王五"},{"id":2,"name":"张三"}];
var obj = {};
arr = arr.reduce(function(item, next) {
       obj[next.id] ? '' : obj[next.id] = true && item.push(next);
       return item;
    }, []);
console.log(arr);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、set去重

var arr2 = [{"id":1,"name":"张三"},{"id":2,"name":"李四"},{"id":3,"name":"王五"},{"id":2,"name":"张三"},{'id':4,'name':'李四'}];
function unique(arr){
    const res= new Map()
    return arr.filter((a)=> !res.has(a.id) && res.set(a.id,1) && !res.has(a.name) && res.set(a.name,1))
}
//输出结果:
unique(arr2)
[
    {
        "id": 1,
        "name": "张三"
    },
    {
        "id": 2,
        "name": "李四"
    },
    {
        "id": 3,
        "name": "王五"
    }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

37、数组去重

1、用ES6的Set方法

//Set数据结构,它类似于数组,其成员的值都是唯一的
let unique = Array.from(new Set(arr));
  • 1
  • 2

兼容性不是最好的,无法去除空对象;
2、利用hasOwnProperty

let arr =[1,1,true,true,'true',"false","false",null,null,undefined,undefined,NaN,NaN,'NaN',{},{}];
let obj = {};
//将数组值作为对象的属性名,当不存在时,则放入对象中 (利用对象的属性不可重复)
//typeof item + item作为属性名
let unique = arr.filter((item,index)=>{
	return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
console.log(unique); //[1, true, "true", "false", null, undefined, NaN, "NaN", {…}]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

参考:https://blog.csdn.net/weixin_43299180/article/details/110622086

38、前端工程化理解

前端工程化是将系统化、规范化、可度量的方法用到前端应用的开发、运行、维护的过程中;用工程化的方法构建和维护有效的、实用的和高质量的前端应用;(将系统化、规范化的方法用到前端开发的整个过程当中去)

前端工程化的主要目标就是解放生产力、提高生产效率。通过制定一系列的规范,借助工具和框架解决前端开发以及前后端协作过程中的痛点和难度问题;

工程化变表现

  • 创建:脚手架
  • 编码:代码格式化、编码效率
  • 预览:热更新、Mock、Source Map
  • 代码提交:项目整体检查
  • 部署:自动发布

前端工程化的特点
1、模块化
模块化是指将一个文件拆分成多个相互依赖的文件,最后进行统一的打包和加载,这样能够很好的保证高效的多人协作;

  • JS 模块化:CommonJS、AMD、CMD 以及 ES6 Module。
  • CSS 模块化:Sass、Less、Stylus、BEM、CSS Modules 等。其中预处理器和 BEM 都会有的一个问题就是样式覆盖。而 CSS Modules 则是通过 JS 来管理依赖,最大化的结合了 JS 模块化和 CSS 生态,比如 Vue 中的 style scoped。
  • 资源模块化:任何资源都能以模块的形式进行加载,目前大部分项目中的文件、CSS、图片等都能直接通过 JS 做统一的依赖关系处理。

2、组件化
不同于模块化,模块化是对文件、对代码和资源拆分,而组件化则是对 UI 层面的拆分;
3、规范化
正所谓无规矩不成方圆,一些好的规范则能很好的帮助我们对项目进行良好的开发管理。规范化指的是我们在工程开发初期以及开发期间制定的系列规范;

  • 项目目录结构
  • 编码规范:对于编码这块的约束,一般我们都会采用一些强制措施,比如 ESLint、StyleLint 等。
  • 联调规范
  • 文件命名规范
  • 样式管理规范:目前流行的样式管理有 BEM、Sass、Less、Stylus、CSS Modules 等方式。
  • git flow 工作流:其中包含分支命名规范、代码合并规范等。
  • 定期 code review

4、自动化
从最早先的 grunt、gulp 等,再到目前的 webpack、parcel。这些自动化工具在自动化合并、构建、打包都能为我们节省很多工作。而这些只是前端自动化其中的一部分,前端自动化还包含了持续集成、自动化测试等方方面面;

39、判断对象是否有某个属性?

1、!==

let obj = { x: 1 };
obj.x !== undefined;             // true 有x属性
obj.y !== undefined;             // false 无y属性
obj.toString !== undefined;      // true 从Object继承toString属性
  • 1
  • 2
  • 3
  • 4

2、in 运算符
in 的语法是: attr in obj ,该表达式也返回一个布尔值

let obj = { x: 1 };
'x' in obj;             // true
'y' in obj;             // false
'toString' in obj;      // true
  • 1
  • 2
  • 3
  • 4

3、hasOwnPropety方法(判断自身属性)
对象的 hasOwnProperty() 方法也可以检测指定属性名是否在对象内,同样返回是布尔值, 当检测属性为自有属性(非继承)的时候返回true

let obj = { x: 1, abc: 2 };
let a = 'a';
let b = 'bc';
obj.hasOwnProperty('x');               // true 包含
obj.hasOwnProperty('y');               // false 不包含
obj.hasOwnProperty('toString');        // false 继承属性
obj.hasOwnProperty(a + b);             // true 判断的是属性abc
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4、propertyIsEnumerable
propertyIsEnumerable() 是hasOwnProperty() 的增强版,这个方法的用法与hasOwnProperty()相同,但当检测属性是自有属性(非继承)且这个属性是可枚举的,才会返回true;

let obj = Object.create({x: 1});                   // 通过create()创建一个继承了X属性的对象obj
obj.propertyIsEnumerable('x');                     // false x是继承属性
obj.y = 1;                                         // 给obj添加一个自有可枚举属性y
obj.propertyIsEnumerable('y');                     // true
Object.prototype.propertyIsEnumerable('toString'); // false 不可枚举
  • 1
  • 2
  • 3
  • 4
  • 5

!== 、in 运算符和 hasOwnProperty() 的区别就在于!== 、in运算符可以判断来自继承的属性,而hasOwnProperty() 和 propertyIsEnumerable判断自身的属性,其中 propertyIsEnumerable 判断的是自身可枚举属性;

40、TS的?、??、!、!!

  • ?:表示该属性或参数为可选项;
  • ??:表示只有当左侧为null和undefined时, 才会返回右侧的数;否则返回前面的;
  • !:表示强制解析;变量前使用!表示取反, 变量后使用!表示类型推断排除null、undefined从而使null 和undefined类型可以赋值给其他类型并通过编译;
  • !!:将把表达式强行转换为逻辑值;
// ?
const user = null;
console.log(user.account)   // 报错
console.log(user?.account)  // undefined
// !
interface IDemo {
    x?: number 
}
const demo = (parma: IDemo) => {
    const y:number = parma.x!  // 变量值可空, 当x为空时将返回undefined
    return y
}
console.log(demo({}))          // 输出: undefined
console.log(demo({x: 3}))      // 输出: 3

// !!
console.log(!!(0 + 0)) // 输出: false
console.log(!!(3 * 3)) // 输出: true
// ??
const foo = null ?? 'default string';
console.log(foo); // 输出: "default string"

const baz = 0 ?? 42;
console.log(baz); // 输出: 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

41、不使用第三个变量,让两个变量互换值

1、利用JS弱类型(数组、对象都可以)

let x = 2, y = 1;
x = [x, y]; // x = [2, 1]
y = x[0]; // y = 2
x = x[1]; // x = 1
console.log(x, y); // 1 2
  • 1
  • 2
  • 3
  • 4
  • 5
let x = 2, y = 1;
x = { x, y }; // x = {x: 2, y: 1}
y = x.x; // y = 2
x = x.y; // x = 1
console.log(x, y); // 1 2
  • 1
  • 2
  • 3
  • 4
  • 5

2、ES6 解构赋值

let x = 2, y = 1;
[x, y] = [y, x]; // x = 1 y =2
console.log(x, y); // 1 2
  • 1
  • 2
  • 3

42、类数组和数组的区别,如何相互转换

1、区别
相同点:都可用下标访问每个元素,都有length属性。

不同点:数组对象的类型是 Array,类数组对象的类型是 object ;类数组不具有数组所具有的方法,数组遍历可以用 for in 和for循环,类数组只能用for循环遍历;

2、相关转换
数组转为类数组:

[ ].push.apply(obj, arr);
  • 1

类数组转数组:

Array.from(arr);
拓展运算符:let arr = [...obj]
for of 循环将类数组每一项 push 到数组中
call:Array.protype.slice.call(obj)

参考:https://blog.csdn.net/weixin_43299180/article/details/115527832?spm=1001.2014.3001.5502

43、阅读源码给你带来什么

加深对框架的理解:了解框架是怎么实现的;
提升个人能力,巩固已有的知识体系,提升代码质量;
提高编程能力,拓展知识盲区;
培养设计思维和架构能力

44、那些情况会引起内存泄漏

意外声明全局变量(a=1):使用未声明的全局变量(var、let或const关键字声明、this关键字)
定时器或者回调函数:使用完值设置为 null
闭包或者循环中:使用完值设置为 null
清除页面dom元素时,dom元素绑定的事件未解绑;
变量的循环引用

45、长列表优化

不做响应式:只是纯粹的数据展示,不会有任何动态改变的场景下,就不需要对数据做响应化处理,可以大大提升渲染速度;
分页:一页展示10条数据,通过滚动或者点击加载其他页的数据;
可视区域渲染:假设总数据为1000条,可视区域高度为500px,每个列表item高度为50px,那么在可视区域内只需要渲染10个就可以了,而不是1000条,根据用户滚动在对可视区域的节点位置和下标进行计算,在总数据中进行截取替换,每次只是渲染可视区域的数据节点;

46、模块化的理解

概要:就是将一个工程项目代码依据一定的规则封装成多个模块(文件),并进行组合在一起;块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;JavaScript 在早期的设计中就没有模块、包、类的概念,开发者需要模拟出类似的功能,来隔离、组织复杂的JavaScript代码,我们称为模块化;

模块化的优点和好处:

避免命名冲突(减少命名空间污染)
更好的分离, 按需加载
更高复用性
高可维护性
方便依赖关系管理

减少JS文件的请求次数,通过模块化将JS文件整合为一个入口,然后引入页面可以有效的减少对JS文件的请求;
使各JS文件的依赖关系清晰,在模块化中可以清晰的分析各模块的引用关系,明确JS代码的结构;
降低项目的维护成本,当有某个模块需要添加或减少某个功能使,不需要将整个代码重构,只需要在相应的模块进行修改就可以。

模块化演化过程:
1、无模块化
早期没有模块化标准的时候是通过下面三种方式来处理模块的:

  • 1、文件划分:把应用的状态和逻辑放到不同的 JS 文件中,通过 script 引入;导致问题如下:变量都是全局定义会出现命名冲突、难以调试的情况,无法清晰管理模块之间的依赖关系和加载顺序(script 执行顺序需要手动调整,不然可能会产生运行时错误);
  • 2、命名空间:每个变量都有自己专属的命名空间,我们可以清楚地知道某个变量到底属于哪个模块,同时也避免全局变量命名的问题;
  • 3、立即执行函数:定义时就会立即执行的 JavaScript 函数, 在一个单独的函数作用域中执行代码,避免变量冲突;

2、CommonJS 规范
CommonJS 是业界最早正式提出的 JavaScript 模块规范,主要用于服务端 Node.js:使用require来导入一个模块,用 module.exports 来导出一个模块;

缺点:
模块加载器由 Node.js 提供,依赖了 Node.js,无法直接放到浏览器中执行;
CommonJS 约定以同步的方式进行模块加载,这种加载机制放到浏览器端,会带来明显的性能问题。它会产生大量同步的模块请求,浏览器要等待响应返回后才能继续解析模块(模块请求会造成浏览器 JS 解析过程的阻塞,导致页面加载速度缓慢);

3、AMD 规范
AMD 即 Asynchronous Module Definition;是一个在浏览器端模块化开发的规范,而AMD规范的实现,就是require.js。特点:依赖必须提前声明好;

通过 define 去定义或加载一个模块,使用 require 关键字来加载一个模块;

优点:异步加载,不阻塞页面的加载,能并行加载多个模块
缺点:不能按需加载,必须提前加载所需依赖

由于没有得到浏览器的原生支持,AMD 规范仍然需要由第三方的 loader 来实现。不过 AMD 规范使用起来稍显复杂,代码阅读和书写都比较困难,所以才会有下面的内容;

4、CMD规范
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD(seaJS)规范整合了CommonJS和AMD规范的特点;特点:支持动态引入依赖文件。

defined 配合 exports, module 定义,使用 require 使用模块;

优点:可以按需加载,依赖就近
缺点:依赖SPM打包,模块的加载逻辑偏重

5、UMD规范
UMD 叫做通用模块定义规范(Universal Module Definition);是集结了 CommonJs、CMD、AMD 的规范于一身。它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行;

UMD 规范将浏览器端、服务器端甚至是 APP 端都大统一了;

6、webpack(require.ensure)
webpack 2.x 版本中的代码分割。

7、ES Module
ES6 在语言标准的层面上,实现了模块功能,而且非常简单,ES6到来,完全可以取代 CommonJS 和 AMD规范,成为浏览器和服务器通用的模块解决方案;
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。 注:由于ES6目前在部分版本的浏览器中无法执行,所以,我们需要通过babel将不被支持的import编译为当前受到广泛支持的 require。

export、import 来暴露和引入模块;

参考:https://blog.csdn.net/guorui999/article/details/128802748

47、web网络协议

1、http1、2、3区别
HTTP1.0:最基本的HTTP协议, 协议整体使用请求头 + 请求体的形式来作为报文的边界用于解析读取;
优点:简单、灵活扩展(各种请求和响应都可以随意扩展,)、应用广;
缺点:无状态、明文传输,不安全,链接资源消耗大;

HTTP1.1:
改进:

使用长连接的方式改善了HTTP1.0短连接造成的性能开销
支持管道网络传输, 可以连续发送请求而不必等待请求响应后再发送请求
增加了更多的协议头, 让协议支持更多的协商内容, 比如浏览器和服务器沟通资源缓存相关的操作

缺点:

请求 / 响应头没有压缩就发送, 首部信息越多延迟越大, 只能压缩body部分
发送的消息带有冗长的首部, 每次发送相同的首部浪费资源
服务器是按照请求的顺序响应的, 如果服务器第一个响应处理时间长, 会造成响应队头阻塞
没有请求优先级 ( 类似上面的问题 )
请求只能从客户端开始, 服务器只能被动响应

HTTP2.0:

改进:

头部压缩
采用二进制格式传输
数据流传输
使用了多路复用的方式支持并发传输
支持服务器主动推送(HTTP1.1要做到这个要通过更换WebSocket协议来实现)

缺点:
TCP层面依旧存在队头阻塞问题,TCP队头阻塞存在的问题就是TCP的可靠数据传输会等待整个数据都到达后才会传递给应用层, 如果请求的某个部分因为网络延迟没有到达, 那么后续的数据只能存放在内核缓冲区中等待前面报文到达才能提交给应用层;
极端点出现丢包的情况的话, 还可能造成TCP重传, 一整个TCP链接中的所有HTTP请求都要等待这个丢失的包被重穿回来才能被应用层看到解析

HTTP3.0:
优化:

运输层使用UDP传输
简化帧结构
升级新的压缩算法

优点:

无队头阻塞
更快的连接建立
连接迁移

2、http、websocket区别
相同:都是基于tcp的,都是可靠性传输协议;都是应用层协议
不同:

WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息;HTTP是单向的;
他们的协议名不同:http、ws;
WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的;

websocket 通讯:

首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

48、set、map结构

set:类似于数组,但是成员的值都是唯一的,没有重复的值;本身是一个构造函数,用来生成 Set 数据结构;
1、生成set:

不传参数,通过add方法向set中添加元素;
将数组作为参数;

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
//或者
const set = new Set([1, 2, 3, 4, 4]);
  • 1
  • 2
  • 3
  • 4

2、属性和方法
两个属性:constructor:构造函数,默认就是Set函数;size:返回Set实例的成员总数;
方法:操作方法、遍历方法

add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员,没有返回值

向Set加入值时不会发生类型转换, 所以5和“5”是两个不同的值;由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致;entries 返回的是键值对,键值是相同的;

3、使用

数组去重;
Array.from方法可以将 Set 结构转为数组;
去除字符串里面的重复字符;
Set的遍历顺序就是插入顺序:使用 Set 保存一个回调函数列表,调用时能保证按照添加顺序调用;
扩展运算符(…)内部使用了for…of循环,所以也可以用于 Set 结构;
数组的map和filter方法也可以间接用于 Set ;

map

Map 数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Object 结构提供了“字符串—值”的对应(只能用字符串当作键),Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构(键值对的集合)实现。
如果对同一个键多次赋值,后面的值将覆盖前面的值。
Map 的键是唯一的。键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。

1、生成map

不传参,通过set方法,为其赋值;
传参。可以接受一个数组作为参数,且该数组的成员是一个个表示键值对的数组;

2、性和方法
属性:size,返回 Map 结构的成员总数
操作方法和遍历方法:

set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
get(key):读取key对应的键值,如果找不到key,返回undefined。
has(key):返回一个布尔值,表示某个键是否在当前 Map 对象之中。
delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。
clear():clear方法清除所有成员,没有返回值。

keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。

3、应用
使用扩展运算符(…),将Map 结构转为数组结构
删除最久未使用的键值对:map.keys().next().value 获取第一个键

4、Map与数组、对象、JSON的互相转换
Map 转为数组:使用扩展运算符(…),[…map]
数组 转为 Map:将数组作为参数,传入 Map 构造函数
Map 转为对象:遍历赋值,如果map的键是非字符串,会将其转化为字符串
对象转为 Map:通过Object.entries(),然后数组转map
Map转为JSON:键名都是字符串,可以选择转为对象 JSON;键名有非字符串,可以选择转为数组 JSON(先转成对象或者数组)
键名都是字符串:转对象,对象转 map

5、注意
1)如果map的键是一个基本数据类型,则只要两个值严格相等,Map 将其视为一个键。
特殊:虽然NaN不严格相等于自身,但 Map 将NaN视为同一个键。
2)如果map的键是非基本数据类型(数组、对象等),只有内存地址不一样,才视为两个键。
3)对同一个键多次赋值,后面的值将覆盖前面的值。
4)Map 的遍历顺序就是插入顺序。

49、forEach,map 可以跳出循环吗

map,forEach里面所执行的都是函数个体,return一个,其余的也会继续执行;某种程度上来说,map、forEach是不能跳出本身的’循环’的;

从跳出数组本身来考虑,我们可以采用​​扔出异常​​的方式,来捕获异常,从而跳出’循环’:

try {
            [0,1,2,3,4,5].forEach(element => {
                if(element>3){
                    throw new Error('Irregular element')
                }
                console.log(element);// 0 1 2 3
            });
        } catch (error) {
            console.log(error.toString());//Error: Irregular element
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

50、设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

常见:

单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。

详细解说请参考:https://blog.csdn.net/u014723137/article/details/125639019

51、变量提升和函数提升、相关题型

JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理;

1、函数提升在变量提升前面进行,变量提升之后是 undefined,函数提升之后可以正常使用函数;
2、只有 var 声明的会提升,a=1 这种不会提升;函数只有声明式函数体会提升,函数表达式只有表达式赋值的变量提升;
3、函数声明和变量同时存在,声明会先提升,变量后提升,并且同名会覆盖前面的;
4、函数里面如 var a = b = 1 ;相当于 b = 1,是全局变量,之后 var a = b; 把 b 的值1 赋值给a,此时a是局部变量;
5、赋值时变量要先定义,var a=b;这个时候会报错 b is not defined
6、注意报错之后会阻断下面的运行,注意控制台输出问题;
7、函数内部修改变量,变量和入参名称一样,则这个变量是针对入参的修改;

var a = 10
function db(a){
	//a是对参数的修改,跟全局的 a 无关
	a = 0
}
db(20)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

52、class特性

1、constructor:是初始化 class对象 的特殊函数,称之为构造函数

class Square extends Polygon {
    constructor() {
        super();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

一个类中只能有一个名为 constructor 的函数;
如果不指定 constructor ,会使用默认构造函数(空函数);
constructor 中,可以用 super 调用父类的构造函数、属性、函数,但必须在this之前

2、extend:用于表示创建某个类的子类,实现继承

// 语法
class ChildClass extends ParentClass { ... }
  • 1
  • 2

3、super:用于调用父类构造函数、属性、函数。只能在构造函数中使用且在this之前。

4、new:用于创建用户定义的对象实例

总结:
class 创建出来的是一个函数;
调用 class 创建的函数,必须先 new 然后在调用,否则报错;

53、code-review的标准

1、代码规范检查:页面代码顺序、命名规范(方法名,文件名)、代码注释、公共方法的导入导出规范;
2、代码的正确性:代码逻辑是否符合文档需求,代码中的变量是否都被正确使用(非响应式变量提取),是否有冗余代码;
3、代码的稳定性:边界值的处理,对数据的操作是否影响原来数据、是否会被数据在增删影响,代码接口直接依赖关系处理是否得当;
4、代码可拓展性:一些公共方法是否独立、是否预留类似场景的拓展(日期处理),组件是否容易扩展(一些插槽的使用);
5、可重用性:新封装的方法、组件能否和现有的方法组件合并,公共方法的提取;

54、高并发对前端的影响

高并发:是指在同一个时间点,有很多用户同时的访问同一 API 接口或者 Url 地址;高并发会导致站点服务器/DB服务器资源被占满崩溃,甚至出现服务器宕机的情况;数据的存储不完整,数据更新异常问题

对页面的影响:接口相应时间长,页面加载慢、功能无法使用等;用户体验差;

55、移动端适配(rem和vw区别)

rem对标的是根目录的字体大小;而vw对标的是浏览器窗口;

rem:设计稿盒子的宽度(高度) / 根元素的字体大小 = 具体的rem单位值
vw:100vw / 浏览器窗口宽度 * 设计稿盒子的宽度 = 具体的vw单位值

可以使用二者结合;

56、oo和js 函数编程有什么不同

1、函数式编程:以函数思维做为核心,在这种思维的角度去思考问题。
支持:

闭包:一个函数内定义另一个函数
高阶函数:参数作为函数,或者返回值作为函数的函数
惰性计算:在将表达式赋值给变量的时候,不计算表达式的值,而是在变量第一次使用的时候再计算
独立性:函数不依赖外部的状态,也不修改外部的状态(只要我们输入的参数不变,那么输出的结果也一定是一样的)
无锁并发:因为函数的独立性,所以函数各个部分的执行顺序可以打乱,多个线程之间的执行不会互相干扰,不需要锁来保护,所以函数式编程的多线程并发是安全的

2、面向对象编程:面向对象把计算机程序视为一组对象的集合,基于对象的概念,以类作为对象的模板,把类和继承作为构造机制,以对象为中心,来思考并解决问题。
特征:

封装:把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,对数据的访问只能通过已定义的接口;
继承:继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等;
多态:多态指同一个实体同时具有多种形式(例如同一个接口,使用不同的实例而执行不同操作);

57、实现sleep函数,使用async怎么实现

sleep是一种函数,他的作用是使程序暂停指定的时间,起到延时的效果;

function sleep(time){
	return new Promise((resolve,reject)=>{
		setTimeout(resolve,time)
	})
}
async function run(){
	await sleep(1000)
	console.log('xxxxx')
	await sleep(2000)
	console.log('ssssss')
}
run()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

也可以直接基于promise,用 then 调用;

sleep(1000).then(()=>{
	console.log('xxxxx')
	sleep(1000).then(()=>{
		console.log('sssss')
	})
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

58、浏览器跨域是通过什么判断的

端口、协议、域名这三个任何一个不一样就是跨域;

59、reduce如何使用,空数组会导致那些问题

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 对于空数组是不会执行回调函数的(传入initialValue值则不会报错);

reduce()可以接收两个参数:第一个是回调函数(必填),第二个是 initialValue 设置回调函数的初始值(可选),作为第一次调用 callback 的第一个参数;回调函数有四个参数:total 初始值或者上一步返回值、currentValue 当前元素、currentIndex 当前元素下标、arr 当前元素所属数组;

常用场景:
1、数组各项直接累加或者累乘

let num = arr.reduce((total,current)=>{
	return a+b
})
  • 1
  • 2
  • 3

2、求数组各项之间的最大值

let num = arr.reduce((a,b)=>{
	return Math.max(a,b)
})
  • 1
  • 2
  • 3

3、数组去重

let arr1 = arr.reduce((a,b)=>{
	if(a.indexOf(b) == -1 ){
		a.push(b)
	}
	return a
},[]) //这里给一个数组作为初始值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4、二维数组扁平化

let arr1 = arr.reduce((a,b)=>{
	return a.concat(b)
},[])
  • 1
  • 2
  • 3

5、计算数组中元素出现的次数

let obj = arr.reduce((a,b)=>{
	if(b in a) {
		a[b] ++
	}else{
		a[b] = 1
	}
	return a
},{})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

60、声明函数的方式

函数声明

function a(){}
  • 1

函数表达式

let a = function(){}
  • 1

Function()构造器

var f =new Function()
  • 1

61、数据类型

基本数据类型:string、number、boolean、undefined、null、symbol、bigint
引用类型:object

62、js 的隐式转换

有三种类型:将值转化为原始值(ToPrimitive)、将值转化为数字(ToNumber)、将值转化为字符串(ToString);

1、ToPrimitive:一般目标是对象;
number:

如果输入的值已经是一个原始值,则直接返回它
否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
否则,抛出TypeError异常。

string:

如果输入的值已经是一个原始值,则直接返回它
否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
否则,抛出TypeError异常。

注意:任何对象都会有 valueOf 和 toString 方法
Number、Boolean、String 这三种构造函数生成的基础值的对象形式,通过 valueOf 转换后会变成相应的原始值。Date 这种特殊的对象转换为日期的毫秒的形式的数值;除此之外返回的都为 this,即对象本身;

Number、Boolean、String、Array、Date、RegExp、Function 这几种构造函数生成的对象,通过 toString 转换后会变成相应的字符串的形式;

2、ToNumber
undefined NaN null false 转换为 0,布尔值 true 转换 1, 数字无须转换,字符串有字符串解析为数字,‘qwer’转换为 NaN 对象(obj)先进行 ToPrimitive(obj, Number)转换得到原始值,在进行 ToNumber 转换为数字;

3、ToString
undefined ’undefined’,null ’null’ ,布尔值转换为 ’true’ 或 ‘false’,数字转换字符串,比如:1.765 转为’1.765’,字符串无须转换,对象(obj)先进行 ToPrimitive(obj, String)转换得到原始值,再进行 ToString 转换为字符串;

4、== 转换

undefined == undefined
null == null
0 == -0
undefined == null
'2' == 2
0 == false
'' == 0
'' == false
[] == 0、false

null != 'null'
undefined != 'undefined '
false != 'false'
NaN != NaN
[] != []、null、undefined
{} !={}、 0、false、true、null、undefined
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

63、js和ts区别

相同:
他们都遵循 ES 的标准;
他们都是弱类型的语言;

不同:
ts 是 js 的超集中可以正常编写 js 代码,适合项目的逐渐迁移;
ts 的类型检查,变量的类型在运行前就会被确认;
ts 的静态类型,在编译时就会进行错误检查,js 的动态类型,在运行时进行错误检查;
ts 在运行前需要编译成 js 才能在浏览器等 js 可运行的环境上运行;

64、手写new

function myNew(obj,...argus){
		//创建一个对象,并将指向原型链
		let res = Object.create(obj.prototype);
		//执行构造函数内部代码
		let ret = obj.apply(res,argus);
		return ((typeof ret === 'object' && ret !== null) || typeof ret === 'function') ? ret : res
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

65、async 、await的原理

在es7中提出了async/await来让我们能够用同步的方式来实现异步;主要是为了解决多个Promise函数产生的嵌套层级过多的问题;

async await是基于Generator实现的代码中断操作,async 相当于 *,await 相当于 yield;函数构造器:函数内部可以使用yield关键字来暂停函数的执行,切换到外部函数,其实就是协程的切换。在外部函数中可以通过调用next()方法来恢复函数的执行。

async 标记的函数的返回值是一个 promise, await 本身就相当于一个 promise;他们都支持 then、catch 链式调用;

66、es6以及新版本

ES6:

let、const、解构赋值、变量声明(逗号隔开,一次声明多个)、简化对象(赋值时同名可省略)、箭头函数、扩展运算符、Symbol、rest 参数(…args)、迭代器、函数生成器、promise、Set、Map、Class、模块化(import、export)

ES7:

include:检测数组中是否包含某个元素
指数运算符:**,2 ** 10类似 Math.pow

ES8:

async、await
对象方法扩展(values、entries)

ES9:

为对象提供了 rest和扩展运算符
正则表达式命名捕获(?)
正则表达式反向断言(<=)
正则表达式 dotAll 模式:正则表达式中点.匹配除回车外的任何单字符,标记『s』改变这种行为,允许行 终止符出现

ES10:

Object.fromEntries
trimStart 和 trimEnd
flat(数组降维)、flatMap(遍历数组)
Symbol.prototype.description(获取描述)

ES11:

Promise.allSettled
私有属性(#属性名)
matchAll(正则批量匹配)
动态import:可以实现模块化按需加载
可选链操作符:对象属性的层级比较深,可以用来简化访问:config && config.db && config.db.host => config?.db?.host
BigInt大整形
glabalThis:操作全局对象

ES12:

replaceAll:正则所有匹配都会被替代项替换
Promise.any、
逻辑赋值操作符 :逻辑空赋值(左侧为 null、undefined时对左侧赋值):??=、逻辑与赋值(左侧为true时对左侧赋值):&&=、 逻辑或赋值(左侧为false时对左侧赋值):||=
WeakRef:是一个 Class,可以让你拿到一个对象的弱引用,可以用 WeakRef.prototype.deref() 来取到 anObject 的值。但是,在被引用对象被垃圾回收之后,这个函数就会返回undefined
下划线 (_) 分隔符:使用了数字分隔符 _ (下划线),就可以让数字读的更清晰(1_000 = 1000)
Intl.ListFormat:是一个构造函数,用来处理和多语言相关的对象格式化操作
Intl.DateTimeFormat API 中的 dateStyle 和 timeStyle 的配置项:

ES13:

Object.hasOwn():检查一个属性是否属于该对象,替代 Object.prototype.hasOwnProperty()
at():数组方法,用于通过给定索引来获取数组元素;也可操作字符串
正则表达式匹配索引:利用 d 字符来表示想要匹配字符串的开始和结束索引
Error Cause:目的主要是为了便捷的传递导致错误的原因
类:公共字段、静态公共字段(static 只可访问不可修改)、私有字段(#字段名,不可访问和修改)
顶层 await:允许我们在 async 函数外使用 await 关键字

67、手写bind

Function.prototype.myBind = function(context,...arg){
		//this指的是 myBind 调用的函数
		if(typeof this !== function) return;
		//缓存需要指向的this
		let fn = this;
		return function(...args){
			fn.call(context,...arg,...args)
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

68、定时器加循环

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log( i);
    }, 1000);
}
console.log( i);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

控制台输出:5、55555

原因:i 是全局变量,循环5次,i变为 5;定时器是异步等同步循环结束之后打印,5个5;

想输出 0、1、2、3、4怎么操作?
1、利用闭包,用一个匿名函数包裹定时器,将 i 一参数的形式传入;
2、利用函数作用域,将定时器提出来放到一个函数里面,循环中引入函数,i以参数形式传入;
3、用 let 代替 var;

69、操作对象的方法

1、Object.definedProperty
直接在一个对象上定义一个新属性,或者修改一个已经存在的属性;

Object.defineproperty(obj, prop, desc ):obj 是定义属性的对象,prop 是定义的属性,desc 是描述(属性值,属性可枚举、可修改、可删除、get、set)

Object.defineProperty(person,'age',{
	value:18, // 属性值
	enumerable:true, //控制属性是否可以枚举,默认值是false
	writable:true, //控制属性是否可以被修改,默认值是false
	configurable:true //控制属性是否可以被删除,默认值是false
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

默认是不可枚举、不可修改、不可删除(delete也删除不掉),当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错滴);

注意:Object.defineProperties(object, descriptors); 可以一次定义多个属性;

2、删除属性
delete:删除对象上的某个可删除属性,会删除值和属性本身;对变量或函数没有影响;delete 关键词不会删除原型链上继承的属性,但是如果您删除了某个原型属性,则将影响到所有从原型继承的对象;(全局变量会定义在 window 上,是可以通过 delete 删除的);

可根据指定下标删除数组元素;

delete person.age;
  • 1

Reflect.deleteProperty(target, propertyKey):ES6 新增;删除对象的属性。如果该方法返回true,则表示删除属性成功。否则,它将返回false;

如果目标不是对象,则发生TypeError;删除数组元素时,propertyKey指的是下标;

3、是否有某个属性
hasOwnProperty:obj.hasOwnProperty(prop);对象自身属性中(非继承属性)是否具有指定的属性;js 没有保护hasOwnProperty属性名,如果对象中有同名的,会被影响;(如果对象是用Object.create(null)创建的话,那么就不能用这个方法了)

Object.prototype.hasOwnProperty(‘toString’):防止出现上面的问题,直接使用原型上的 hasOwnProperty 来判断,更准确;

Object.hasOwn():ES13新增,简化 hasOwnProperty 的操作;

in:检查属性是否存在于对象中,检查的属性包括原型链上的属性;

Reflect.has() :跟 in 一样;

4、获取属性
Object.keys():获取可枚举的全部属性组成的数组;可以获取原型链上可枚举属性;

Object.getOwnPropertyNames():指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组;不会获取到原型链上的属性;

Object.getOwnPropertyDescriptor(obj,prop) 获取该属性的描述对象;也就是 { value: 123, writable: true, enumerable: true, configurable: true }

Object.getOwnPropertyDescriptors(obj) 返回指定对象所有自身属性(非继承属性)的描述对象;

Object.getOwnPropertySymbols():对象自身上找到的所有 Symbol 属性的数组;

5、Object.isExtensible():判断对象是否可扩展;可扩展的意思是可以给对象添加属性;

6、Object.preventExtensions(obj):禁止扩展

7、Object.setPrototypeOf(a,b):为对象设置原型链,为a设置原型链是b;

8、Object.getPrototypeOf: 获取指定对象的原型;注意:Object.getPrototypeOf( Object ) === Function.prototype;

9、Object.freeze:冻结对象;isFrozen 判断一个对象是否已经被冻结;不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象;

只能冻结一层;可以通过递归冻结所有的层级属性;

10、Object.seal:密封对象; isSealed 判断一个对象是否为密封的;不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可能可以修改已有属性的值的对象;

11、Object.assign( target, source, source1 ):对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target);不拷贝继承属性;

12、propertyIsEnumerable():(a.propertyIsEnumerable(b))指定的属性是否可枚举;

13、Object.is(a,b):比较两个值是否严格相等;

14、fn.caller :返回当前函数的调用者;

15、toValue()、toString()、call、apply、band、name

71、移动端适配方案

1、媒体查询:通过查询设备的宽度来执行不同的 css 代码;
所有的元素都得在不同的 @media 中定义一遍不同的尺寸,这个代价有点高;

2、rem :根据根节点字体大小来设置;可以配合媒体查询对不同尺寸的设备设置不同的根节点大小
转换比例 = 设计稿宽度 / 字体大小

rem = px / 转换比例

rem 布局对小数值不友好,容易出现偏差

3、flexible:使用 js 动态来设置根字体

4、viewport:浏览器屏幕就是 100 vw;

73、BOM 对象

1、window:网页中任何一个对象都是在window这个对象里面的(定时器、alert)

2、location:获得当前窗口中加载的文档有关的信息或者设置窗体的URL(href、hash)

3、history:保存着用户上网的历史记录,从窗口被打开的那一刻算起

4、navigator:保存着浏览器的各种信息(版本、插件)

74、数据检测方法

1、typeof :(数组,null和对象都是object,不能区分),能检测undefined、Boolean、number、string、object、function、symbol

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
  • 1
  • 2
  • 3

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  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、constructor属性: constrcutor 对象访问它的构造函数;

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

75、手写 instanceof

// 判断构造函数的原型是否出现在对象的原型链上的任何位置
// left对象 right构造函数
function myInstanceof (left,right){
// 1.获取对象的原型
let proto = Object.getPrototypeOf(left)
// 2.获取构造函数的prototype对象
let prototype = right.prototype
while(true){
    // 对象的原型不存在则返回或者递归判断的结束条件
    if(!proto) return false
    // 当构造函数的原型和对象的原型相等
    if(proto = prototype) return true
    // 如果没有找到,就继续从原型上去找,通过Object.getPrototype方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto)
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

76、js 包装类型

基本类型是没有属性和方法的,但是为了便于操 作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在 后台隐式地将基本类型的值转换为对象;

使用Object ()函数显式地将基本类型转换为包装类 型
valueOf ()方法将包装类型倒转成基本类型

77、Object.is() 与比较操作符 “=== ” 、 “==” 的区别?

使用双等号==进行相等判断时,如果两边的类型不一致,则会进 行强制类型转化后再进行比较。
使用三等号===进行相等判断时,如果两边的类型不一致时,不 会做强制类型准换,直接返回 false。
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相 同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的 一般情况下NaN不与任何类型相等包括自身

78、如何获取一个安全的 undefined 值

undefined 是基础数据类型,可以作为变量来赋值,但是会影响到 undefined 的正常判断;可以用 volid 关键字,使用void 0来代替undefined;

79、js 延迟加载的方式

添加属性 async 、defer
使用延时器
js放在最后面
动态创建 script 标签引入 js

80、JSON的理解

是一种轻量级的数据交换格式,是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小;

JSON和XML区别:
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
JSON对数据的描述性比XML较差
JSON的速度要远远快于XML

81、Ajax原理

Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面;

优点:
通过异步模式,提升了用户体验.
优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
Ajax可以实现动态不刷新(局部刷新)
缺点:
安全问题 AJAX暴露了与服务器交互的细节。
对搜索引擎的支持比较弱。
不容易调试

82、XML和JSON的区别

JSON相对于XML来讲,数据的体积小,传递的速度更快些。
JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
JSON对数据的描述性比XML较差
JSON的速度要远远快于XML

83、undefined 和 null区别

1、undefined 表示未定义,null 表示值为空
2、null 是一个对象,没有任何属性和方法
3、变量被生声明单身没有赋值,则为 undefined
4、判断 null 需要用 === ;因为 null==undefined

84、 JS 的遍历方式,以及几种方式的比较

forEach
map
for
for in:枚举的是键名,枚举原型上所有可枚举对象,返回下标;
for of:枚举的是键值,枚举的是对象可枚举的值,返回下标对应的值;

85、数组乱序

var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
    return Math.random() - 0.5;
})
console.log(arr);
  • 1
  • 2
  • 3
  • 4
  • 5

86、window.onload和$(document).ready

window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。
$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕

87、ajax、axios、fetch区别

ajax 是一种技术统称
fetch 一个原生API
axios 一个第三方库

88、JavaScript如何实现一个类,怎么实例化这个类

1、构造函数
用到了 this 和 prototype,编写复杂,可读性差

function Mobile(name, price){
  this.name = name;
  this.price = price;
}
var iPhone7 = new Mobile("iPhone7", 1000);
  • 1
  • 2
  • 3
  • 4
  • 5

2、Object.create
不能实现私有属性和私有方法,实例对象之间也不能共享数据

var Person = {
     firstname: "Mark",
     lastname: "Yun",
     age: 25,
     introduce: function(){
         alert('I am ' + Person.firstname + ' ' + Person.lastname);
     }
 };
 var person = Object.create(Person);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3、ES6 语法糖 class – 用 new 关键字 生成实例对象

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

var point = new Point(2, 3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

89、JavaScript 中,调用函数有哪几种方式

方法调用模式 Foo.foo(arg1, arg2);
函数调用模式 foo(arg1, arg2);
构造器调用模式 (new Foo())(arg1, arg2);
call/applay调用模式 Foo.foo.call(that, arg1, arg2);
bind调用模式 Foo.foo.bind(that)(arg1, arg2)();

90、有四个操作会忽略enumerable为false的属性(不可枚举)

for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

91、原型链判断

Object.prototype.proto; //null
Function.prototype.proto; //Object.prototype
Object.proto; //Function.prototype
Object instanceof Function; //true
Function instanceof Object; //true
Function.prototype === Function.proto; //true

Arrayfrom和ArrayOf

其他

1、Math.max 没有传递参数时返回的是-Infinity;Math.min没有参数,则返回 Infinity;
2、循环数组中有空位时
forEach(), filter(), reduce(), every() 和some()都会跳过空位。
map()会跳过空位,但会保留这个值
join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串
ES6 中都会将空位当做undefined

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/339845
推荐阅读
相关标签
  

闽ICP备14008679号