一、Js的数据类型
基础类型有7个
- String
- Number
- BigInt
- Boolean
- Symbol
- Null
- Undefined
引用类型有1个
- Object(细分有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等)
二、闭包和作用域
闭包是作用域应用的特殊场景。 js中常见的作用域包括全局作用域、函数作用域、块级作用域。要知道js中自由变量的查找是在函数定义的地方,向上级作用域查找,不是在执行的地方。 常见的闭包使用有两种场景:一种是函数作为参数被传递;一种是函数作为返回值被返回。
// 函数作为返回值
function create() {
let a = 100;
return function () {
console.log(a);
};
}
const fn = create();
const a = 200;
fn(); // 100
// 函数作为参数被传递
function print(fb) {
const b = 200;
fb();
}
const b = 100;
function fb() {
console.log(b);
}
print(fb); // 100
三、Set WeakSet Map WeakMap之间的区别
Set
成员唯一、无序且不重复
[value1, value2, …],键值与键名是一致的(或者说只有键值,没有键名)
可以遍历,方法有:add、delete、has
set可以给基本数据类型去重
const list = [4, 2, 1, 3, 3]
const mySet = new Set(list);
// 将 Set 转换为数组
const Array_ = [...mySet];
console.log(Array_); // 输出排序后的数组
WeakSet
成员都是对象
成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
不能遍历,方法有add、delete、has
非常适合 检测循环引用
// 对传入的 subject 对象内部存储的所有内容执行回调
function execRecursively(fn, subject, _refs = new WeakSet()) {
// 避免无限递归
if (_refs.has(subject)) {
return;
}
fn(subject);
if (typeof subject === "object") {
_refs.add(subject);
for (const key in subject) {
execRecursively(fn, subject[key], _refs);
}
}
}
const foo = {
foo: "Foo",
bar: {
bar: "Bar",
},
};
foo.bar.baz = foo; // 循环引用!
execRecursively((obj) => console.log(obj), foo);
Map
本质上是键值对的集合,类似集合
可以遍历,方法很多可以跟各种数据格式转换
const myMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
// 示例使用对象作为键:
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const myMapWithObjects = new Map([
[obj1, 'value1'],
[obj2, 'value2']
]);
WeakMap
只接受对象类型作为键名,不接受其他类型的值作为键名
键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的,一个对象作为 WeakMap 的键存在,不会阻止该对象被垃圾回收。不能遍历,方法有get、set、has、delete
四、setTimeOut准时吗?
在理论上,setTimeout 是不准时的,因为它的执行时间取决于 JavaScript 引擎的当前负载和其他因素。虽然 setTimeout 的参数是以毫秒为单位的延迟时间,但并不意味着回调函数会在精确的毫秒数后立即执行。
具体来说,setTimeout 的延迟时间只是一个最小值,而不是精确的等待时间。实际上,当 setTimeout 的延迟时间到期后,回调函数会被添加到事件队列中,在 JavaScript 引擎执行主线程上的任务之后尽快执行。但是,如果主线程忙于执行其他任务,可能会导致回调函数的实际执行时间延迟,尤其是在高负载时或者在执行大量计算的情况下。
另外,一些浏览器甚至在隐藏的标签页或后台标签页中对 setTimeout 进行了优化,可能会延迟执行回调函数,以节省资源并提高性能。
因此,在实际应用中,不应该依赖于 setTimeout 的准确性。如果需要精确的时间控制,可以考虑使用 requestAnimationFrame、requestIdleCallback 或 Web Workers 等其他机制。
五、JS打乱数组最简单的方法
JavaScript 中打乱数组最简单的方法之一是使用 Fisher-Yates 洗牌算法(也称为 Knuth 洗牌算法)。这是一种经典的随机置换算法,其思想是从数组的末尾开始,随机选择一个元素并将其与当前位置的元素交换,然后继续向前,直到第一个元素。
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 生成随机索引
[array[i], array[j]] = [array[j], array[i]]; // 交换元素
}
return array;
}
// 示例用法
const myArray = [1, 2, 3, 4, 5];
console.log(shuffleArray(myArray)); // 输出打乱后的数组
六、防抖函数和节流函数
防抖函数
防抖函数的作用是在触发事件后等待一定的时间再执行函数,如果在等待时间内又触发了该事件,则重新计时。在最后一次触发事件之后的一段时间内没有再次触发事件,才会执行函数。
适用于处理频繁触发的事件,例如输入框输入、窗口调整大小等。可以用于优化性能,避免频繁执行重复的操作。
function debounce(func, delay) {
let timerId;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
const debouncedFunction = debounce(function() {
console.log('Debounced function called');
}, 1000);
// 在一秒内连续调用多次
debouncedFunction(); // 只有在一秒后才会执行
debouncedFunction(); // 在一秒内又调用了,重新计时
节流函数
节流函数的作用是在一定的时间间隔内只执行一次函数,无论该时间段内触发了多少次事件。即在固定的时间间隔内执行函数,而不管触发次数。
适用于控制事件触发的频率,例如滚动事件、鼠标移动事件等。可以用于限制事件处理的频率,防止过多的事件处理导致性能问题。
function throttle(func, delay) {
let canCall = true;
return function(...args) {
if (canCall) {
func.apply(this, args);
canCall = false;
setTimeout(() => {
canCall = true;
}, delay);
}
};
}
const throttledFunction = throttle(function() {
console.log('Throttled function called');
}, 1000);
// 连续调用多次
throttledFunction(); // 只有第一次会立即执行
throttledFunction(); // 在一秒内的其他调用会被忽略
七、实现图片懒加载
html
<img class="lazyload" data-src="path_to_image.jpg" alt="Image">
css
.lazyload {
opacity: 0; /* 默认隐藏 */
transition: opacity 0.3s ease-in-out; /* 可选的渐变效果 */
}
.lazyloaded {
opacity: 1; /* 图片加载后显示 */
}
js
document.addEventListener("DOMContentLoaded", function() {
let lazyloadImages = document.querySelectorAll(".lazyload");
let imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazyload");
lazyImage.classList.add("lazyloaded");
imageObserver.unobserve(lazyImage);
}
});
});
lazyloadImages.forEach(function(image) {
imageObserver.observe(image);
});
});
当页面加载完毕后,通过 querySelectorAll 查找所有具有 lazyload 类的图片元素。然后创建一个 IntersectionObserver 实例,用于观察每个图片元素是否进入了视口(即可见区域)。当图片元素进入视口时,触发回调函数,将 data-src 属性的值赋给 src 属性,从而加载图片。同时移除 lazyload 类并添加 lazyloaded 类,以便标识图片已经被加载。最后,通过 unobserve 方法停止观察该图片元素,以减少性能消耗。
八、从输入 URL 到页面展示,这中间发生了什么?
1、解析 URL:
首先,浏览器会解析输入的 URL。这个过程涉及到解析协议(如 HTTP、HTTPS)、域名、路径和查询参数等信息。
2、DNS 解析:
浏览器会查询 DNS 服务器,将输入的域名解析为对应的 IP 地址。如果 DNS 缓存中已经存在该域名的解析结果,则直接使用缓存的结果。
3、建立 TCP 连接:
浏览器与服务器之间通过 TCP 协议建立连接。这个过程涉及到 TCP 的三次握手,包括客户端向服务器发送 SYN,服务器响应 SYN+ACK,最后客户端发送 ACK。
4、发起 HTTP 请求:
一旦建立了 TCP 连接,浏览器就会发送 HTTP 请求。请求的内容包括请求方法(如 GET、POST)、请求头、请求体等信息。
5、服务器处理请求:
服务器收到请求后,根据请求的内容进行处理。处理的过程可能涉及到查询数据库、生成动态内容、读取文件等操作。
6、服务器返回响应:
服务器处理完请求后,会返回一个 HTTP 响应。响应包括状态码、响应头、响应体等信息。
7、接收响应:
浏览器接收到响应后,会根据响应头中的信息确定如何处理响应。如果是 HTML 内容,则继续下一步的渲染过程。
8、解析 HTML 和构建 DOM 树:
浏览器会解析 HTML 内容,并根据 HTML 标记构建 DOM 树。DOM 树表示了文档的结构,包括元素、属性和它们之间的关系。
9、构建 CSSOM 树:
浏览器会解析 CSS 样式表,并构建 CSSOM 树。CSSOM 树表示了文档的样式信息,包括样式规则、选择器和样式声明等。
10、合并 DOM 树和 CSSOM 树,生成渲染树:
浏览器将 DOM 树和 CSSOM 树合并,生成渲染树(Render Tree)。渲染树只包含渲染页面所需的内容,即可视和渲染的元素及其样式信息。
11、布局和绘制:
浏览器根据渲染树中的信息,计算每个元素在页面中的位置和大小,并进行绘制。这个过程称为布局(Layout)和绘制(Painting)。
12、页面展示:
最后,浏览器将绘制好的页面展示给用户。这个过程可能还会涉及到一些额外的步骤,如图层合成、动画渲染等
总结就是
解析 URL 和 DNS 解析 → 建立 TCP 连接 → 发起 HTTP 请求 → 服务器处理请求并返回响应 → 浏览器解析响应并展示页面
九、HTML5 WebStorage 是什么
Web Storage中包含两个关键的对象,分别是localStorage对象和sessionStorage对象,它们都是Web Storage的实例,所以都能使用Web Storage接口提供的方法和属性。localStorage对象用于本地存储,sessionStorage对象用于会话存储。
1937

被折叠的 条评论
为什么被折叠?



