前端面试题目100及最佳答案

注册登录是怎么实现的 1.登陆注册要做成受控组件,组件定义state,和表单绑定 2.redux-saga调用数据请求,发送action修改数据, useEffect中dispatch发送数据请求,后端比对用户名是否重复,返回state 3.前端根据返回的信息成功跳转登陆页 4.登陆发送数据请求,数据库对比用户名密码是否正确, 根据后端返回的结果进入首页 5

注册登录是怎么实现的

1.登陆注册要做成受控组件,组件定义state,和表单绑定
	
2.redux-saga调用数据请求,发送action修改数据,
  useEffect中dispatch发送数据请求,后端比对用户名是否重复,返回state

3.前端根据返回的信息成功跳转登陆页

4.登陆发送数据请求,数据库对比用户名密码是否正确, 根据后端返回的结果进入首页

5.setCookie将用户登录名密码token存cookie中   通过JWT(Json web token)

6.免密登陆  getCookie获取token    发给后端对比    根据返回结果是否自动登陆

7.注册通过Ant  Design ,validator中进行表单正则的验证

8.用户体验  注册的时候跳转其他页面的时候给用户提示是否需要跳转,避免因为跳转后导致注册信息没有了   
  用组件内后置守卫做
  如果输入框都没有填信息,不拦截跳转如果用户输入信息,弹窗提示,点确定,跳转,点取消,不跳转

后台管理系统和普通App面向用户的区别

toB和toC的项目

面向企业内部和面向用户的项目的区别

后台管理系统权限比较细   App高并发比较多  做性能优化

正则表达式

\d 匹配数字  \D 匹配非数字  \w 匹配数字字母下划线 \W 匹配非数字字母下划线  
\n 匹配一个换行符 \s 匹配任何不可见字符包括空格、制表符、换页符等等  \S 匹配任何可见字符
^ 匹配输入字行首   $匹配输入行尾 *(0到多次)匹配前面的子表达式任意次   
+(1到多) 匹配前面的子表达式一次或多次(大于等于1次)
?(0或1)匹配前面的子表达式零次或一次 
{n}n是一个非负整数,匹配确定的n次
{n,}n是一个非负整数。至少匹配n次

Ajax

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。
创建步骤:创建xhr对象->配置Ajax请求地址通过open方法->发送请求通过send方法->监听请求,接收响应
//1:创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax请求地址
xhr.open('get','index.xml',true);
//3:发送请求
xhr.send(null); // 严谨写法
//4:监听请求,接受响应
xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304)
          console.log(xhr.responseXML)
}

js 延迟加载的方式

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
我了解到的几种方式是:

1、将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
2、给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
3、给 js 脚本添加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
4、动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

模块化开发的理解

我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,
所以并没有模块的概念,但随着程序越来越复杂,代码的模块化开发变得越来越重要。

由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,
但是这种方式容易造成全局变量的污染,并且模块间没有联系。

后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,
但是这种办法会暴露所有的所有的模块成员,外部代码可以修改内部属性的值。

现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。

js 的几种模块规范

js 中现在比较成熟的有四种模块加载方案:

第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。

AMD和CMD规范的区别?

它们之间的主要区别有两个方面。
第一个方面是在模块定义时对依赖的处理不同。AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。
第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句
的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。

ES6模块与CommonJS 模块、AMD、CMD的差异

1.CommonJS 模块输出的是一个值的拷贝,
ES6 模块输出的是值的引用。
CommonJS 模块输出的是值的,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
2.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

requireJS的核心原理

require.js 的核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数

js的原理(运行机制)

首先js是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在执行同步代码的时候,如果遇到了异步事件,js引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。

当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

异步运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。
	只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。
	那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

arguments 的对象

arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,
我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,
如:forEach、reduce、filter和map。

我们可以使用Array.prototype.slice将arguments对象转换成一个数组。

Array.prototype.slice.call(arguments)     

V8 引擎的垃圾回收机制

v8的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。
新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。
新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge(斯盖V橘) 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步:
(1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。
(2)如果对象不存活,则释放对象的空间。
(3)最后将 From 空间和 To 空间角色进行交换。
新生代对象晋升到老生代有两个条件:
(1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。
(2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。

老生代采用了标记清除法和标记压缩法。
标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。
由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行

垃圾回收机制的两种方法

现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。

1、标记清除
  这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
  垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。

2、引用计数
  另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

会造成内存泄漏的操作

1.意外的全局变量
2.被遗忘的计时器或回调函数
3.脱离 DOM 的引用
4.闭包

第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
第二种情况是我们设置了setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
第三种情况是我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。

ECMAScript(ES)

ECMAScript 是编写脚本语言的标准,ECMA(European Computer Manufacturers Association)规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM

ES2015(ES6)新特性

块作用域
类
箭头函数
模板字符串
对象解构
Promise
模块
Symbol
代理(proxy)Set
函数默认参数
rest
扩展运算符
数组和对象的扩展

ES2016(ES7)新特性

求幂运算符(**)
                (因颗录此)
Array.prototype.includes()方法
	数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。
	该方法接受两个参数,分别是查询的数据和初始的查询索引值。

ES2017(ES8)新特性

async  await

函数参数列表结尾允许逗号
	es2017允许函数对象的定义调用时参数可以加入尾逗号,以及json对象array对象都允许
	
Object.values()
	values: [obj],返回obj自身可枚举属性的属性值的集合
	   (安吹斯)
Object.entries()
	entries:[obj], 与values类似,返回的一个2元素的数组
	   
Object.getOwnPropertyDescriptors()
	getOwnpropertyDescriptors: [obj],返回obj对象的属性描述符
	(get 哦 泼破踢 迪斯亏不踢斯)
String padding: 
	padStart()和padEnd(),填充字符串达到当前长度
	在字符串首位开始添加string直到满足length为止并返回新的字符串
	
ShareArrayBuffer和Atomics对象,用于从共享内存位置读取和写入
(夏尔 啊锐 八法儿)	 (啊偷没此)

ES2018(ES9)新特性

异步迭代
Promise.finally()
		(饭的嘞)
Rest/Spread 属性
(锐斯特)(斯破锐的)
正则表达式命名捕获组(Regular Expression Named Capture Groups)
正则表达式反向断言(lookbehind)
正则表达式dotAll模式 
正则表达式 Unicode 转义  
		 (右内扣的)
非转义序列的模板字符串

ES2019(ES10)新特性

行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配
更加友好的JSON.stringify
新增了Array的flat()方法和flatMap()方法
新增了String的trimStart()方法和trimEnd()方法
Object.fromEntries()
Symbol.prototype.description
String.prototype.matchAll
Function.prototype.toString()现在返回精确字符,包括空格和注释
简化try{} catch{},修改catch绑定
新的基本数据类型BigInt
globalThis
import()
Legacy RegEx
私有的实例方法和访问器

ES2020(ES11)新特性

私有变量
	ES11在类中新增私有变量控制符#,在内部变量或者函数前添加一个hash符号#,
	可以将它们设置为私有属性,只能在类的内部可以使用。
空值合并运算符
	空值合并操作符就是 ?? :如果左侧的值为null或者undefined就返回左侧的值,如果没有就返回右侧的值
可选链操作符
	可选链操作符 (?.) :如果左侧表达式有值,就会继续访问右侧的字段
BigInt
	使用BigInt的方式有两种:
		1.在数字后面加n
		2.使用BigInt函数
动态导入

globalThis
	提供一种标准化的方式去访问全局对象,可以在任意上下文中获取全局对象自身,并且不用担心环境的问题
Promise.all缺陷与Promise.allSettled
	promise.all可以并发执行异步任务,如果其中某个任务执行出现了异常,所有任务都会over,Promise会直接进入reject状态
	使用Promise.allSettled,它会创建一个新的promise,在所有promise完成后返回一个包含每个promise结果的数组

var,let和const的区别

1.var声明的变量会挂载在window上,而let和const声明的变量不会:
2.var声明变量存在变量提升,let和const不存在变量提升
3.let和const声明形成块作用域
4.同一作用域下let和const不能声明同名变量,而var可以

const  
	一旦声明必须赋值,不能使用null占位。
	声明后不能再修改
	如果声明的是复合类型数据,可以修改其属性

暂存死区
var a = 100;
if(1){
    a = 10;
    //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
    // 而这时,还未到声明时候,所以控制台Error:a is not defined
    let a = 1;
}

var和let在for循环的一些不同表现

在var中执行的时候:	
	因为var是没有块级作用域的,所以在for循环中声明的i会存在于test()函数作用域中。
	每一次for循环就会声明一次i,但后面声明的变量会覆盖前面声明的变量。
在let中执行的时候:
	因为块级作用域的原因,let声明的i都会存在于for块级作用域中,每一次for循环都会生成一个块级作用域。

箭头函数

箭头函数表达式的语法比函数表达式更简洁

箭头函数没有自己的this值。箭头函数里的this指向的是父级的this. 

箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

在箭头函数版本中,当只有一个表达式或值需要返回,我们只需要()括号,
不需要 return 语句,箭头函数就会有一个隐式的返回。

如果我们在一个箭头函数中有一个参数,则可以省略括号。

箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。
相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。

模板字符串

模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。

在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。

在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用`+`运算符。
在模板字符串s中,我们可以使用${expr}嵌入一个表达式,这使其比 ES5 版本更整洁。

对象解构

对象解构是从对象或数组中获取或提取值的一种新的、更简洁的方法
我们还可以为属性取别名
let { firstName: fName, position } = employee;
当然如果属性值为 undefined 时,我们还可以指定默认值
let { firstName = "Mark" } = employee;

Set及应用场景

一、创建Set对象实例
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
1.构造函数	 	   (伊特波)
	语法:new Set([iterable])
	参数:iterable 如果传递一个可迭代对象,它的所有元素将被添加到新的Set中;
		 如果不指定此参数或其值为null,则新的 Set为空

二、Set实例属性
	size属性将会返回Set对象中元素的个数

三、Set实例方法
1.add() 方法用来向一个 Set 对象的末尾添加一个指定的值
	语法:mySet.add(value)
	参数:value 必需,需要添加到 Set 对象的元素的值
2.delete() 方法可以从一个 Set 对象中删除指定的元素
	语法:mySet.delete(value)
	参数:value 将要删除的元素
	返回值:成功删除返回 true,否则返回 false
3.clear() 方法用来清空一个 Set 对象中的所有元素
	语法:mySet.clear()
4.has() 方法返回一个布尔值来指示对应的值value是否存在Set对象中
	语法:mySet.has(value)
	参数:value 必须,是否存在于Set的值
	返回值:如果指定的值(value)存在于Set对象当中,返回true; 否则返回 false
5.entries() (安吹斯)
	语法:mySet.entries()
	返回值:一个新的包含 [value, value] 形式的数组迭代器对象,value 是给定集合中的每个元素,
		  迭代器 对象元素的顺序即集合对象中元素插入的顺序
6.values()
	语法:mySet.values() 或者 mySet.keys()
	返回值:返回一个 Iterator(因特瑞特) 对象,这个对象以插入Set对象的顺序包含了原 Set 对象里的每个元素
7.forEach()
	语法:mySet.forEach(callback[, thisArg]) (this傲歌)
	参数:callback 每个元素都会执行的函数
     	 thisArg 当执行callback函数时候,可以当作this来使用
5、什么是WeakSet()?
和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象(null 除外)。
而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。

应用场景:
1、简单数组去重
2、JSON数组去重
	JSON数组是比较常见的一种数据结构,形如[{…},…{…}]
	假如你需要统计某个属性中不同的值。
	step1:先使用.map将JSON数组变成简单数组,然后用set执行去重
	step2: 由于生成的Set属于可迭代对象,所以可以使用数组解构符进行解构
3、二维数组去重
	我们可以将Set用作存储每项的唯一值,结合reduce进行对比,得出无重复的项目。
	当然,上面的代码缺点还是不少的。因为只是简单地将其转变成字符串作为对比的键,
	所以不能区分[1,2]、[‘1’ ,‘2’]、[‘1,2’]等子数组。
4、数组之间的对比
	Set的特性不单单可以可以用于单数组,对于数组之间的比较也是十分在行。

什么是WeakSet()?
	和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象。

WeakSet的API:
	add() 	 //增
	delete() //删
	has() 	 //是否存在
	
为什么WeakSet不可遍历?
	因为WeakSet的成员都是弱引用,随时可能消失,成员是不稳定的。

WeakSet的用处:
	使用ws储存DOM节点,就不用担心节点从文档移除时,会引发内存泄漏(即在被移除的节点上绑定的click等事件)。

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”

函数式编程

函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。
函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范式的其他示例也包括面向对象编程和过程编程。
函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试

高阶函数

首先高阶函数肯定是函数,不同的是输入的参数和返回的值这两项中的一项必须是函数才能叫高阶函数。
这个问题在回答的时候可以稍微拓展一下,介绍一下常用的的高阶函数,
比如:map、flatMap、filter、reduce、fold。

为什么函数被称为一等公民

在JavaScript中,函数不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样:

赋值(var func = function(){})、
传参(function func(x,callback){callback();})、
返回(function(){return function(){}}),

这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)(因斯疼斯)。这样的多重身份让JavaScript的函数变得非常重要。
									

. new操作符的实现

1、创建一个空对象 {}
2、将构造函数中的this指向新创建的对象 (原型链)
	obj.__proto__ = Dog.prototype // 设置原型链
3、链接该对象到另一个对象 __proto__
4、如果该函数没有返回对象,则返回this

回调函数?回调函数有什么缺点

回调函数是一个匿名函数,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。可以让异步代码同步执行。
回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)

instanceof的原理

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
实现 instanceof:

首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}

设计模式

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
1、单例模式
2、工厂模式
3、观察者模式
4、代理模式
5、策略模式
6、迭代器模式

单例模式(Singleton Pattern)
	单例模式中Class的实例个数最多为1。当需要一个对象去贯穿整个系统执行某些任务时,单例模式就派上了用场。而除此之外的场景尽量避免单例模式的使用,因为单例模式会引入全局状态,而一个健康的系统应该避免引入过多的全局状态。
	
工厂模式
	工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
使用场景:如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }}
class Factory {
    create(name) {
        return new Product(name)
    }
}
// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
https://juejin.im/post/6844904200917221389#heading-81  

js的基本数据类型、值是如何存储的

JavaScript一共有8种数据类型
7种基本数据类型:
Undefined、Null、Boolean、Number、String、Symbol(es6新增,表示独一无二的值)和BigInt(es10新增)

1种引用数据类型
Object(Object本质上是由一组无序的名值对组成的)。
里面包含 function、Array、Date等。

JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。
原始数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

&& 、||和!! 运算符

&& 叫逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最后一个真值表达式。它采用短路来防止不必要的工作。
|| 叫逻辑或,在其操作数中找到第一个真值表达式并返回它。这也使用了短路来防止不必要的工作。在支持 ES6 默认函数参数之前,它用于初始化函数中的默认参数值。
!! 运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法

js的数据类型的转换

在 JS 中类型转换只有三种情况,分别是:

转换为布尔值(调用Boolean()方法)
转换为数字(调用Number()、parseInt()和parseFloat()方法)
转换为字符串(调用.toString()或者String()方法)

js 内置对象

js 中的内置对象主要指的是
在程序执行前存在全局作用域里的由 js定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。
一般我们经常用到的如全局变量值 NaN、undefined,
全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,
还有提供数学计算的单体内置对象如 Math 对象。

null 和 undefined 的区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,
null 代表的含义是空对象(其实不是真的对象,请看下面的注意!)。一般变量声明了但还没有定义的时候会返回 undefined,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。
当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等 号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

{}和[]的valueOf和toString的结果

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"
[] 的 valueOf 结果为 [] ,toString 的结果为 ""

js 的作用域和作用域链

作用域: 
	作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。

作用域链: 
	作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。
	作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。

js 创建对象的方式

	我们一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象创建方式,我了解到的方式有这么几种:

(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。

(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此我们可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。

(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。

(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。

对this、call、apply和bind的理解

1、在浏览器里,在全局范围内this 指向window对象;
2、在函数中,this永远指向最后调用他的那个对象;
3、构造函数中,this指向new出来的那个新的对象;
4、call、apply、bind中的this被强绑定在指定的那个对象上;
5、箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
6、apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参。

什么是 DOM 和 BOM

DOM指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口。
BOM指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。

BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,
又是一个 Global(全局)(歌楼波)对象。
这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。
window 对象含有 location 对象、navigator(那V给特)对象、screen(斯歌锐)对象等子对象,
并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

三种事件模型

事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
DOM0级模型: 
	这种模型不会传播,所以没有事件流的概念,同元素 绑定多个事件,只会绑定最后一次的事件,前面的会被覆盖。

IE 事件模型:
	在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。
	事件处理阶段会首先执行目标元素绑定的监听事件。
	然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,
	依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
	这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,
	会按顺序依次执行。detachEvent删除事件
	
DOM2 级事件模型: 
	在该事件模型中,一次事件共有三个过程,
	第一个过程是事件捕获阶段事件处理阶段,和事件冒泡阶段。
	捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,
	如果有则执行。。
	这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
	取消事件removeEventListener

事件委托(代理)

本质上是利用了事件冒泡的机制。
并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,
由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托
支持为同一个DOM元素注册多个同类型事件,可将事件分成事件捕获和事件冒泡机制

事件传播

事件传播有三个阶段:
捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或event.target。
目标阶段–事件已达到目标元素。
冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。

什么是事件捕获
当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从window开始,一直到触发事件的元素。
什么是事件冒泡?
事件冒泡刚好与事件捕获相反,当前元素---->body ----> html---->document ---->window。当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window为止。

DOM 操作

(1)创建新节点
 	createDocumentFragment()    //创建一个DOM片段 (科瑞A特)(法歌们特)
 	createElement()             //创建一个具体的元素
 	createTextNode()   		    //创建一个文本节点

(2)添加、移除、替换、插入
	appendChild(node) 
	removeChild(node)
	replaceChild(new,old) (锐普利斯)
	insertBefore(new,old) (因色特比佛)

(3)获取、查找
	getElementById();
	getElementsByName();
	getElementsByTagName();
	getElementsByClassName();
	querySelector(); (斯来科特)
	querySelectorAll();

(4)属性操作
	getAttribute(key); (去比又特)
	setAttribute(key, value);
	hasAttribute(key);
	removeAttribute(key);

数据检测类型判断

检测方法4种

 1、Object.prototype.toString.call()
    作用: 可以检测所有数据类型
    所有数据类型都可以检测,而且非常正确
    语法: Object.prototype.toString.call( 'xxx'/11/[ ] )
    返回值: [object Xxx], Xxx 就是对象的类型
 2、constructor
    作用: 可以检测基本数据类型和引用数据类型
    弊端: 把类的原型进行重写, 很有可能把之前的constructor覆盖 检测出来的结果就会不准确
    语法: ("xx")/([])/(function(){}).constructor === String/Array/Function	
    返回值: true/false
3、instanceOf
    原理: 判断对象类型,基于原型链去判断(obj instanceof Object)
    左边对象的原型链proto上是否有右边构造函数的proptotype属性
    作用: 判断左边的对象是否是右边构造函数的实例
    弊端: 用于引用类型的检测, 对于基本数据类型不生效
    语法: " "/[ ]/true instanceOf String/Array/Boolean
    返回值: true/false
4、typeOf
    作用: 用于检测基本数据类型和函数
    弊端: 引用数据类型(Arrary/function/object)只会返回Object, 不起作用
    语法: typeOf " "/[ ]/xx
    返回值: "string"/"boolean"/"object" (无法区分)

原型/原型链

原型:
Javascript规定,每一个函数都有一个prototype对象属性,指向另一个对象
prototype就是调用构造函数所创建的那个实例对象的原型

原型链:
实例对象与原型之间的连接,叫做原型链。
JS在创建对象的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。

获取原型的方法
p.proto
p.constructor.prototype
Object.getPrototypeOf(p)

prototype、proto、constructor三者的关系

1、prototype:
每一个函数都有一个prototype这个属性,而这个属性指向一个对象,这个对象我们叫做原型对象 
作用:节约内存扩展属性和方法可以实现类之间的继承 
2、__proto__: 
每一个对象都有一个__proto__属性,__proto__指向创建自己的那个构造函数的原型对象对象可以直接访问__proto__里面的属性和方法 
3、constructor:
指向创建自己的那个构造函数 ,是原型上的方法

总结:
当我们创建一个构造函数的时候这个构造函数自带了一个prototype属性,而这个属性指向一个对象,也就是原型对象。 这个原型对象里面有一个constructor构造器,它的作用是指向创建自己的构造函数。
除此之外 prototype还可以存放公共的属性和方法。 
当我们实例化一个对象的时候(被new调用的时候),这个对象自带了一个 proto 属性,
这个proto 指向创建自己的构造函数的原型对象。可以使用这个原型对象里面的属性和方法

constructor与class的区别

传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。
原型对象特点就是将自身的属性共享给新对象。 

ES6引入了Class(类)这个概念,通过class关键字可以定义类。
该关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言

constructor: 
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
一个类必须有constructor()方法,如果没有显式定义,一个空的 constructor()方法会被默认添加。

构造函数与普通函数的区别

1. 返回值类型的区别:
	构造函数是没有返回值类型的,
	普通函数是有返回值类型的,即使函数没有返回值,返回值类型也要写上void。
2. 函数名的区别:
	构造函数的函数名必须要与类名一致,
	普通函数的函数名只要符合标识符的命名规则即可。
3. 调用方式的区别:
	构造函数是在创建对象的时候由jvm调用的。
	普通函数是由我们使用对象调用的,一个对象可以对象多次普通的函数,
4. 作用上的区别:
	构造函数的作用用于初始化一个对象。
	普通函数是用于描述一类事物的公共行为的。

跨域出现的原因/解决方法

原因:由于浏览器的同源策略,即属于不同域的⻚面之间不能相互访问各自的⻚面内容。

哪些情况下产生跨域
1、域名不同
2、端口号不同
3、协议不同(http/https)
4、域名和域名对应ip
5、主域名相同(127.0.01 和 localhost) 多域名匹配一个ip地址
6、子域名不同(一级和二级域名)

解决方法

1、后端代理 
后端不存在跨域(后端代码脱离浏览器,后端是服务器端)
利用后端(自己公司的后端)去获取接口数据,将数据传给前端
2、jsonp
原理:
	利用浏览器的"漏洞" src不受同源策略的影响,可以请求任何链接 。动态创建script标签,将事先写好的函数名传给服务器,供服务器使用
(1)script标签src属性不存在跨域
(2)get方式--传递函数名 --弊端
(3)callback回调函数(传递函数名)
3、反向代理  
proxy  webpack配置
"proxy": {
    "/index.php": {
      "target": "http://qinqin.net",
      "changeOrigin": true
    }
 }
4、CORS解决跨域(xhr2)(后端)
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
需要服务器(提供接口的源码里面)添加下面两句话。
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Method:POST,GET');

jsonp是一种非正式传输协议,用于解决跨域问题流程: 
1、创建一个全局函数 
2、创建一个script标签 
3、给script添加src 
4、给src添加回调函数test(callback=test) callback是传给后端的一个参数
5、将script放到⻚面上 
6、script请求完成,将自己从⻚面上删除

闭包原理/优点/缺点/使用场景

闭包原理:定义在一个函数内部的函数(函数嵌套函数),闭包就是将函数内部和函数外部连接起来的一座桥梁。
打破了作用域链的规则 闭包就是能够读取其他函数内部变量的函数 

用途:
第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

优点:
1、使用闭包是不会污染全局环境,
2、方便进行模块化开发,
3、减少形参个数,延长了形参的生命周期,

坏处:
1、就是不恰当使用会造成内存泄漏 

闭包的不适用于返回闭包的函数是个特别大的函数,很多高级应用都要依靠闭包实现.

使用场景
1、通过循环给页面上多个dom节点绑定事件
2、封装私有变量(计数器)    
3、延续局部变量的寿命
4、高阶组件  
5、函数防抖

模块化的就是以闭包为基础构建的;

内存泄漏

1、意外的全局变量 
2、被遗忘的定时器 
3、没有清除的dom应用 ,故要及时清除,
4、滥用闭包

清除内存泄漏方法有两种,一是标记清除,二便是引用计数清除。

promise/async&await

Promise是es6新增的,异步编程的一种解决方案,用来取代回调函数和事件,比传统的解决方案——回调函数和事件——更合理和更强大。

Promise 对象用于延迟(deferred) 计算和异步(asynchronous)计算。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。Promise 对象是
知秋君
上一篇 2024-08-04 18:48
下一篇 2024-08-04 18:12

相关推荐