文章目录
- 24年面试题
- 面试题总结
- JS相关
- 数组拉平
- WebSocket
- js汇总
- Vue相关
- React相关
- React 18
- react汇总
- useState更新是否异步
- setTimout 里 setState
- typescript
- 其他问题
- 浏览器如何缓存文件
- http常见状态码
- http、https的区别
- ES6 模块与 CommonJS 模块的差异
- 如何捕获代码错误
- 设计模式和准则有哪些
- 手写发送请求
- 场景题
- 手写方法
- 源码
- 封装jequry中的AJAX的应用
- JQ源码简单理解
- jquery核心源码
- Vuex的源码
- Vuex核心源码封装
- VueRouter源码
- redux源码
- react-redux工程化
- Promise封装
24年面试题
- 数组拉平 不用 flat
- 多个数组排序成一个 不用 sort
- promise.all promise.race(手写)
- 框架写一个 倒计时组件
- vue 计算属性 和 监听 区别
- useState 是否异步
- cdn原理
- 项目中有什么难点,收获
- 实现自动刷新hooks
- 两个有序数组合成一个
- cookie(谷歌新出的cookie第三方规则)
- 本地存储
- 状态码
- 强缓存
- 两边宽100px 自适应布局
- px 转 rem
- class 组件 和 函数式组件的区别
- 多维数组拉平用reduce怎么做
- react diff 算法
- setTimout 里 setState 同步异步
- 受控组件和非受控组件
- 面试官:多看书,挖深度,找非互联网公司
- 事件循环
- setState 同步&异步
- useEffect 执行机制, 如何清除副作用
- es6数组新增的方法有哪些?
- 用框架实现todolist
- Antd Form 方法 单独一个属性变化的方法
- css 实现正方体
- svg 直线怎么写 , 定义路径标签
- react 18 更新了什么
- hooks 有哪些
- 封装了哪些业务组件,基于 antd 二次封装
- 封装了哪些 hooks
- html
- 渲染机制
- 跨域
- cookie
- css
○ 垂直居中布局
○ 固定定位
○ 左侧固定右侧自适应布局
○ flex:1 组合属性
○ 弹性盒子
○ 塌陷问题
○ flex:1 组合
○ 移动端适配 - js
- 取递增递减中最大值
- this
- 原型链
- 手写trim
- 手写promise.all race
- typeof {}
- Object.prototype.apply([])
- 事件循环机制+对应题
- 数组map和forEach的区别
- ES6
- this指向题
- 箭头函数
- vue
○ hash history 区别
○ $route $router
○ vuex 存储
○ hoc
computed 和 watch的区别
v-show 和 v-if 的区别 - ts
○ interface 和 type 区别
○ 高阶工具组合 - webpack
○ 配置优化
○ 构建工具 - react
○ hooks
○ 和class区别
○ 生命周期 - 业务
○ 项目难点
○ 性能优化
○ 手写table组件实现编辑功能
封装公共组件和公共方法
○ 用hooks写一个form tab table组件
○ 线上bug定位修改 - 小程序
面试题总结
JS相关
- Web前端面试题之原生JS
数组拉平
function flattenArray(arr) {
return arr.reduce((acc, val) => {
if (Array.isArray(arr)) {
return acc.concat(flatArray(val));
} else {
return acc.concat(val);
}
}, []);
}
const multiDimensionalArray = [1, 2, 3, [4, 5, [6]], 7];
const flatArray = flattenArray(multiDimensionalArray);
console.log("flatArray: ", flatArray);
WebSocket
WebSocket是一种网络通信协议,提供了浏览器(客户端)和服务器间的全双工通信能力。这意味着客户端和服务器可以在同一时间内互相发送数据,而无需像传统的HTTP请求那样,每次通信都需要新的请求和响应。WebSocket使得实时、双向的Web应用程序成为可能,例如在线游戏、实时交易平台、协作工具和聊天应用等。
WebSocket的工作原理:
握手:
首先,客户端通过发送一个HTTP请求来初始化WebSocket连接。这个请求被称为“握手请求”。这个请求包含了一个特殊的头部:Upgrade: websocket,表示客户端希望将连接升级到WebSocket。
升级协议:
如果服务器支持WebSocket,它会响应一个101 Switching Protocols状态码,表示同意更改协议。这个HTTP响应也包含一个Upgrade: websocket头部,表明服务器也同意升级协议。
数据传输:
一旦握手成功,客户端和服务器间的HTTP连接就会升级到WebSocket连接。此时,双方可以开始通过这个持久化的连接发送数据。数据以“帧”的形式传输,可以包含文本或二进制数据。
保持连接:
WebSocket连接会保持活跃,直到客户端或服务器主动关闭连接。在连接期间,双方可以随时相互发送数据。
WebSocket的优点:
低延迟:
WebSocket提供了比HTTP更低的延迟,因为它不需要为每个消息建立新的连接。
全双工通信:
WebSocket允许客户端和服务器双向实时通信,没有“请求-响应”的限制。
减少开销:
相较于HTTP轮询,WebSocket减少了额外的HTTP头部信息,因此能更高效地利用带宽。
持久化连接:
一旦WebSocket连接建立,它会保持打开状态直到被客户端或服务器显式关闭。
WebSocket如何使用:
在客户端(浏览器中),你可以使用WebSocket对象来建立和管理WebSocket连接:
// https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
// 创建一个新的WebSocket连接
var socket = new WebSocket("ws://127.0.0.1:5500/");
// 监听WebSocket连接打开事件
socket.onopen = function (event) {
console.log("socket.readyState : ", socket.readyState);
if (socket.readyState === WebSocket.OPEN) {
/*
CONNECTING:值为0,表示正在连接;
OPEN:值为1,表示连接成功,可以通信了;
CLOSING:值为2,表示连接正在关闭;
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
*/
// 状态为OPEN,可以安全地发送数据到服务器
socket.send("Hello, Server!");
}
};
// 监听接收到服务器数据事件
socket.onmessage = function (event) {
console.log("Message from server: ", event.data);
};
// 监听WebSocket错误事件
socket.onerror = function (event) {
console.error("WebSocket error: ", event);
};
// 监听WebSocket连接关闭事件
socket.onclose = function (event) {
console.log("WebSocket connection closed: ", event);
};
socket.onopen();
/*
WebSocket实时通信,比较常采用的方式是 Ajax 轮询
HTTP 请求一般包含的头部信息比较多,其中有效的数据可能只占很小的一部分,导致带宽浪费;
服务器被动接收浏览器的请求然后响应,数据没有更新时仍然要接收并处理请求,导致服务器 CPU 占用;
WebSocket 的出现可以对应解决上述问题:
WebSocket 的头部信息少,通常只有 2Bytes 左右,能节省带宽;
WebSocket 支持服务端主动推送消息,更好地支持实时通信;
WebSocket 的特点
建立在 TCP 协议之上;
与 HTTP 协议有着良好的兼容性:默认端口也是 80(ws) 和 443(wss,运行在 TLS 之上),并且握手阶段采用 HTTP 协议;
较少的控制开销:连接创建后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小,而 HTTP 协议每次通信都需要携带完整的头部;
可以发送文本,也可以发送二进制数据;
没有同源限制,客户端可以与任意服务器通信;
协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL;
支持扩展:ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议(比如支持自定义压缩算法等);
js汇总
● js基础数据类型
boolean、string、number、null、undefined、bigint、Symbol
● 数据类型判断的方法
typeof instanceof Object.prototype.toString.call
● typeof instanceof Object.prototype.toString.call的区别
typeof只能检测基本数据类型 引用数据类型都会返回object null也是返回object
instanceof检测当前实例是否是属于某个类的实例,不能够检测字面量方式创建的基本数据类型,只能检测引用数据类型的值
Object.prototype.toString.call会默认返回当前方法中的this对应的结果,成字符串数组形式返回’[object 对应的类型]’
● let 和 var 的区别、
let不能重复声明,var可以重复声明
let有块级作用域,var没有
let没有变量提升,var有
var会给全局作用域定义一个变量,给window增加属性,let声明的变量不会
● 什么是作用域
当打开页面,浏览器就会形成全局作用域为代码执行提供环境,而这个环境就是全局作用域
而函数执行时,会生成一个私有作用域,为代码执行提供环境
● 什么是作用域链
在私有作用域中,函数执行,如果要使用一个变量,自己作用域要是有,就使用自己的,要是没有,就向上一级作用域查找,上一级还没有,在向上一级查找,直到找到全局作用域,如果还没有就报错—>这种一级一级向上查找的机制就是【作用域链查找机制】
● Object.toString.call的底层运行机制
Object.toString.call是JavaScript中用于获取对象的类型的方法。它的底层运行机制如下:
- 首先,Object.toString方法是Object原型上的一个方法,它返回一个表示对象的字符串。
- 当我们调用Object.toString时,如果该方法没有被重写,它会返回一个默认的字符串,格式为[object 类型],其中类型是对象的实际类型。
- 为了获取对象的类型,我们可以使用call方法来改变Object.toString方法的执行上下文,使其在目标对象上执行。这样,Object.toString.call(对象)将返回一个表示该对象类型的字符串。
例如,如果我们调用Object.toString.call([]),它将返回"[object Array]“,表示该对象是一个数组类型。
需要注意的是,Object.toString.call方法只能正确地返回内置对象的类型,对于自定义对象,它将返回”[object Object]",无法区分具体的自定义类型。如果需要判断自定义对象的类型,可以使用其他方法,如instanceof运算符或typeof运算符。
● 数组的方法有哪些
pop(删除的一项)、push(新length)、shift(删除的一项)、unshift(新length)、sort、forEach、map、splice、split、reverse、concat、filter
some、every、reduce、from、fill、flat
● forEach和map的区别
forEach没有返回值,只是对数组中的每一个元素进行操作
map会返回一个新数组,这个数组的元素是原数组中每个元素执行回调函数后的一个返回值
● some、every的区别
some有一项与判定符合就返回true
every每项与判定符合就返回true
● reduce的使用
reduce遍历使用
参数1 上一次回调的值或者默认值 参数2 当前项 参数3 索引 参数4 原数组
● es6/7语法有哪些
扩展运算符…
箭头函数
数组新增方法
对象新增方法
new map new set
proxy
promise
async函数
generator函数
● 箭头函数的区别
箭头函数没有this
不能作为generator函数
不能作为构造函数
● 遍历对象的方法
for in object.keys object.values
● 对象常用的方法
object.is() object.assign() object.keys() object.values() Object.entries() [将对象转数组] Object.fromEntries() [将数组转对象]
object.create()
● 什么是原型链
每个JavaScript对象都有一个原型对象,对象可以从其原型对象继承属性和方法。当访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端(即Object.prototype)。
在JavaScript中,每个对象都有一个__proto__属性,指向其原型对象。通过原型链,可以实现对象之间的属性和方法的共享,当我们创建一个对象时,JavaScript会自动为该对象分配一个原型对象,这个原型对象又会有自己的原型对象,依次形成原型链。
● 原型链的查找机制
在对象里查找一个属性,先看自己私有的有没有,如果自己没有,就通过 proto 属性找 到当前所属类的原型上,如果原型上有,就直接用,如果没有,通过原型的 proto 继续往Object类的原型上找,如果还没有,就是undefined, 这种一级一级向上查找就会形成原型链
● new的运行原理及手写new方法
● 什么是闭包,为什么会出现闭包
闭包是指在函数内部创建的函数,它可以访问并持有其外部函数的变量、参数和内部函数,即使外部函数已经执行完毕,闭包仍然可以访问这些变量。
闭包出现的原因是由于JavaScript的词法作用域和函数的特性。当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量,这个内部函数就形成了闭包。闭包会捕获并保存外部函数的作用域链,使得外部函数的变量在内部函数执行时仍然可用。
闭包的出现有以下几个常见的原因:
- 保护变量:闭包可以将变量私有化,避免全局污染,只有通过闭包内部的函数才能访问和修改这些变量。
- 记忆状态:闭包可以记住函数执行时的上下文环境,使得函数在后续调用时能够保持之前的状态。
- 实现模块化:通过闭包可以创建私有的命名空间,将相关的函数和变量封装在一起,避免命名冲突。
需要注意的是,闭包会引用外部函数的变量,导致这些变量无法被垃圾回收,可能会造成内存泄漏。因此,在使用闭包时需要注意及时释放不再需要的资源,避免造成不必要的内存占用。
● 装饰器了解过吗
● 实现一个promise.half方法(字节)
● 跨域的方法有哪些,cors的深入了解过吗(字节)
Vue相关
- Web前端面试题之Vue相关
React相关
- Web前端面试题之React相关
React 18
● React 18 版本引入了许多新特性,但核心的 Hooks API 并没有显著改变。
○ React 18 引入的主要变化集中在并发特性上,如并发渲染(Concurrent Rendering),以及与之相关的新API如 useTransition 和 useDeferredValue。新的并发特性可以帮助开发者控制渲染的优先级,提高大型应用的性能和用户体验。例如:
useTransition: 用于标记更新的优先级较低,允许React可以在保持响应性的同时,延迟处理这些更新。
useDeferredValue: 用于延迟某个值的更新,与 useTransition 类似,它可以让React延迟渲染较新的值,直到没有更高优先级的更新。
这些新的并发特性是可选的,并不会改变现有代码的行为。开发者可以逐步在他们的应用中采用新特性。总的来说,React 18之前的Hooks和18之后的Hooks在核心API上是相同的,但18带来了更多的并发特性和相关的Hooks。
React 会在高优先级更新渲染完成之后,才会启动低优先级更新渲染,并且低优先级渲染随时可被其它高优先级更新中断。
当然,在低优先状态等待更新过程中,如果能有一个 Loading 状态,那就更好了。React 18 提供了 useTransition来跟踪 transition 状态。
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 会 render 两次,每次 state 变化更新一次
}, 1000);
//18只会渲染一次,想渲染两次
import { flushSync } from ‘react-dom’; 强制同步执行
Suspense 在 React 18 中,我们通过 Suspense包裹,可以告诉 React,我们不需要等这个组件,可以先返回其它内容,等这个组件准备好之后,单独返回。
react汇总
● useEffect是哪几个class的生命周期
useEffect可以看作是类组件中componentDidMount、componentDidUpdate和componentWillUnmount三个生命周期函数的组合。
具体来说,useEffect可以替代以下三个生命周期函数:
- componentDidMount:在组件挂载后执行副作用操作,相当于useEffect的第一个参数函数。
- componentDidUpdate:在组件更新后执行副作用操作,相当于useEffect的第一个参数函数,并且可以通过第二个参数数组来控制更新的条件。
- componentWillUnmount:在组件卸载前执行清理操作,相当于useEffect的第一个参数函数返回的清理函数。
需要注意的是,useEffect的执行时机和类组件中的生命周期函数略有不同,它是在组件渲染后异步执行的,而不是同步执行的。这意味着在执行副作用操作时,组件可能已经更新了多次,因此需要通过依赖数组来控制更新的条件,避免出现不必要的副作用。
● useEffect和useLayoutEffect的区别
useEffect 是异步执行的,useEffect 的执行时机是浏览器完成渲染之后
useLayoutEffect是同步执行的。useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。
● 为什么useEffect是异步的,在class里对应的生命周期是同步的
执行时机和调用方式不同。
在类组件中,生命周期函数是在组件的不同阶段被调用的,例如componentDidMount在组件挂载完成后被调用,componentDidUpdate在组件更新后被调用,componentWillUnmount在组件卸载前被调用。这些生命周期函数是由React框架在合适的时机直接调用的,是同步执行的。
而useEffect是React函数组件中的一个Hook,它是在组件渲染完成后异步执行的。这是因为React函数组件的渲染过程是异步的,React会将函数组件的执行放入任务队列中,等待当前任务完成后再执行下一个任务,这样可以提高性能和响应速度。因此,useEffect中的副作用操作会在组件渲染完成后被调度执行,而不是立即执行。
由于useEffect是异步执行的,所以在执行副作用操作时,组件可能已经更新了多次。为了控制副作用的执行时机,可以通过依赖数组来指定更新的条件,只有当依赖发生变化时,才会重新执行副作用操作。这样可以避免不必要的副作用和性能问题。
● 类组件和函数组件的区别
类组件是面向对象编程,可继承,存在生命周期,存在this的指向问题
函数组件是函数式编程,不可继承,没有生命周期
性能渲染
类组件是通过shouldcomponentUpdate来阻断渲染
函数组件通过memo来进行优化
● setState是同步还是异步的
在生命周期和合成事件中是异步
在addeventlistener、setTimeout等原生事件是同步
● react组件通信
父子通过props传参
子父通过callback函数
redux状态管理
store存储数据,reducer处理逻辑,dispatch派发action
● useEffect和useLayoutEffect区别
都是处理副作用,useEffect是异步处理,useLayoutEffect是同步处理
● 实现一个useState
● useState和useRef声明的变量有什么不同
● 实现useHover hook
useState更新是否异步
useState 的更新不是异步的,而是由于 React 的批处理和更新调度机制导致的
setTimout 里 setState
setTimeout(() => {
// 这里的 setState 会立即触发一个组件的更新,不会被批处理
this.setState({ count: this.state.count + 1 });
// 这里的状态更新会立即反映,因为它不会被批处理
console.log(this.state.count); // 输出: 1
}, 0);
typescript
● interface和type的区别
interface只能定义接口类型,type可以定义任何类型
type会合并重复声明的类型,interface不能
● ts的好处和坏处
好处:
静态类型检查,可以更方便的找出出错的问题
坏处:
增加学习成本和编译时间,不适合所有项目
● type of 和 key of 的区别
● 实现题
type info = {age, name, id}
ts实现声明一个类型 只能是info的类型定义 例子 type childrenInfo = { age|name|id}
好处:
- 静态类型检查: TypeScript 提供了静态类型检查,可以在编译阶段捕获一些常见的错误,提高代码质量和可靠性。
- 更好的代码提示和自动补全: TypeScript的类型系统可以提供更好的代码提示和自动补全功能,减少开发过程中的错误。
- 更好的可读性和可维护性: 强类型系统和明确的类型注解可以使代码更易于阅读和理解,有助于团队协作和代码维护。
- 更好的重构支持: TypeScript的类型系统可以帮助开发人员更轻松地进行代码重构,减少因重构而引入的错误。
- 更好的工具支持: TypeScript有丰富的工具支持,如编辑器插件、调试器等,可以提高开发效率。
坏处:
- 学习曲线: 对于习惯了动态类型语言的开发人员来说,学习 TypeScript 的静态类型系统可能需要一定的时间。
- 增加开发成本: 引入 TypeScript 可能会增加项目的开发成本,因为需要花费额外的时间来编写类型注解。
- 编译时间: TypeScript 需要先编译成 JavaScript 才能在浏览器中运行,这可能会增加开发过程中的编译时间。
- 不适合所有项目: 对于一些小型项目或者快速原型开发,引入 TypeScript 可能会显得过于繁琐。
其他问题
-Web前端面试题之其他问题
浏览器如何缓存文件
浏览器可以通过多种方式来缓存文件,以提高网页加载速度和减少网络流量消耗。以下是浏览器缓存文件的常见方式:
- HTTP缓存:浏览器通过HTTP协议来缓存文件,可以分为强缓存和协商缓存两种方式。
○ 强缓存:浏览器在请求资源时,会先检查本地缓存是否过期,如果未过期则直接使用本地缓存,不发送请求到服务器。
○ 协商缓存:当强缓存失效时,浏览器会向服务器发送请求,服务器会根据资源的最新状态返回304 Not Modified状态码,告诉浏览器可以使用本地缓存。 - 缓存控制头:通过设置HTTP响应头来控制浏览器文件的缓存行为,常见的响应头包括:
○ Cache-Control:指定缓存策略,如max-age、no-cache、no-store等。
○ Expires:指定资源的过期时间,告诉浏览器在该时间之前可以使用本地缓存。 - ETag:服务器通过ETag(Entity Tag)来标识资源的版本号,浏览器在请求资源时会将ETag发送给服务器,服务器根据ETag判断资源是否发生变化,返回304状态码或新的资源。
- Service Worker:Service Worker是运行在浏览器背后的脚本,可以拦截网络请求并自定义缓存策略,实现更灵活的文件缓存控制。
- LocalStorage和SessionStorage:浏览器提供的本地存储机制,可以将部分数据存储在本地,减少对服务器的请求。
http常见状态码
- 2xx 成功状态码:
○ 200 OK:请求成功。
○ 201 Created:请求已经被实现,且创建了新的资源。
○ 204 No Content:服务器成功处理了请求,但不需要返回任何实体内容。 - 3xx 重定向状态码:
○ 301 Moved Permanently:请求的资源已被永久移动到新位置。
○ 302 Found:请求的资源临时从不同的URI响应请求。
○ 304 Not Modified:资源未被修改,可以使用缓存的版本。 - 4xx 客户端错误状态码:
○ 400 Bad Request:请求无效。
○ 401 Unauthorized:请求未经授权。
○ 404 Not Found:请求的资源不存在。 - 5xx 服务器错误状态码:
○ 500 Internal Server Error:服务器遇到错误,无法完成请求。
○ 502 Bad Gateway:作为网关或代理服务器的服务器尝试执行请求时,从上游服务器接收到无效的响应。
○ 503 Service Unavailable:服务器暂时无法处理请求。
○ 504 Gateway Timeout:作为网关或代理服务器的服务器在等待上游服务器的响应时超时。
○ 505 HTTP Version Not Supported:服务器不支持请求中所用的HTTP协议版本。
○ 507 Insufficient Storage:服务器无法存储完成请求所必须的内容。
http、https的区别
- HTTP:
○ HTTP是一种用于传输超文本的协议,数据传输是明文的,不加密。
○ HTTP数据传输速度快,适用于不涉及敏感信息的网站和应用。
○ HTTP在传输过程中容易被窃听和篡改,存在安全风险。 - HTTPS:
○ HTTPS通过在HTTP上加入SSL/TLS加密层来保护数据传输的安全性。
○ HTTPS传输的数据是加密的,可以防止数据被窃听和篡改。
○ HTTPS使用加密证书来验证服务器身份,确保通信双方的身份和数据的完整性。
除了安全性方面的区别,HTTP和HTTPS在使用端口上也有区别:
● HTTP默认使用端口80进行通信。
● HTTPS默认使用端口443进行通信。
● http/1和http/2的区别
- 性能:
○ HTTP/2相比HTTP/1.1具有更好的性能表现,主要体现在以下几个方面:
■ 多路复用:HTTP/2支持在单个连接上并行交换多个请求和响应,而HTTP/1.1需要多个连接来处理并行请求。
■ 头部压缩:HTTP/2使用HPACK算法对头部信息进行压缩,减少了数据传输量。
■ 服务器推送:HTTP/2支持服务器主动推送资源给客户端,减少了客户端请求的延迟。 - 安全性:
○ HTTP/2对安全性的要求更高,推荐使用HTTPS协议来保护数据传输的安全性。 - 协议:
○ HTTP/1.1是基于文本的协议,而HTTP/2是二进制协议,更加高效。 - 头部压缩:
○ HTTP/2使用HPACK算法对头部信息进行压缩,减少了数据传输量,而HTTP/1.1没有头部压缩功能。 - 服务器推送:
○ HTTP/2支持服务器推送功能,可以在客户端请求之前将相关资源推送给客户端,提高性能。
ES6 模块与 CommonJS 模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
如何捕获代码错误
同步错误使用try catch 捕获
异步使用promise.catch
window.addEventListener监听某个方法
设计模式和准则有哪些
设计模式:
● 工厂模式(Factory Pattern):用于创建对象的模式,通过工厂函数或类来创建对象实例。
● 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
● 观察者模式(Observer Pattern):定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。
● 发布-订阅模式(Publish-Subscribe Pattern):类似观察者模式,但使用一个调度中心来管理订阅和发布事件。
● 策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,并使它们可以互相替换。
● 装饰者模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,就像给一个人穿不同的外套一样。
准则:
● DRY原则(Don’t Repeat Yourself):避免重复代码,尽量将重复的逻辑抽象成函数或模块。
● 单一职责原则(Single Responsibility Principle):一个类应该只有一个引起变化的原因。
● 开放-封闭原则(Open-Closed Principle):软件实体应该对扩展开放,对修改封闭。
● Liskov替换原则(Liskov Substitution Principle):子类应该能够替换其父类而不影响程序的正确性。
● 接口隔离原则(Interface Segregation Principle):多个特定接口要好于一个通用接口。
● 依赖倒置原则(Dependency Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
手写发送请求
let xhr = new XMLHttpRequest;
xhr.open(“get”, url, false);
xhr.onreadystatechange=function(){
if(xhr.readyState===4&&/^2\d{2}/.test(xhr.status)){
// 说明数据已经传输到了客户端
}
}
xhr.send();
场景题
有一组数据如下 根据相同的group分组 实现一个 tab切换页面 对应的tab展示对应的分组,点击对应的tab展示对应的数据
[{id,name,group},{id,name,group},{id,name,group}…]
tab1 tab2 tab3
group1 group2 group
增加一个筛选项,根据筛选项展示,如果group没有对应的值,tab隐藏,只展示有数据的tab
input 条件
tab2 tab3
group2 group3
如何处理数据,如何能最简便的方式处理
手写方法
● 手写冒泡排序,插入排序
● 防抖
function debounce(fn, delay) {
let timer;
return function(…args) {
let context = this;
timer ? clearTimeout(timer): null;
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
● 深克隆
function deepClone(source) {
if (source instanceof Object === false) return source;
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === ‘object’) {
target[i] = deepClone(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}
● 手写延时器
function delay(time) {
return new Promise((res) => {
setTimeout(() => {
res()
}, time)
})
}
● url截取参数转换为对象
let strUrl = ‘www.baidu.com?a=1&b=2&c=3&d=4’;
function getUrlParams(url) {
let obj = {};
let paramsAry = url.substring(url.indexOf(‘?’) + 1).split(‘&’)
paramsAry.forEach(item => {
let itm = item.split(‘=’)
obj[itm[0]] = itm[1]
});
return obj;
}
getUrlParams(strUrl)
● 数组扁平化
const flat1 = (array) => {
return array.reduce((result, it) => {
return result.concat(Array.isArray(it) ? flat1(it) : it)
}, [])
}
const flat2 = (array) => {
return array.flat(Infinity)
}
● new操作符
function create(Con, …args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
● map实现
Array.prototype.mapMap = function (fn, thisArg) {
console.log(fn, thisArg);
if (typeof fn !== ‘function’) {
throw new Error(${fn} is not a function)
}
return this.reduce((pre, cur, index, ary) => {
return pre.concat(fn.call(thisArg, cur, index, ary))
}, [])
}
● forEach实现
Array.prototype.myForEach = function(callback) {
if (typeof callback !== ‘function’) {
throw new Error(${callback} is not a function)
}
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
};
// 示例用法
const arr = [1, 2, 3];
arr.myForEach((item, index, array) => {
console.log(第
i
n
d
e
x
个元素是
{index}个元素是
index个元素是{item},数组是${array});
});
● 找出2个数组中不重复出现的数字 输入 array1 = [1,2,3,4,5] array2 = [2,3,4,6], 输出 [1,5,6]
● 数组对象去重
function uniquedObj(params) {
let map = new Map();
let res = []
params.forEach(item => {
if (!map.has(item.id)) {
res.push(item)
map.set(item.id, item.id)
}
})
return res;
}
let a1 = [
{ id: 1, name: ‘12’ },
{ id: 12, name: ‘122’ },
{ id: 13, name: ‘133’ },
{ id: 1, name: ‘12’ },
{ id: 12, name: ‘122’ },
]
console.log(uniquedObj(a1));
let arr = [2, 4, 6, 8]
console.log(arr.mapMap(item => item * 2));
● 合并2个有序链表
/**
● @param {ListNode} l1
● @param {ListNode} l2
● @return {ListNode}
*/
const mergeTwoLists = function(l1, l2) {
// 定义头结点,确保链表可以被访问到
let head = new ListNode()
// cur 这里就是咱们那根“针”
let cur = head
// “针”开始在 l1 和 l2 间穿梭了
while(l1 && l2) {
// 如果 l1 的结点值较小
if(l1.val<=l2.val) {
// 先串起 l1 的结点
cur.next = l1
// l1 指针向前一步
l1 = l1.next
} else {
// l2 较小时,串起 l2 结点
cur.next = l2
// l2 向前一步
l2 = l2.next
}
// “针”在串起一个结点后,也会往前一步
cur = cur.next
}
// 处理链表不等长的情况
cur.next = l1!==null?l1:l2
// 返回起始结点
return head.next
};
● 删除重复元素
/**
● @param {ListNode} head
● @return {ListNode}
*/
const deleteDuplicates = function(head) {
// 设定 cur 指针,初始位置为链表第一个结点
let cur = head;
// 遍历链表
while(cur != null && cur.next != null) {
// 若当前结点和它后面一个结点值相等(重复)
if(cur.val === cur.next.val) {
// 删除靠后的那个结点(去重)
cur.next = cur.next.next;
} else {
// 若不重复,继续遍历
cur = cur.next;
}
}
return head;
};
● 删除链表的倒数第 N 个结点
/**
● @param {ListNode} head
● @param {number} n
● @return {ListNode}
*/
const removeNthFromEnd = function(head, n) {
// 初始化 dummy 结点
const dummy = new ListNode()
// dummy指向头结点
dummy.next = head
// 初始化快慢指针,均指向dummy
let fast = dummy
let slow = dummy
// 快指针闷头走 n 步
while(n!==0){
fast = fast.next
n–
}
// 快慢指针一起走
while(fast.next){
fast = fast.next
slow = slow.next
}
// 慢指针删除自己的后继结点
slow.next = slow.next.next
// 返回头结点
return dummy.next
};
● 手写翻转链表
/**
● @param {ListNode} head
● @return {ListNode}
*/
const reverseList = function(head) {
// 初始化前驱结点为 null
let pre = null;
// 初始化目标结点为头结点
let cur = head;
// 只要目标结点不为 null,遍历就得继续
while (cur !== null) {
// 记录一下 next 结点
let next = cur.next;
// 反转指针
cur.next = pre;
// pre 往前走一步
pre = cur;
// cur往前走一步
cur = next;
}
// 反转结束后,pre 就会变成新链表的头结点
return pre
};
● leetcode 88 合并2个有序数组 将num2合入nums1
例: nums1 = [0], m = 0, nums2 = [1], n = 1 输出[1]
var merge = function (nums1, m, nums2, n) {
let len = m + n;
while (n > 0) {
if (m <= 0) {
nums1[–len] = nums2[–n]
continue
}
nums1[–len] = nums1[m - 1] >= nums2[n - 1] ? nums1[–m] : nums2[–n]
}
};
● leetcode 70 爬楼梯
const climbStairs = (n) => {
let prev = 1;
let cur = 1;
for (let i = 2; i < n + 1; i++) {
const temp = cur; // 暂存上一次的cur
cur = prev + cur; // 当前的cur = 上上次cur + 上一次cur
prev = temp; // prev 更新为 上一次的cur
}
return cur;
}
console.log(climbStairs(3));
源码
封装jequry中的AJAX的应用
function ajax(options){
// 准备一个默认的对象
let default_op={
type:"get",
async:true,
cache:true,
success:null,
data:null
}
// 循环options,给default中属性名重新赋值;
for(let key in options){
default_op[key]=options[key];
}
if(default_op.type.toLowerCase()==="get"){
// 为了解决传参;get请求需要将data的值拼到url的后面;
let str=`?`;
for(let key in default_op.data){
str+=`${key}=${default_op.data[key]}&`
}
str=str.slice(0,str.length-1);
default_op.url+=str;
if(!default_op.cache){
// 如果不走缓存,在后面添加时间戳;
default_op.url+= `&time=${Date.now()}`;
}
}
let xhr = new XMLHttpRequest;
// 取到default_op中的值;给open方法传入参数;
xhr.open(default_op.type,default_op.url,default_op.async);
xhr.onreadystatechange=function(){
if(xhr.readyState===4&&/^2\d{2}/.test(xhr.status)){
// 把请求回来的数据转成JSON格式的对象,传给success的回调;
let val = JSON.parse(xhr.responseText);
default_op.success(val);
}else if(xhr.readyState===4){
// 如果请求不成功,执行失败的回调;
default_op.error();
}
}
// 发送请求;
if(default_op.type==="get"){
default_op.data=null;
}
xhr.send(default_op.data);
}
ajax({
url:"data.txt",
type:"get",
data:{username:"a",password:"b"},
cache:false,
success:function(data){
console.log(data);
}
})
JQ源码简单理解
<script>
(function (global, factory) {
// global就是 window
// factory是 function (window, noGlobal) {}
if (typeof module === "object" && typeof module.exports === "object") {
// ...
// 支持CommonJs模块规范的执行这里(例如node.js)
} else {
// 代码能走到这里说明是浏览器或者webView环境
// 当外层自执行代码执行的时候,factory执行,function (window, noGlobal) {}
// window
// 也就是说function的里第一个形参被赋值的实参就是window
factory(global);
}
// typeof windiw => 'object'
}(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
// 参数信息
// window --> window
// noGlobal --> undefined
// ....
var version = "1.11.3",
jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context);
};
// jQqury还一个自定义类,他们把jQuery的原型重定向了,
// 而且还给jQ加了一个属性,属性值也是自己的原型 jQuery.fn === jQuery.prototype
jQuery.fn = jQuery.prototype = {
// 这里面是jQ的公有属性和方法
jquery: version,
// 我们自己重定向后的原型是没有construstor,所以他给手动增加了一个constructor属性指向自己的类
// 为了保证原型的完整性
constructor: jQuery,
// 转换为数组的方法
// this:一般是当前类jQuery的实例
toArray: function () {
// this:一般是当前类jQuery的实例
return slice.call(this);
},
// 把jQ对象转换为原生js对象
get: function (num) {
return num != null ?
// Return just the one element from the set
(num < 0 ? this[num + this.length] : this[num]) :
// Return all the elements in a clean array
slice.call(this);
},
each: function (callback, args) {
// this就是当前实例,
// each是jQ类的一个私有属性(把jQ当做对象来用)
// 一会去jQ里查each方法
return jQuery.each(this, callback, args);
},
eq: function (i) {
var len = this.length,
j = +i + (i < 0 ? len : 0);
return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
},
}
// 把jQuery赋值给window的$和jQuery,这样你就在全局下都可以使用了
if (typeof noGlobal === "undefined") {
window.jQuery = window.$ = jQuery;
}
}));
$()
//jQ提供的方法放到了两个位置
// 1、原型上jQuery.prototype={toArray:fn}
// $().toArray()
// 只有jQ的实例才可以调用
// 2、对象上jQuery.ajax = ...
// $.ajax()
// 直接调取使用
// 检测当前对象是数组还是类数组
// function isArraylike(obj) {
// if (type === "function" || jQuery.isWindow(obj)) {
// return false;
// }
// if (obj.nodeType === 1 && length) {
// return true;
// }
// return type === "array" || length === 0 ||
// typeof length === "number" && length > 0 && (length - 1) in obj;
// }
</script>
jquery核心源码
<script>
(function (global, factory) {
factory(global); // factory 是实参的回调函数
}(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
// 在这个作用域准备了数组和对象的常用方法;
var deletedIds = [];
var slice = deletedIds.slice;
var concat = deletedIds.concat;
var push = deletedIds.push;
var indexOf = deletedIds.indexOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var support = {};
var jQuery = function (selector, context) {
// jQuery执行时,会返回一个init的实例;
return new jQuery.fn.init(selector, context);
};
jQuery.fn = jQuery.prototype = {
// 这是jquery原型上的方法;jQuery实例能用;
jquery: version,
constructor: jQuery,
toArray: function () {
return slice.call(this);
},
}
// 是往原型上扩展方法的;
jQuery.extend = jQuery.fn.extend = function () {}
// 扩展传一个对象,那么jquery的实例以后就可以调用新扩展的方法了;
jQuery.extend({
toArray: function () {
},
slice: function () {
}
})
// 返回的init实例,就是通过传入选择器,获取到的对应的元素
init = jQuery.fn.init = function (selector, context) {
}
//
init.prototype = jQuery.fn; // 把jQuery的prototype给了init的原型
// 把jQuery这个方法给了全局下的$
window.jQuery = window.$ = jQuery;
}))();
$("#box") // jQuery 的实例,可以调用jquery原型上的方法;
// $.addClass// jQuery 的实例,可以调用jquery原型上的方法;
// console.log(module);
// $("#box").prev();
// $.ajax
// $("#box").ajax()
// jquery的私有属性和jquery这个类原型的方法;
</script>
Vuex的源码
Vue的插件必须使用Vue.use;只是vuex会默认检测到是vue的官方插件,看不到vue.use;vue.use执行时,会默认调用里面的install;
<body>
<div id="app">
{{$store.state.count}}
<child></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script src="../node_modules/vuex/dist/vuex.js"></script>
<script>
let Vuex=(function(){
class Store{
constructor(){
// this==> 返回值store实例
}
}
function install(_Vue){
// _Vue : 就是Vue这个类函数;
// mixin : 将生成store通过mixin方法注入到每一个组件上
_Vue.mixin({
beforeCreate(){// 比组件内部的beforeCreate早执行
console.log(this);// 代表每一个组件实例;
}
})
}
return {
Store,
install
}
})();
// Vue的插件必须使用Vue.use;只是vuex会默认检测到是vue的官方插件,看不到vue.use;
vue.use执行时,会默认调用里面的install;
Vue.use(Vuex);
// Vuex.Store
// Vuex.mapState
// store 是Store的一个实例;并且这个实例最后放到了vue的实例属性上;
let store = new Vuex.Store({
state:{
count:100
},
mutations:{
addCount(state){
state.count++;
}
},
actoions:{
add({commit},payload){
commit("addCount",10)
}
}
});
let child={
template:"<div>{{this.$store.state.count}}</div>"
}
// $store 会添加到每一个组件的实例上;
let vm = new Vue({
el:"#app",
store,// 会给当前实例以及当前实例的所有子孙组件都会新增一个$store属性;
// 把Store的一个实例给了这个store属性
components:{child}
});
</script>
</body>
Vuex核心源码封装
<body>
<div id="app">
{{$store.state.msg}}
{{$store.getters.str}}
<child></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
// vuex:只要改变了state,凡是依赖state的组件都会高效的更新;
// new Vue的data属性;
let Vuex=(function(){
class Store{
constructor(options){
// this==> 返回值store实例
// 初始化一个state;如果传递进来的state,会给其默认一个{}
// 为了能让数据能够监听到,当数据发生改变,依赖的视图也要更新的;
let vm = new Vue({
data:{
state:options.state||{}
}
});
//this.state=options.state||{};
// 将Vm的state的空间地址赋值给了this.state
this.state = vm.state;
// this==> $store
this.mutations={};
let mutations=options.mutations;//这个就是传递进来的那个mutations
// Object.keys : 把对象中的属性名挑出来放到一个数组中
// 就是在实例身上准备一个mutations对象,里面包含了options外界传递进来的方法,
那么方法中的this已经是指向了store这个实例;
//
Object.keys(options.mutations).forEach(key=>{
//this.mutations[key].bind(this,this.state)
this.mutations[key]=(payload)=>{
options.mutations[key].call(this,this.state,payload)
}
});
// 执行私有属性的方法时,调用原型上的方法;
let commit = this.commit;// 把原型的commit给了变量commit;
// 给当前实例新增一个commit属性,属性值是一个函数
this.commit=(type,option)=>{
commit.call(this,type,option)
}
// this.commit=function(type,option){
// options.mutations[type].call(this,option)
// }
// actions
this.actions = {};
let actions = options.actions||{};
Object.keys(actions).forEach(key=>{
this.actions[key]=(option)=>{
// 第一个this指向把函数中的this改成当前实例store
// 把store传给action的方法;
actions[key].call(this,this,option)
}
});
let dispatch = this.dispatch;
this.dispatch=(type,option)=>{
dispatch.call(this,type,option)
}
// getters
this.getters={};
let getters = options.getters||{};
// Object.keys : 将对象的属性名收集起来放到一个数组中
Object.keys(getters).forEach(key=>{
// 给getters中每一个属性新增一个get方法;
Object.defineProperty(this.getters,key,{
get:()=>{
// 会进行缓存,只有依赖的属性发生改变会执行;
return getters[key].call(this,this.state)
}
});
});
}
// 把commit 放到store的原型上
commit(type,payload){
console.log(this);// Store的实例
this.mutations[type](payload)
}
dispatch(type,option){
this.actions[type](option)
}
}
//...Vuex.mapState(['a',"b"]);
// 将store中的state放到当前的computed属性中
function mapState(ary){
let obj ={};
ary.forEach(key=>{
obj[key]=function(){
console.log(this);
// this 执行组件的实例
return this.$store.state[key]
}
})
return obj;
}
function mapGetters(ary){
let obj ={};
ary.forEach(key=>{
obj[key]=function(){
return this.$store.getters[key]
}
})
return obj;
}
function mapActions(ary){
let obj ={};
ary.forEach(key=>{
obj[key]=function(option){
return this.$store.dispatch(key,option)
}
})
return obj;
}
function mapMutations(ary){
let obj ={};
ary.forEach(key=>{
obj[key]=function(option){
return this.$store.commit(key,option)
}
})
return obj;
}
// ...Vuex.mapState(["count"])
function install(_Vue){
// _Vue和外面的Vue指向同一个空间地址
// _Vue : 就是Vue这个类函数;
// mixin : 将生成store通过mixin方法注入到每一个组件上
_Vue.mixin({
beforeCreate(){// 比组件内部的beforeCreate早执行
// 生成一个组件的实例,就会执行一次;并且在自己的beforecreate之前执行的;
//console.log(this);// 代表每一个组件实例;
// this --> Vue的实例vm
// 第二次执行 组件的实例
//this.$store=this.$options.store
//console.log(this);
// 这个会进行循环遍历,
if(this.$options&&this.$options.store){
// 如果该条件是成立的,说明是vm实例;
this.$store=this.$options.store;
}else{
// 如果不成立,说明是子孙组件
// 如果父组件存在,那么就把父组件的$store属性赋值给子组件的$store属性;
// $parent : 指的是当前组件的父组件
this.$store =this.$parent&&this.$parent.$store
}
}
})
}
return {
Store,
install,
mapState,
mapActions,
mapGetters,
mapMutations
}
})();
// Vue的插件必须使用Vue.use;只是vuex会默认检测到是vue的官方插件,看不到vue.use;vue.use
执行时,会默认调用里面的install;
Vue.use(Vuex);
// Vuex.Store
// Vuex.mapState
// store 是Store的一个实例;并且这个实例最后放到了vue的实例属性上;
let store = new Vuex.Store({
state:{
count:100,
msg:"李明帅"
},
mutations:{
add(state,val){
// this==> store
console.log(state);
state.count+=val;
}
},
actions:{
addNum({commit},val){
commit("add",val);
}
},
getters:{
str(state){
return state.count%2===0?"偶数":"奇数";
}
}
});
let child={
created(){
// 组件在使用时,才会触发其钩子函数
},
methods:{
fn(){
// this.$store===store==Store的实例
this.$store.commit("add",100);
// this.$store.dispatch("addNum",1)
}
},
computed:{
str(){
},
...Vuex.mapState(['count'])
},
template:"<div>{{$store.state.count}}{{count}}<button @click='fn'>增加
</button></div>"
}
// $store 会添加到每一个组件的实例上;
let vm = new Vue({
el:"#app",
store,// 会给当前实例以及当前实例的所有子孙组件都会新增一个$store属性;
//a:100,
beforeCreate(){
//console.log(this);
// debugger
},
// 把Store的一个实例给了这个store属性
// 组件在注册时,不会调用生命周期钩子函数,
components:{child}
});
//console.log(vm);// 目前vm身上没有$store
// $options:代表 :new的对象,会把对象中的键值对添加到当前实例的$options上
// 1.先准备vuex对象【Store,install】;
// 2. Vue.use执行调用了里面install,install执行调用了Vuex.mixin,对Vue这个类进行了修改
// 3.生成了一个store
// 4.new Vue;把store放到了实例的$options
// 5.随后vm的生命周期,执行了mixin中的beforeCreate=>把options的store直接赋值给了实例的
$store属性;
</script>
</body>
VueRouter源码
class VueRouter{
constructor(options){
const {routes}=options;
// 监听当前页面的hash值的切换
// 当第一次解析页面时,会有一个默认的hash值
/// 循环遍历routes,把path和component重新放入一个新的对象中
// {"/home/:id":home}
this.routeMap = routes.reduce((prev,next)=>{
prev[next.path]=next.component;
return prev;
},{});
//
// this ==> VueRouter的实例,也是每一个组件上的_router
Vue.util.defineReactive(this.route={},'path',"/");
window.addEventListener("load",()=>{
// 如果没有hash值,那么给其赋默认值/;如果本来就有hash,什么也不做;
location.hash?null:location.hash="/";
})
window.addEventListener("hashchange",()=>{
// 当页面hash值发生改变以后,会触发这个方法;1.a标签 2.手动
// 获取当当前页面的hash值,获取到#后面的字符串;
let path = location.hash.slice(1);
this.route.path = path;
})
}
}
//在Vuex注入了$store,在路由注入_router
VueRouter.install=function(_Vue){
_Vue.mixin({
// 给每一个组件新增一个_router的属性,这个属性的属性值是VueRouter的实例
beforeCreate(){
// this==> 每一个组件实例
if(this.$options&&this.$options.router){
// 给每一个组件实例新增_router属性,属性值就是VueRouter的实例;
这是给Vm这个Vue实例新增
this._router=this.$options.router;
}else{
// 给vm的组件的实例新增
this._router=this.$parent && this.$parent._router;
}
// 给每一个实例添加$route属性,
Object.defineProperty(this,"$route",{
value:{
route:this._router.route// ? 这个route
}
});
// 注册两个内置组件
// router-link router-view
// 注册全局组件
<router-link to="/home"></router-link>
let child = {}
Vue.component("router-link",{
props:{
to:String
},
// template:"<div></div>",
render(createElement){// h是一个createdElement,这个方法可以直接接受一个组件;
createElement 用来创建虚拟的DOM
//return createElement("a",{},首页)
// render : 渲染函数
// render: 将虚拟DOM可以转成真实的DOM;这个函数返回什么,
那么最终router-link就渲染成什么
// this==> 当前的组件实例
// return + 组件;可以把组件渲染成一个真实的DOM;
// return h(child);
// return <child></child>
// $slots
return <a href={`#${this.to}`}>this.$slots.default</a>
}
});
// router-view : 根据页面的hash值显示对应的组件
Vue.component("router-view",{
render(createdElement){
// 这个传递一个动态的组件名字
return createElement(this._router.routeMap[this._router.route.path])
}
})
}
})
};
let router=new VueRouter({
routes:[]
})
let vm = new Vue({
router,
render(){
}
})
export default VueRouter;
redux源码
function createStore(reducer) {
let state;
let getState = () => JSON.parse(JSON.stringify(state));
// action : type 要改的数据
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(item => {
if (typeof item === "function") {
item();
}
})
}
let listeners = [];// 存储订阅的事件的一个容器;当调用dispatch的时候,让这个事件池中的方法执行;
dispatch({});// 为了初始化数据
let subcribe = (fn) => {
listeners.push(fn);
return () => {
listeners = listeners.filter(item => item !== fn);
}
}
return {
getState,
dispatch,
subcribe
}
}
export default createStore;
补充
<script>
//vue->vueX
//react->
//事件池 所有公共状态
//修改状态
//reducer 管理员 登记在案 (原始状态state={},dispatch对象) return state
把原始容器中的状态修改为啥;
//getState()获得信息
//store,dispatch type
//store.subscribe 发布订阅
//redux 的漏洞
//setState的同步异步?
//index.js 工程化
//action-types.js 派发行为标识ACTION,TYPE的宏管理 变量
//reducers index.js 把各个小reducers版块合一起
//
//redux源码
function createStore(reducer) {
if (typeof reducer !== "function") {
throw new TypeError("reducer must be a function")
}
let state;
let listeners = [];
const getState = function getState() {
//防止返回状态和原始共用地址
return JSON.parse(JSON.stringify(state));
}
//向容器事件池中加方法
const subscribe = function getState(func) {
if (typeof func !== "function") {
if (listeners.includes(func)) {
listeners.push(func);
}
}
return function unsubscribe() {
listeners = listeners.filter(item => item !== func);
}
}
//派发任务
const dispatch = function getState(action) {
if (action === null || action === undefined) return;
if (typeof action !== "object") return;
if (!action.hasOwnProperty("type")) return;
//执行
state = reducer(state, action);
//
listeners.forEach(item => {
item();
})
}
//初始的时候先开发一次dispatch
// const randomString=function(){
// return Math.random().toString(36).substring(7).split("").join(".")
// }
dispatch({
type: "@@redux/INIT$(Math.random())"
})
return {
getState,
subscribe,
dispatch
}
}
export {
createStore
};
</script>
react-redux工程化
import React from "react";
// import store from "../store/index.js";
import actions from "../store/actions/counter";
// react-redux: 将store的公共数据放到组件的属性上;
// 属性和状态的更新都能引发视图的更新;
// react-redux要求返回一个连接后的组件;
import {connect} from "react-redux";
// 可以将store中的数据和action的动作以属性的方式传递给该组件
class Counter extends React.Component{
// constructor(){
// super();
// this.state={num:store.getState().counter.num}
// }
// componentDidMount(){
// // 在redux中,A组件通过dispatch更新了store中的数据,同时B组件也使用了store中这个数据,
但是B组件不会自动更新;
// store.subscribe(()=>{
// this.setState({num:store.getState().counter.num})
// })
// }
add=()=>{
// store.dispatch(actions.add());
//当使用方法时,保持和action中的add一致;
this.props.add();
}
min=()=>{
// store.dispatch(actions.min());
this.props.min();
}
render(){
// 1. 组件事件 ==> 2. action-type==>3.actions==> 4.reducer==>5.组件
//想更新视图,需要调用render方法,那么执行setState就会调用render;
// 每当dispatch时,都要更新视图的;那么把setState方法进行订阅;
return <div>
<button onClick={this.add}>+</button>
{this.props.num}
<button onClick={this.min}>-</button>
</div>
}
}
//源码
// connect : 第一个参数: 函数 第二个参数: 是当前组件
// actions : 是一个返回类型的对象;
// mapStateToProps\mapDisPatchToProps都是在connect函数内部调用的
let mapStateToProps=state=>({...state.counter}); // 当执行connect时,会默认调用这个箭头函数,
并且将store中的state数据传给当前的函数state参数;返回当前组件对应的数据,并且放在了当前组件的行间属性上;
let mapDisPatchToProps=(dispatch)=>{// dispatch==>store.dispatch
return {// 这个对象会放在组件的属性上;
add:()=>{dispatch(actions.add())},
min:()=>{dispatch(actions.min())}
}
}
// 都是将这两个函数的返回值放到组件的属性上;
export default connect(mapStateToProps,actions)(Counter);
//在执行connect时,判断第二个参数是否是一个函数,如果是函数,则将函数的执行结果放在组件的行间属性上,
如果是一个对象,那么会默认调用一个bindActionsCreator这个方法,将该方法的返回值放在组件行间属性上
当前属性传给组件
// action 就是connect传入的action dispatch是store中的dispatch方法
// let bindActionCreator=(action,dispatch)=>{
// let obj ={};
// for(let key in action){
// obj[key]= ()=>{
// dispatch(action[key]())
// }
// }
// return obj;
// }
Promise封装
<script>
// 重写Promise;写一个类似于Promise这个类的方法
class MyPromise {
constructor(excutor) { // 当new MyPromise,constructor执行的;
// this --> Promise的实例;
// pending fulfilled rejected
this.state = "pending";
// 用来存储成功的回调函数和失败的回调函数的;
this.fulfilledEvent = [];
this.rejectedEvent = [];
// 1.resolve,改变了状态
let resolve = (result) => {
// 执行resolve,那么实例的状态变成了成功态;this-->Promise实例
// 如果不是pending状态,就不再向下执行;
if (this.state !== "pending") return;
this.state = "fulfilled";
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.fulfilledEvent.forEach(item => {
if (typeof item == "function") {
item(result);
}
})
})
};
let reject = (result) => {
if (this.state !== "pending") return;
this.state = "rejected";
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.rejectedEvent.forEach(item => {
if (typeof item == "function") {
item(result);
}
})
})
}
try {
excutor(resolve, reject);
} catch (e) {
// e: 错误信息
reject(e);
}
}
// 是订阅;是在往成功的事件池和失败的事件池放入成功的回调函数和失败的回调函数
then(resolveFn, rejectedFn) {
// 当then不传回调时,给两个形参赋默认值
if (resolveFn === undefined) {
resolveFn = () => {}
}
if (rejectedFn === undefined) {
rejectedFn = () => {}
}
// this
return new MyPromise((resolve, reject) => { // then 返回的这个实例是p2;
// 往事件池中放入方法;
// resolve :这个是函数,函数中的this--> p2;
// this--> then返回的promise实例
// this.fulfilledEvent.push(resolveFn);
this.fulfilledEvent.push((result) => {
try {
let x = resolveFn(result);
// resolve : 让成功的事件池中的方法运行,里面执行时,
this-->P2的事件池中的方法执行
x instanceof MyPromise ? x.then(resolve, reject) : resolve();
} catch (e) {
reject(e);
}
});
this.rejectedEvent.push((result) => {
try {
let x = rejectedFn(result);
// resolve : 让成功的事件池中的方法运行,里面执行时,
this-->P2的事件池中的方法执行
x instanceof MyPromise ? x.then(resolve, reject) : resolve();
} catch (e) {
reject(e);
}
});
})
}
}
let p1 = new MyPromise(function (resolve, reject) {
// resolve(100)
// resolve(200);
// reject();
// console.log(a);
// console.log(resolve);
resolve();
})
p1.then(function (a) {
// fn1
// 成功的回调函数
console.log(a);
return new Promise(function (resolve, reject) { //:x===p3
resolve
(); // 这个resolve 执行,能让p3的事件池中的方法执行,
p3的事件池中有个resolve;所以就会这个p3事件中的resolve执行,p3中的resolve执行,
这个this指向p2,那么就能让p2事件池中的方法运行;
})
}, function () {
// fn2
// 失败的回调
console.log(88);
}).then(function () {
// fn3
// 如果上一个then中的回调返回一个promise实例,那么这个函数会被resolve包裹一下,
再放进这个实例的事件池中
}, function () {
})
//console.log(p1);
// p1 p2 p3
// 第一个then:把方法放到p1的事件池中;
// 第二个then:把方法放到p2的事件池中;
//
let p = new Promise(function (resolve, reject) { // resolve : 形参
// 如果代码异常,会走失败;不会在控制台抛出异常
//console.log(a);
resolve();
})
let c = p.then(function () {
//
}).then(function () {
// 上一个中的回调如果不返回promise实例,
那么这个then受上一个then默认返回的promise的状态影响,如果回调中返回了promise实例,
那么这个then就受返回的promise实例的影响
}, function () {
});
console.log(c);
</script>