2024年Web前端面试题(最全、最详细、持续更新)_web前端面试问题(4)

文末

从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。

个人将这段时间所学的知识,分为三个阶段:

第一阶段:HTML&CSS&JavaScript基础

第二阶段:移动端开发技术

第三阶段:前端常用框架

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 推荐学习方式:针对某个知识点,可以先简单过一下我的笔记,如果理解,那是最好,可以帮助快速解决问题;

  • 大厂的面试难在,针对一个基础知识点,比如JS的事件循环机制,不会上来就问概念,而是换个角度,从题目入手,看你是否真正掌握。所以对于概念的理解真的很重要。

  1. componentDidMount:在组件挂载后执行副作用操作,相当于useEffect的第一个参数函数。
  2. componentDidUpdate:在组件更新后执行副作用操作,相当于useEffect的第一个参数函数,并且可以通过第二个参数数组来控制更新的条件。
  3. componentWillUnmount:在组件卸载前执行清理操作,相当于useEffect的第一个参数函数返回的清理函数。

需要注意的是,useEffect的执行时机和类组件中的生命周期函数略有不同,它是在组件渲染后异步执行的,而不是同步执行的。这意味着在执行副作用操作时,组件可能已经更新了多次,因此需要通过依赖数组来控制更新的条件,避免出现不必要的副作用。

  1. useEffect和useLayoutEffect的区别
    useEffect 是异步执行的,useEffect 的执行时机是浏览器完成渲染之后
    useLayoutEffect是同步执行的。useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。
  2. 为什么useEffect是异步的,在class里对应的生命周期是同步的
    执行时机和调用方式不同。

在类组件中,生命周期函数是在组件的不同阶段被调用的,例如componentDidMount在组件挂载完成后被调用,componentDidUpdate在组件更新后被调用,componentWillUnmount在组件卸载前被调用。这些生命周期函数是由React框架在合适的时机直接调用的,是同步执行的。

而useEffect是React函数组件中的一个Hook,它是在组件渲染完成后异步执行的。这是因为React函数组件的渲染过程是异步的,React会将函数组件的执行放入任务队列中,等待当前任务完成后再执行下一个任务,这样可以提高性能和响应速度。因此,useEffect中的副作用操作会在组件渲染完成后被调度执行,而不是立即执行。
由于useEffect是异步执行的,所以在执行副作用操作时,组件可能已经更新了多次。为了控制副作用的执行时机,可以通过依赖数组来指定更新的条件,只有当依赖发生变化时,才会重新执行副作用操作。这样可以避免不必要的副作用和性能问题。
4. 类组件和函数组件的区别
类组件是面向对象编程,可继承,存在生命周期,存在this的指向问题
函数组件是函数式编程,不可继承,没有生命周期
性能渲染
类组件是通过shouldcomponentUpdate来阻断渲染
函数组件通过memo来进行优化
5. setState是同步还是异步的
在生命周期和合成事件中是异步
在addeventlistener、setTimeout等原生事件是同步
6. react组件通信
父子通过props传参
子父通过callback函数
redux状态管理
store存储数据,reducer处理逻辑,dispatch派发action
7. useEffect和useLayoutEffect区别
都是处理副作用,useEffect是异步处理,useLayoutEffect是同步处理
8. 实现一个useState
9. useState和useRef声明的变量有什么不同
10. 实现useHover hook

useState更新是否异步

useState 的更新不是异步的,而是由于 React 的批处理和更新调度机制导致的

setTimout 里 setState
setTimeout(() => {
      // 这里的 setState 会立即触发一个组件的更新,不会被批处理
      this.setState({ count: this.state.count + 1 });
      
      // 这里的状态更新会立即反映,因为它不会被批处理
      console.log(this.state.count); // 输出: 1
    }, 0);

typescript
  1. interface和type的区别
    interface只能定义接口类型,type可以定义任何类型
    type会合并重复声明的类型,interface不能

  2. ts的好处和坏处
    好处:

    1. 静态类型检查: TypeScript 提供了静态类型检查,可以在编译阶段捕获一些常见的错误,提高代码质量和可靠性。

    2. 更好的代码提示和自动补全: TypeScript的类型系统可以提供更好的代码提示和自动补全功能,减少开发过程中的错误。

    3. 更好的可读性和可维护性: 强类型系统和明确的类型注解可以使代码更易于阅读和理解,有助于团队协作和代码维护。

    4. 更好的重构支持: TypeScript的类型系统可以帮助开发人员更轻松地进行代码重构,减少因重构而引入的错误。

    5. 更好的工具支持: TypeScript有丰富的工具支持,如编辑器插件、调试器等,可以提高开发效率。坏处:

    6. 学习曲线: 对于习惯了动态类型语言的开发人员来说,学习 TypeScript 的静态类型系统可能需要一定的时间。

    7. 增加开发成本: 引入 TypeScript 可能会增加项目的开发成本,因为需要花费额外的时间来编写类型注解。

    8. 编译时间: TypeScript 需要先编译成 JavaScript 才能在浏览器中运行,这可能会增加开发过程中的编译时间。

    9. 不适合所有项目: 对于一些小型项目或者快速原型开发,引入 TypeScript 可能会显得过于繁琐。

  3. type of 和 key of 的区别

  4. 实现题
    type info = {age, name, id}
    ts实现声明一个类型 只能是info的类型定义 例子 type childrenInfo = { age|name|id}

其他问题

-Web前端面试题之其他问题

浏览器如何缓存文件

浏览器可以通过多种方式来缓存文件,以提高网页加载速度和减少网络流量消耗。以下是浏览器缓存文件的常见方式:

  1. HTTP缓存:浏览器通过HTTP协议来缓存文件,可以分为强缓存和协商缓存两种方式。
    ○ 强缓存:浏览器在请求资源时,会先检查本地缓存是否过期,如果未过期则直接使用本地缓存,不发送请求到服务器。
    ○ 协商缓存:当强缓存失效时,浏览器会向服务器发送请求,服务器会根据资源的最新状态返回304 Not Modified状态码,告诉浏览器可以使用本地缓存。
  2. 缓存控制头:通过设置HTTP响应头来控制浏览器文件的缓存行为,常见的响应头包括:
    ○ Cache-Control:指定缓存策略,如max-age、no-cache、no-store等。
    ○ Expires:指定资源的过期时间,告诉浏览器在该时间之前可以使用本地缓存。
  3. ETag:服务器通过ETag(Entity Tag)来标识资源的版本号,浏览器在请求资源时会将ETag发送给服务器,服务器根据ETag判断资源是否发生变化,返回304状态码或新的资源。
  4. Service Worker:Service Worker是运行在浏览器背后的脚本,可以拦截网络请求并自定义缓存策略,实现更灵活的文件缓存控制。
  5. LocalStorage和SessionStorage:浏览器提供的本地存储机制,可以将部分数据存储在本地,减少对服务器的请求。
http常见状态码
  1. 2xx 成功状态码:
    ○ 200 OK:请求成功。
    ○ 201 Created:请求已经被实现,且创建了新的资源。
    ○ 204 No Content:服务器成功处理了请求,但不需要返回任何实体内容。
  2. 3xx 重定向状态码:
    ○ 301 Moved Permanently:请求的资源已被永久移动到新位置。
    ○ 302 Found:请求的资源临时从不同的URI响应请求。
    ○ 304 Not Modified:资源未被修改,可以使用缓存的版本。
  3. 4xx 客户端错误状态码:
    ○ 400 Bad Request:请求无效。
    ○ 401 Unauthorized:请求未经授权。
    ○ 404 Not Found:请求的资源不存在。
  4. 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的区别
  1. HTTP:
    ○ HTTP是一种用于传输超文本的协议,数据传输是明文的,不加密。
    ○ HTTP数据传输速度快,适用于不涉及敏感信息的网站和应用。
    ○ HTTP在传输过程中容易被窃听和篡改,存在安全风险。
  2. HTTPS:
    ○ HTTPS通过在HTTP上加入SSL/TLS加密层来保护数据传输的安全性。
    ○ HTTPS传输的数据是加密的,可以防止数据被窃听和篡改。
    ○ HTTPS使用加密证书来验证服务器身份,确保通信双方的身份和数据的完整性。

除了安全性方面的区别,HTTP和HTTPS在使用端口上也有区别:

●  HTTP默认使用端口80进行通信。 
●  HTTPS默认使用端口443进行通信。 
●  http/1和http/2的区别 

  1. 性能:
    ○ HTTP/2相比HTTP/1.1具有更好的性能表现,主要体现在以下几个方面:
    ■ 多路复用:HTTP/2支持在单个连接上并行交换多个请求和响应,而HTTP/1.1需要多个连接来处理并行请求。
    ■ 头部压缩:HTTP/2使用HPACK算法对头部信息进行压缩,减少了数据传输量。
    ■ 服务器推送:HTTP/2支持服务器主动推送资源给客户端,减少了客户端请求的延迟。
  2. 安全性:
    ○ HTTP/2对安全性的要求更高,推荐使用HTTPS协议来保护数据传输的安全性。
  3. 协议:
    ○ HTTP/1.1是基于文本的协议,而HTTP/2是二进制协议,更加高效。
  4. 头部压缩:
    ○ HTTP/2使用HPACK算法对头部信息进行压缩,减少了数据传输量,而HTTP/1.1没有头部压缩功能。
  5. 服务器推送:
    ○ 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
如何处理数据,如何能最简便的方式处理

手写方法

手写冒泡排序,插入排序
防抖和节流
 <body>
    <input id="box" />
    <button id="box1">+++</button>
    <script>
 let box = document.querySelector("#box");
 function fn() {
 console.log(this, 2);
 }
 // 防抖
 function debounce(fn, delay) {
 delay = delay || 200;
 let timer;
 return function (...args) {
 let context = this;
 timer && clearTimeout(timer);
 timer = setTimeout(() => {
 fn.apply(context, args);
 }, delay);
 };
 }
 box.oninput = debounce(fn, 400);
 // 节流
 function throttle(fn, interval) {
 var last;
 var timer;
 interval = interval || 200;
 return function () {
 var th = this;
 var args = arguments;
 var now = +new Date();
 if (last && now - last < interval) {
 clearTimeout(timer);
 timer = setTimeout(function () {
 last = now;
 fn.apply(th, args);
 }, interval);
 } else {
 last = now;
 fn.apply(th, args);
 }
 };
 }
 function log() {
 console.log("99");
 }
 let box1 = document.querySelector("#box1");
 box1.onclick = throttle(log, 4000);
 </script>
  </body>

深克隆
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)

数组扁平化(拉平)
let ary = [1, 2, 3, [3, 4, [5, 6]], 5, [3, 4]];
function flattenArray(ary) {
  // 1
  // return ary.join(",").split(",").map(v => parseFloat(v))
  // 2
  // return ary.flat(3)
  // 3
  // return ary.flat(Infinity)
  // 4
  // return ary.reduce((pre,cur)=>{
  // return pre.concat(Array.isArray(cur)?fn(cur):cur)
  // },[])
  // 5
  // while (ary.some(Array.isArray)) {
  // ary = [].concat(...ary);
  // }
  // return ary;
  // 6
  // return JSON.parse("["+JSON.stringify(ary).replace(/(\[|\])/g, "")+"]")
  // 7
  // return JSON.stringify(ary).replace(/(\[|\])/g, "").split(",").map(v => parseFloat(v))
  //8
  let result = [];
  for (let item of arr) {
    if (Array.isArray(item)) {
      result = result.concat(flattenArray(item));
    } else {
      result.push(item);
    }
  }
  return result;
}
console.log(flattenArray(ary));

 // function flattenArray(arr, result = []) {
 // for (let i = 0; i < arr.length; i++) {
 // const item = arr[i];
 // if (Array.isArray(item)) {
 // flattenArray(item, result); // 递归处理子数组
 // } else {
 // result[result.length] = item; // 手动添加非数组元素到结果数组
 // }
 // }
 // return result;
 // }

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(第${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));

合并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));

手写题

对象结构转树结构
const arr = [
  { id: 12, parentId: 1, name: "朝阳区" },
  { id: 241, parentId: 24, name: "田林街道" },
  { id: 31, parentId: 3, name: "广州市" },
  { id: 13, parentId: 1, name: "昌平区" },
  { id: 2421, parentId: 242, name: "上海科技绿洲" },
  { id: 21, parentId: 2, name: "静安区" },
  { id: 242, parentId: 24, name: "漕河泾街道" },
  { id: 22, parentId: 2, name: "黄浦区" },
  { id: 11, parentId: 1, name: "顺义区" },
  { id: 2, parentId: 0, name: "上海市" },
  { id: 24, parentId: 2, name: "徐汇区" },
  { id: 1, parentId: 0, name: "北京市" },
  { id: 2422, parentId: 242, name: "漕河泾开发区" },
  { id: 32, parentId: 3, name: "深圳市" },
  { id: 33, parentId: 3, name: "东莞市" },
  { id: 3, parentId: 0, name: "广东省" },
];
function getTree(arr) {
  const newArr = arr.sort((a, b) => b.parentId - a.parentId);
  for (let i = 0; i < newArr.length; i++) {
    let item = newArr[i];
    if (item.parentId) {
      newArr.forEach((arrItem) => {
        if (arrItem.id === item.parentId) {
          if (arrItem.children) {
            arrItem.children.push(item);
          } else {
            arrItem.children = [item];
          }
        }
      });
    }
  }
  return newArr.filter((item) => !item.parentId).sort((a, b) => a.id - b.id);
}
const tree = getTree(arr);
console.log("tree: ", JSON.stringify(tree, null, 2));

树结构转对象结构
const obj = {
  id: 0,
  value: "test\_0",
  children: [
    {
      id: 1,
      value: "test\_1",
    },
    {
      id: 2,
      value: "test\_2",
    },
    {
      id: 3,
      value: "test\_3",
      children: [
        {
          id: 31,
          value: "test\_31",
        },
        {
          id: 32,
          value: "test\_32",
        },
        {
          id: 33,
          value: "test\_33",
          children: [
            {
              id: 331,
              value: "test\_331",
            },
            {
              id: 332,
              value: "test\_332",
            },
          ],
        },
      ],
    },
  ],
};

const arr = [];
function changeObj(obj) {
  arr.push({ id: obj.id, value: obj.value });
  if (obj.children) {
    for (let i = 0; i < obj.children.length; i++) {
      changeObj(obj.children[i]);
    }
  }
}
changeObj(obj);
console.log(arr);
// [
// { id: 0, value: 'test\_0' },
// { id: 1, value: 'test\_1' },
// { id: 2, value: 'test\_2' },
// { id: 3, value: 'test\_3' },
// { id: 31, value: 'test\_31' },
// { id: 32, value: 'test\_32' },
// { id: 33, value: 'test\_33' },
// { id: 331, value: 'test\_331' },
// { id: 332, value: 'test\_332' }
// ]

字符串转数组
// 输入
const str = `
1 21 3

 4 5 6 
 7 8 9
 `;
// 输出
// arr=[
// ['1','21','3'],
// ['4','5','6'],
// ['7','8','9']
// ]
let newStr = str.replace(/\s|\n/g, function (a, b, c, d) {
  if (a === "\n") {
    return "n";
  } else {
    return "s";
  }
});

let arr = newStr
  .split("n")
  .map((item) => item.split("s").filter((item) => item))
  .filter((item) => item.length);
console.log("arr: ", arr);

源码

封装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{


### 最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于**前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等**

>**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

![](https://img-blog.csdnimg.cn/img_convert/1e7effa86b3ff4fad0e850e8645f7e3f.webp?x-oss-process=image/format,png)

**前端视频资料:**
![](https://img-blog.csdnimg.cn/img_convert/394be7a60a775c5cbf9f45f44d86ada4.webp?x-oss-process=image/format,png)

 },
 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{


### 最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于**前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等**

>**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

[外链图片转存中...(img-drNP2zL9-1715472119242)]

**前端视频资料:**
[外链图片转存中...(img-dfaGkr1m-1715472119243)]

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值