1.闭包的理解,优点缺点,应用场景
闭包的理解:闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量
闭包的优点缺点:
优点:
1.防止全局变量的污染
2.内部函数可以访问外部函数的变量
3.可以定义私有属性和私有方法
4.变量长期驻扎在内存中
缺点:
1.占用内存,使用不当会导致内存泄漏或溢出
2.外部函数执行完毕之后,其作用域会被销毁,但是变量不会进行销毁,因为内部函数的作用域仍然在利用它。直到内部函数执行完毕之后才会全部(除全局作用域)被销毁。
应用场景:
1.单例模式
2.延长变量的声明周期
3.使用闭包模拟私有变量、方法、实现函数柯里化、模块化
4.setTimeOut传参
5.回调
6.防抖节流
2.事件循环的理解?以及应用理解?
javascript是单线程:如果是多线程的话会造成许多复杂的问题。
假设:有A、B两个线程,同时对一个节点进行操作:A删除节点,B添加节点内容,如果再增加几个线程,情况会变得更加复杂,无论我们对那个线程进行操作,将会对后面带来一系列的问题。
单线程的含义是js只能在一个线程上运行,同时只能执行一个JS任务,其他任务则排队等到执行(服从先到先得)
同步任务和异步任务:
同步任务:js的同步任务是在主线程排队支持的任务,前一个执行结束,再执行下一个。到一个后一个这个顺序会形成执行栈
异步任务:会被主线程挂起,不会进入主线程,而是进入消息队列,必须指定callback(回调 )函数。只有消息队列通知主线程,并且同步任务形成的执行栈为空,该消息队列对应的任务才会进入执行栈获得执行的机会
异步任务分为宏任务和微任务,会先执行微任务,再执行宏任务。
微任务:在当前主线程任务执行结束之后就会立即执行,会在当前宏任务之前运行,而不是到整个任务的后面。他是有js引擎自身提供的,微任务在执行的时候,能获取到任务外的上下文。微任务包含 promise、async
宏任务:消息队列(回调队列)中的任务称为宏任务,是由宿主环境(浏览器或者node)提供的,不断从消息队列中取出并被事件循环执行,宏任务在执行的时候,他不能获取到任务外的上下文。宏任务包含:setTimeout/setinterval,ui、i/o
事件循环的理解:事件循环是一种用于处理事件和任务的机制,常常用于异步编程。它通过循环遍历事件队列(Event Queue,机制为先进先出)。主线程内的任务执行完毕为空,会去任务队列读取对任务队列读取对应的任务,推入主线程执行。
事件循环的应用非常广泛,特别是在前端开发的过程中。在浏览器环境下,常见的事件包括鼠标点击、键盘输入、网络请求完成等。通过事件循环、可以注册相应的事件处理函数、并在事件发生时进行处理,从而实现用户交互相应、异步数据加载等功能。
3.Js类型校验的方式?
1.typeof操作符:使用typeof操作符可以检查一个值的类型
2.instanceof操作符:可以检查一个对象是否属于某一个特定的类
3.Object.prototype.toString方法:通过调用Object.prototype.toSring方法可以获取一个对象的内部属性,从而得到具体的类型信息。
4.typeof和instnceof结合使用:在某些情况下,typeof和instnceof可以结合起来进行更精确的类型校验。
5. constructor:
指向创建该实例对象的构造函数,注意null和undefined没有constructor,以及constructor可以被更改
6.inprototypeof
判断是否在实例对象的原型链上,与instanceof相当
4.面向对象编程的方式的理解?
面向过程:简称po,从名字可以看出来它是注重过程的。当解决一个问题的时候,面向过程会吧事情拆分成:一个个函数和数据(用于方法的参数)。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完成之后,事情就搞定了。
面向对象:简称oo,看名字他是注重对象的。当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有那些对象,然后给对象附一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;例如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:性能比面向过程低
面向对象编程的特性
三大基本特性:封装、继承、多态
封装
封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承
继承,指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过 “继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用父类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
多态
多态,是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
五、五大基本原则:SPR, OCP, LSP, DIP, ISP
单一职责原则SRP:
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。
开放封闭原则OCP:
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。
里式替换原则LSP:
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。
依赖倒置原则DIP:
具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类: 而应当由B定义一个抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。
接口分离原则ISP:
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来
5.封装一个使用递归方式的深拷贝方法deepClone?
// 递归的方式实现深拷贝
// 创建一个函数,传入参数是需要深拷贝的对象
function deepclone(obj) {
// 判断参数是对象还是数组
let isArr = Array.isArray(obj);
// 根据传入的参数声明克隆的类型
let objClone = isArr ? [] : {}
// 遍历需要拷贝的对象的属性
for (const key in obj) {
// console.log(obj[key]);
// 判断属性的值是否为对象
if (typeof obj[key] === 'object') {
// 是对象递归复制
objClone[key] = deepclone(obj[key])
} else {
// 不是对象直接复制
objClone[key] = obj[key]
}
}
return objClone
}
console.log(deepclone(obj));
6.Git命令以及命令作用
7.你对自定义hook的理解,模拟简易版的useState
自定义hook是一个函数,函数内部调用了其他hook函数,且以use开头命名,从而可称为自定义hook。其主要,是让代码拥有更好的重用性,依旧是函数式思维。在自定义hook的顶层可以无条件的调用其他hook(useState,useEffect)。我们可以自由决定Hook的参数和返回值。每次使用自定义Hook时,其中的state和副作用都是完全隔离的。
假如即创建了一个useXXX的函数,但是内部并没有用任何其他Hooks,那么这个函数就不是一个Hook,而是一个普通的函数。但是如果用了其他的Hooks,那么他就是一个Hook。
自定义hook主要特点:
函数本能,多出重用
如同创建对象,只提供特定的数据与修改函数,封闭不必要的访问途径
自定义hook可继续嵌套组合,也就是组件思想
使用场景:封装可复用的逻辑、监听数据的状态、拆分复杂的逻辑
可重用逻辑直接写一个工具类不就行了吗?为什么一定要通过Hook进行封装?
因为在Hooks中,你可以管理当前组件的state,从而将更多的逻辑写可重用的Hooks中。但是要知道,在普通的工具类中是无法直接修改组件的state的,那么也就无法再数据改变的时候触发组件的重新渲染。
let isMount = true; // 申明一个全局变量,用来区分 mount 和 update
let workInProgressHook = null; // 申明一个全局变量,作为链表的指针
const fiber = {
stateNode: App, // stateNode 用来保存当前组件
memoizedState: null, // 用来保存当前组件内部的状态
};
function useState(initialState) {
let hook;
if (isMount) {
hook = {
memoizedState: initialState,
next: null,
queue: {
pending: null,
}
};
if (!fiber.memoizedState) {
fiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
let baseState = hook.memoizedState;
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending.next);
hook.queue.pending = null; // 循环结束,清空链表
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
};
function dispatchAction(queue, action) {
const update = {
action,
next: null,
};
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
schedule();
};
function schedule() {
workInProgressHook = fiber.memoizedState; // 让指针指向当前的useState保存的值
const app = fiber.stateNode(); // 执行组件的渲染函数,将结果保存在app里
isMount = false; // 首次渲染之后,isMount 变成 false
return app; // 将fiber.stateNode的结果返回
};
function App() {
const [num, setNum] = useState(0);
console.log(num);
return {
onClick() {
setNum(n => n + 1);
},
};
};
定义了一个 useState 函数,接收一个初始值作为参数。使用闭包,创建了一个局部变量 state,并将初始值赋给它.
然后,定义了 setState 函数,它接收一个新的值作为参数,并将 state 更新为新的值
最后返回一个数组,包含 state 和 setState,使其可在组件中使用
8.Redux实现原理
为什么要使用redux?
react中数据在组件中是单项绑定的。父组件向子组件传递数据可以通过props,但是兄弟组件传值就比较麻烦,redux就可以解决这一个问题。
redux的设计理念:
redux是将整个应用的state存储在一个公共的store文件中,组件可以通过分发(dispath)一个动作或者行为(action)给这个公用的store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
redux就是一个实现集中管理的容器,遵循三大原则:
单一数据源
state是只读的
使用纯函数来执行修改
工作流程图:
redux的核心原理:
1.将应用的状态统一放到state中,由store来管理state
2.reducer的作用是返回一个新的state去更新store中对用的state。
3.按照redu的原则,ui层每一次状态的改变都应通过action去触发,action传入对应的reducer中,reducer返回一个新的state更新store中存放的state,完成了一次状态的更新
4.subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dispath发起后依次执行
5.可以添加中间件对提交的dispath进行重写
扩展运算符都有哪些作用,详细介绍一下
扩展运算符是JavaScript中的一种语法,用于展开(解构)数组或对象
具体作用
展开数组:讲一个数组展开成单独的元素,可以在函数调用,数组字面量或其他需要多个参数或元素的地方。例如:
const arr = [1, 2, 3];
console.log(...arr); // 输出:1 2 3
const arr1 = [4, 5, 6];
const newArr = [...arr, ...arr1]; // 合并两个数组
console.log(newArr); // 输出:[1, 2, 3, 4, 5, 6]
函数调用时传递参数:通过将数组的元素展开作为函数的参数,简化了向函数传递数组元素的过程。例如:
function add(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
const sum = add(...numbers);
console.log(sum); // 输出:6
数组/对象的浅拷贝:使用扩展运算符可以创建原始数组/对象的浅拷贝,这意味着他们共享相同的引用。例如:
const originalArray = [1, 2, 3];
const copyArray = [...originalArray];
originalArray[0] = 10;
console.log(copyArray); // 输出:[1, 2, 3],原始数组的改变不影响拷贝数组
合并对象:用于合并对象的属性,创建一个新的对象,如果合并的对象具有相同的属性,则后面的属性将覆盖前面的属性。例如:
const obj1 = { x: 1, y: 2 };
const obj2 = { z: 3 };
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // 输出:{ x: 1, y: 2, z: 3 }
通过扩展运算符生成数组和对象字面量:可以使用扩展运算符在数组或对象字面量 中生成新的数组或对象、例如:
const existingArray = [1, 2, 3];
const newArray = [...existingArray, 4, 5, 6];
console.log(newArray); // 输出:[1, 2, 3, 4, 5, 6]
const existingObj = { x: 1, y: 2 };
const newObj = { ...existingObj, z: 3 };
console.log(newObj); // 输出:{ x: 1, y: 2, z: 3 }
总结一下,扩展运算符在JavaScript中多种作用,其中包括展开数组、传递函数参数、浅拷贝数组/对象、合并对象属性以及生成数组和对象字面量,它是一种灵活且强大的语法,可以简化代码并提高开发效率。
说说你对react的理解?有哪些特性?
React用于构建用户界面的javascript库,只提供了ui层面的解决方案
遵循组件设计模式,声明式编程范式和函数式编程概念,以使用前端应用程序更加高效。
使用虚拟dom来有效的操作dom,遵循从高阶组件到低阶组件的单项数据流
帮助我们将各个界面成立成了各个独立的小块,每一块就是一个组件,这些组件可以进行组合嵌套,构成整体页面。
React类组件使用一个名为render的方法或者函数组件中的return,接收输入的数据并且返回需要展示的内容
特性:
Jsx语法、单项数据流、component、xuniDom、声明式编程
- 说说Real DOM和Virtual DOM的区别?优缺点?
两者的区别:
虚拟dom不会频繁的进行排版与重绘,而真实的dom会频繁的进行排版与重绘。
虚拟dom的总损耗是虚拟dom增删改+真实dom差异增删改+排版与重绘。真实DOM的总损耗是Dom完全增删改+排版与重绘
真实Dom:
优点:易用
缺点:效率低、解析速度慢、内存占用量过高
性能差、频繁的操作真实dom,容易造成重绘与回流
虚拟dom:
优点:简单方便,如果使用手动dom来操作真实dom来构成整体页面的话,频繁又容易出错,在大规模应用下来也非常的不利于维护
性能强:使用虚拟dom来有效的避免真实dom频繁的进行排版与重绘,大大提高的性能
跨平台:使用虚拟dom实现了跨平台的能力,一套代码多端使用
缺点:在一些性能要求极高的应用中,虚拟dom无法进行针对性的极致优化。
首次渲染dom的时候,会慢一点,因为比平常多了一层虚拟Dom的计算。
说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
挂载阶段->更新阶段->销毁阶段
挂载阶段:
Constantor:在react挂载之前执行,会调用它的构造函数
ComponentWillmount:在调用render方法之前调用,并且在初始化挂载,以及后续更新的时候都会被调用。
ComponentDidMount:在挂载之后立即调用。
更新阶段:
ComponentWillReceiveProps:在接收父组件父组件的props发生改变的时候需要重新渲染页面
ShouldComponentUpdate:用于控制组件重新渲染的生命周期,state发生变化,组件会重新进入渲染流程。
Render:是class组件中必须实现的一种方法
ComponentDidupdate:state每次发生改变的时候,都会进入这个生命周期
销毁阶段:
ComponentWillUnmount:在此处完成组件的卸载以及数据销毁。
说说React中setState执行机制?
在react事件中,组件的状态可以通过setState进行修改。
一个组件的显示形态是需要数据状态以及外部参数所决定的,数据状态就是state,当需要修改里面的值的时候,需要调用setState才可以修改状态
SetState第一个值是一个函数或者对象,第二个值是一个回调函数,用于实时获取需要更新的数据。
SetState分为两种类型:同步更新和异步更新
异步更新:生命周期 合成事件
同步更新:setTimeout定时器 原生事件
说说react的事件机制?
React事件机制:
React的注册事件会绑定在decoment这个Dom元素上去,而不是react对应的DOM
React自身实现了一种冒泡机制,所以使用event.stopPropagation()无效的原因
React通过队列的形式,从触发的组件向父组件进行回溯,然后调用jsx中定义的callback
React有一套自己的合成事件 syntheticEvent
React组件之间如何通信?
子组件向父组件传值:父组件中组件绑定一个属性,传递给子组件,子组件通过props接收这个方法,直接调用
父组件向子组件传值:在父组件中的子组件绑定一个自定义属性,挂载传递的数据,子组件中props接收传递的数据即可。
兄弟组件传值:状态提升,需要传递的数据放到父组件中,通过父组件进行传递
Context传值:状态数传参
说说你对受控组件和非受控组件的理解?应用场景?
受控组件:
由react控制的输入表单元素而改变其值的关系叫做受控组件。
例如:给input框绑定一个onChange事件,当输入框中的数据发生改变的时候就会触发onChange事件,从而更新state
非受控组件:
非受控组件指的是,表单数据由本身的dom,处理,不受setState控制,与传统的html表单相似,input输入的值,就显示最新的值
在非受控组件中可以使用ref来获取表单中的值。
应用场景:
在实际开发中我们使用受控组件来管理表单元素的状态
说说你对fiber架构的理解?解决了什么问题?
Fider是一种基于现代技术栈的轻量级、高性能、可扩展的web框架。它采用了前后端分离的设计思路。使用nodejs、react、javascript等技术。
Fider主要解决了以下问题:
- 前后端分离:fiber采用的是前后端分离的设计思路,能够清晰的划分职责,简化开发流程,提高开发效率。
- 高性能:fider采用了异步、非阻塞i/o能够提高系统的性能以及吞吐量
- 可扩展:fiber采用模块化的设计思路,能够轻松的进行添加或者移除模块。
- 安全性:fiber采用了安全的编程实践,能够保护应用程序免收攻击和注入攻击等安全隐患。
说说react diff的原理是什么?
Diff算法是虚拟dom的一个必然的结果,他是通过新旧DOM对比,将在不更新页面的情况下局部更新。
Diff算法遵循深度优先,同层比较的原则。
Diff算法有三个等级:tree层、component层、element层
Tree层不做任何的修改,如果不一样直接删除创建
Component层从父级向子级进行查找,如果遇到不同直接删除创建
Element层有key进行比较,如果发现key值可以复用的话,就将位置进行移动,如果没有执行删除创建。
说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
Redux中间件就是介于应用系统和系统软件之间的一种软件,它具有系统软件中的所有功能,目的就是为了达成资源共享,功能共享。
常用的中间件:redux-thunk异步操作 redux-logger 日志操作
实现原理:所有的中间件都会被放到一个数组中嵌套执行,判断传递过来的类型,最后执行store.dispatch,中间件可以拿到middleware,API,可以拿到getState和dispatch方法
如何使用css实现一个三角形,写出两种以上方案得满分?
- 通过border实现:
可以使用border属性来设置四个边框,然后将其他三个边框设置成透明,剩下的一个边框设置成实线,并且设置成宽度为0,这样就可以实现一个三角形
- 通过transfrom实现:
可以使用transfrom旋转来实现,将一个正方形或者矩形旋转45度就可以得到一个三角形。
什么是强缓存和协商缓存?
强缓存:浏览器不会向服务端发送任何请求直接从本地进行读取文件,返回状态码为status code 200 ok
协商缓存:向服务器发送请求,服务器会根据请求带的request和header的一些参数来进行判断是否命中协商缓存,如果命中,则返回304状态码并且携带上request和header通知浏览器从缓存中获取资源
说说React jsx转换成真实DOM的过程?
书写jsx代码->Babel编译->编译后的jsx执行react-createRelement的调用->传入导react-element方法中生成虚拟dom->最后最终返回react.render()生成的真实dom
说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
@reduxjs/toolkit是对redux的二次封装,开箱即用的高效的一个reudx工具,使得创建store和更新store
区别:
@reduxjs/toolkit相比于react-redux比较方便,集成了redux-devtools-extension,不需要额外的配置,更加高效,方便。
@reduxjs/toolkit集成了immutable-js的功能,不需要安装配置,提高效率
@reduxjs/toolkit集成了react-thunk的功能
React render方法的原理,在什么时候会触发?
在类组件中render指的就是render方法,在函数组件中render方法指的就是整个函数组件,render函数中的jsx语法会编译成js语法,在render方法中,react中会将新调用的render方法进行新旧dom进行对比,决定如何更新dom,然后进行diff比较更新dom树。
什么时候触发:
- 在类组件调用setState的时候触发
- 函数组件通过useState hook的时候触发
- 一旦执行了 setState render就会触发
React性能优化的手段有哪些?
React性能优化的手段:
- 避免使用内敛样式
- 使用react Fragments避免额外的标记
- 懒加载组件
- 事件绑定
- 服务端渲染
- 使用immutable
- 使用useCallback和usememo hook方法
如何通过原生js实现一个节流函数和防抖函数,写出核心代码,不是简单的思路?
节流函数:
Function jieliu(){
Let timer;
Return function(){
If(!timer){
Timer=setTimeout(()=>{
Func.apply(this. arguments)
Timder==null;
},dealy)
}
}
}
防抖函数:
Function fangdou(){
Let timer;
Return function(){
If(!timer){
Timer=setTimeout(()=>{
Func.apply(this. arguments)
Timder==null;
},dealy)
}
}
}
说说webpack中代码分割如何实现?
Webpack实现代码分割:使用动态导入语法
动态导入语法:
Webpack2提供了import()函数来实现动态导入模块。这个函数会返回一个promise对象,可以用于异步加载模块。在webpack中,可以将动态导入语法实现代码分割。
- 说说如何借助webpack来优化前端性能?
- js代码压缩
- css代码压缩
- HTML文件压缩
- 图片压缩
- 文件大小压缩
- 缓存处理
说说javascript内存泄漏的几种情况?
- 意外的全局变量
- 定时器
- 闭包的使用不当
- 多出的引用
持续更新中......