一个盒子由四个部分组成:
content
、padding
、border
、margin
。盒子模型可以分成:
W3C 标准盒子模型
IE 怪异盒子模型
标准盒模型:
盒子总宽度 = width + padding + border + margin;
盒子总高度 = height + padding + border + margin
width/height
只是内容高度,不包含padding
和border
值怪异盒模型:
盒子总宽度 = width + margin;
盒子总高度 = height + margin;
CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度
box-sizing: content-box|border-box|inherit;
content-box 默认值,元素的 width/height 不包含padding,border,与标准盒子模型表现一致
border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致
inherit 指定 box-sizing 属性的值,应该从父元素继承
flex-direction
row
(默认值):主轴方向为水平方向,项目从左到右排列。
row-reverse
:主轴方向为水平方向,项目从右到左排列。
column
:主轴方向为垂直方向,项目从上到下排列。
column-reverse
:主轴方向为垂直方向,项目从下到上排列。
回流(reflow)和重绘(repaint)是浏览器渲染页面时的两个关键过程。
回流是指当DOM结构发生变化或者元素样式发生改变时,浏览器重新计算元素的几何属性(位置、大小等),然后重新布局整个页面。回流会涉及到重新计算元素的位置和大小,以及其他相关元素的相互影响,因此是一种比较耗费性能的操作。
重绘是指当元素的样式发生改变,但不影响其几何属性时,浏览器会重新绘制这个元素。重绘不会改变元素的布局,只是重新绘制元素的外观。
回流和重绘的区别在于,回流会触发重绘,但重绘不一定会触发回流。回流的成本比重绘高,因为回流会涉及到整个页面的重新布局,而重绘只需要重新绘制受影响的部分。
let const
箭头函数
1、简化了函数的写法
2、没有 this 机制,this 继承自上一个函数的上下文,如果上一层没有函数,则指向 window
3、作为异步回调函数时,可解决 this 指向问题
Promise
数组的方法
解构赋值
扩展运算符(...)等
map()
:对数组中的每个元素进行操作,并返回一个新的数组,新数组的元素是原数组经过操作后的结果。const numbers = [1, 2, 3, 4, 5]; const doubledNumbers = numbers.map(num => num * 2); console.log(doubledNumbers); // [2, 4, 6, 8, 10]
filter()
:根据指定的条件筛选数组中的元素,并返回一个新的数组,新数组包含符合条件的元素。const numbers = [1, 2, 3, 4, 5]; const evenNumbers = numbers.filter(num => num % 2 === 0); console.log(evenNumbers); // [2, 4]
reduce()
:对数组中的元素进行累积操作,返回一个累积结果。const numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((acc, num) => acc + num, 0); console.log(sum); // 15
forEach()
:对数组中的每个元素执行指定的操作,没有返回值。const numbers = [1, 2, 3, 4, 5]; numbers.forEach(num => console.log(num)); // 1 // 2 // 3 // 4 // 5
find()
:根据指定的条件查找数组中的元素,并返回第一个符合条件的元素。const numbers = [1, 2, 3, 4, 5]; const evenNumber = numbers.find(num => num % 2 === 0); console.log(evenNumber); // 2
some()
:判断数组中是否存在满足指定条件的元素,返回布尔值。const numbers = [1, 2, 3, 4, 5]; const hasEvenNumber = numbers.some(num => num % 2 === 0); console.log(hasEvenNumber); // true
push():向数组末尾添加一个或多个元素。
const numbers = [1, 2, 3]; numbers.push(4); console.log(numbers); // [1, 2, 3, 4]pop():从数组末尾移除最后一个元素,并返回被移除的元素。
const numbers = [1, 2, 3, 4]; const lastNumber = numbers.pop(); console.log(numbers); // [1, 2, 3] console.log(lastNumber); // 4
shift()
:从数组开头移除第一个元素,并返回被移除的元素。const numbers = [1, 2, 3, 4]; const firstNumber = numbers.shift(); console.log(numbers); // [2, 3, 4] console.log(firstNumber); // 1unshift():向数组开头添加一个或多个元素。
const numbers = [2, 3, 4]; numbers.unshift(1); console.log(numbers); // [1, 2, 3, 4]splice():从数组中移除、替换或添加元素。
const numbers = [1, 2, 3, 4, 5]; numbers.splice(2, 1); // 从索引2开始移除1个元素 console.log(numbers); // [1, 2, 4, 5] numbers.splice(1, 0, 6, 7); // 从索引1开始不移除元素,添加6和7 console.log(numbers); // [1, 6, 7, 2, 4, 5]
作用域是指在程序中定义变量的区域,它决定了变量的可见性和生命周期。在JavaScript中,有全局作用域和局部作用域(函数作用域和块级作用域)。
全局作用域:全局作用域是在整个程序中都可访问的作用域。在全局作用域中定义的变量可以在程序的任何地方被访问。
const globalVariable = 'I am in global scope'; function foo() { console.log(globalVariable); // 可以访问全局作用域中的变量 } foo();
函数作用域:函数作用域是在函数内部定义的作用域。在函数作用域中定义的变量只能在函数内部访问,外部无法访问。
function foo() { const functionVariable = 'I am in function scope'; console.log(functionVariable); // 可以访问函数作用域中的变量 } foo(); console.log(functionVariable); // 报错,无法访问函数作用域中的变量
块级作用域:块级作用域是在代码块(通常是由花括号
{}
包围的代码)内部定义的作用域。在ES6之前,JavaScript只有全局作用域和函数作用域,没有块级作用域。但是在ES6中引入了let
和const
关键字,它们可以在块级作用域中定义变量。function foo() { if (true) { const blockVariable = 'I am in block scope'; console.log(blockVariable); // 可以访问块级作用域中的变量 } console.log(blockVariable); // 报错,无法访问块级作用域中的变量 } foo();作用域链是指在嵌套的作用域中查找变量的过程。当访问一个变量时,JavaScript引擎会先在当前作用域中查找,如果找不到,就会向上一级作用域继续查找,直到找到该变量或者到达全局作用域。如果在全局作用域中仍然找不到该变量,则会抛出一个引用错误。
函数执行上下文是指在函数执行期间创建的一个执行环境,它包含了函数的变量、函数的参数和函数的作用域链。当函数被调用时,JavaScript引擎会创建一个函数执行上下文,并将其推入执行上下文栈中。
函数执行上下文包含以下几个重要的组成部分:
变量对象(Variable Object):变量对象是函数执行上下文中的一个内部对象,用于存储函数内部定义的变量、函数声明和函数的形参。它包括函数的所有局部变量、函数声明和形参的标识符。在函数执行过程中,变量对象会被创建并初始化。
作用域链(Scope Chain):作用域链是一个指向变量对象的链表,它用于解析变量的访问。当函数被创建时,它的作用域链就被创建,并且包含了函数自身的变量对象。当函数被调用时,会创建一个新的执行上下文,并将其作用域链指向父级执行上下文的作用域链。
this 值:this 值指向当前执行上下文所属的对象。它的值在函数调用时确定。
函数执行上下文的创建和销毁是由JavaScript引擎自动管理的。当函数执行完毕后,它的执行上下文会被销毁,同时从执行上下文栈中弹出。
理解函数执行上下文对于理解函数的作用域、变量访问和函数调用非常重要。
length
的整数平方数组的函数function createArray(length) { return Array.from({ length }, (_, index) => index + 1).map((num) => num * num); }Array.from( // 第一个参数:可选,表示源对象,可以是类数组对象或可迭代对象。 // 类数组对象:拥有length属性和索引属性的对象。 // 可迭代对象:拥有next方法的对象,每次调用next方法返回一个迭代器。 { length: 3 }, // 第二个参数:可选,表示映射函数,用于处理数组中的每个元素。 // 该函数被数组中的每个元素依次调用,返回一个新数组。 (element, index) => index + 1, // 第三个参数:可选,表示该函数中this的值。 // 如果不提供该参数,则该函数中this的值为Array.from本身。 );该代码使用Array.from将一个类数组对象转换为数组,其中:
第一个参数为一个拥有length属性和索引属性的对象{ length: 3 },表示源对象。
第二个参数为一个映射函数,用于处理数组中的每个元素,该函数被数组中的每个元素依次调用,返回一个新数组。
第三个参数为该函数中this的值,没有提供该参数,函数中this的值为Array.from本身。
Promise 是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 (1)Promise 的实例有三个状态: Pending(进行中) Fulfilled(已完成) Rejected(已拒绝) 当把一件事情交给 promise 时,它的状态就是 Pending,任务完成了状态就变成了 Fulfilled、没有完成失败了就变成了 Rejected。 (2)Promise 的实例有两个过程: pending -> fulfilled : Resolved(已完成) pending -> rejected:Rejected(已拒绝) 注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise 的特点: 对象的状态不受外界影响。promise 对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是 promise 这个名字的由来——“承诺”;
一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled,从pending 变为 rejected。这时就称为 resolved(已定型)。如果改变已经发生了,你再对 promise 对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
Promise 的缺点: 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
总结: Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、fulfilled和rejected,分别代表了进行中、已成功和已失败。实例的状态只能由pending 转变 fulfilled或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。以同步方式编写异步代码,从而避免回调地狱,提高代码的可读性和可维护性。Promise表示一个异步操作的最终完成(或失败)及其结果值,可以链式调用then方法进行处理。
注意:在构造 Promise 的时候,构造函数内部的代码是立即执行的
生成器(Generator):使用 generator 函数和 yield 关键字来处理异步操作。
function* myGenerator() { const data1 = yield 'async1'; const data2 = yield 'async2'; console.log(data1, data2); } const generator = myGenerator(); generator.next(); generator.next('async1'); generator.next('async2');myGenerator 是一个 generator 函数。通过调用 myGenerator() 创建一个迭代器,并使用 next() 方法遍历 generator 函数的执行流程。在每次调用 next() 时,generator 函数会在遇到 yield 语句时暂停执行,并将 yield 之后的值作为 Promise 的结果返回。
接口(Interface)用于描述对象的结构,它定义了一组属性的集合,但没有实现函数。接口可以用来约束类型,确保代码的可维护性和可读性。
接口的语法如下:
interface 名称 { 属性名: 类型; 方法名: () => 类型; }其中,属性的类型可以省略,如果属性的类型为any,则不用指定类型。方法的返回类型也可以省略,如果方法的返回类型为any,则不用指定类型。
例如,下面的接口定义了一个简单的Person对象:
interface Person { name: string; age: number; }这个接口定义了一个对象,它必须有一个名为name的字符串属性和一个名为age的数字属性。
我们可以在类型前直接使用接口来约束变量的类型,例如:
let person: Person = { name: "Tom", age: 18 };除了属性,接口还可以定义索引签名来描述对象的索引属性。例如:
interface Point { x: number; y: number; } interface PointMap { [index: string]: Point; }这样,PointMap就可以用来描述一个拥有任意键的Point对象的映射。
接口还可以与实现联合使用,通过继承接口来扩展接口的定义。例如:
interface Walkable { walk(): void; } interface Flyable { fly(): void; } interface Animal extends Walkable, Flyable { sleep(): void; }这样,Animal对象既具有Walkable接口的所有属性和方法,又具有Flyable接口的所有属性和方法,并且还定义了一个额外的sleep()方法。
接口是TypeScript中非常重要的概念,它可以帮助我们编写出更加健壮、可维护的代码。
interface
和type
都是用于定义类型,但它们之间有一些重要的区别。
interface
用于定义对象的结构,它可以在对象上声明属性和方法,并且可以定义索引签名(index signature)。interface
定义的类型可以被实现或继承,也可以与其他interface
合并。例如,下面的代码定义了一个
Person
接口,它有两个属性name
和age
:interface Person { name: string; age: number; }
type
用于定义变量或函数的类型别名,它可以在不改变现有类型的情况下给类型命名。type
定义的类型不能被实现或继承,也不能与其他type
合并。例如,下面的代码定义了一个类型别名
PersonName
,它指向string
类型:type PersonName = string;另外,
type
还可以用于类型推断。当我们定义一个变量时,如果变量的类型已经很明显,我们可以使用type
来推断这个类型。例如,下面的代码中,我们可以使用
type
来推断Person
接口的类型:type Person = { name: string; age: number; }; const person: Person = { name: "John", age: 30 };
interface
用于定义对象的结构,而type
用于定义类型别名和利用类型推断。在使用时,可以根据实际需要选择合适的语法。
Set和Map是ECMAScript 6(ES6)中引入的两种新的原生对象。它们都是用于存储数据的集合,但它们的设计目的和使用方式有所不同。 Set是一种无序的唯一值集合。这意味着它只存储唯一的值,并且不会存储重复的值。Set对象可以存储任何可比较值,包括对象引用。Set具有以下特点:
集合 是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
字典 是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
Set可以存储任何可比较值。
存储的值是唯一的,重复的值会被自动去重。
Set对象是有序的,但这个顺序并不是确定的,即Set中的元素顺序可能会改变。
Set对象提供了许多有用的方法,如add()、delete()、has()、clear()、size等。
Map是一种键值对的集合。与对象不同,Map的键可以是任何可比较的值,而不仅仅是字符串或符号。这意味着可以使用非字符串值作为键,例如数字或对象。Map对象可以存储任何类型的值作为值。Map具有以下特点:
存储的键和值可以是任何可比较的值。
Map对象是有序的,保留了元素插入的顺序。
Map对象提供了许多有用的方法,如set()、get()、delete()、clear()、size等。 区别:
共同点:集合、字典都可以存储不重复的值
不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储
下面是一个使用Set和Map的示例:
使用Set去重 const arr = [1, 2, 2, 3, 3, 3]; const uniqueArr = [...new Set(arr)]; // [1, 2, 3] console.log(uniqueArr); // 使用Map存储键值对 const map = new Map(); map.set("key1", "value1"); map.set("key2", "value2"); console.log(map.get("key1")); // "value1" console.log(map.has("key2")); // true map.delete("key3"); console.log(map.size); // 2总之,Set和Map都是用于存储数据的集合,但Set用于存储唯一的值,而Map用于存储键值对。根据实际需求选择合适的数据结构可以提高代码的可读性和性能。
WeakSet
创建
WeakSet
实例const ws = new WeakSet();
WeakSet
可以接受一个具有Iterable
接口的对象作为参数const a = [[1, 2], [3, 4]]; const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}在
API
中WeakSet
与Set
有两个区别:
没有遍历操作的
API
没有
size
属性
WeakSet
只能成员只能是引用类型,而不能是其他类型的值let ws=new WeakSet(); // 成员不是引用类型 let weakSet=new WeakSet([2,3]); console.log(weakSet) // 报错 // 成员为引用类型 let obj1={name:1} let obj2={name:1} let ws=new WeakSet([obj1,obj2]); console.log(ws) //WeakSet {{…}, {…}}
WeakSet
里面的引用只要在外部消失,它在WeakSet
里面的引用就会自动消失WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合在
API
中WeakMap
与Map
有两个区别:
没有遍历操作的
API
没有
clear
清空方法// WeakMap 可以使用 set 方法添加成员 const wm1 = new WeakMap(); const key = {foo: 1}; wm1.set(key, 2); wm1.get(key) // 2 // WeakMap 也可以接受一个数组, // 作为构造函数的参数 const k1 = [1, 2, 3]; const k2 = [4, 5, 6]; const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]); wm2.get(k2) // "bar"
WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名const map = new WeakMap(); map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key map.set(null, 2) // TypeError: Invalid value used as weak map key
WeakMap
的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用举个场景例子:
在网页的 DOM 元素上添加数据,就可以使用
WeakMap
结构,当该 DOM 元素被清除,其所对应的WeakMap
记录就会自动被移除const wm = new WeakMap(); const element = document.getElementById('example'); wm.set(element, 'some information'); wm.get(element) // "some information"注意:
WeakMap
弱引用的只是键名,而不是键值。键值依然是正常引用下面代码中,键值
obj
会在WeakMap
产生新的引用,当你修改obj
不会影响到内部const wm = new WeakMap(); let key = {}; let obj = {foo: 1}; wm.set(key, obj); obj = null; wm.get(key) // Object {foo: 1}
function binarySearch(arr, target) { let start = 0; let end = arr.length - 1; while (start <= end) { let mid = Math.floor((start + end) / 2); if (arr[mid] === target) { return mid; } else if (arr[mid] < target) { start = mid + 1; } else { end = mid - 1; } } return -1; // target not found }二分查找算法可以在已排序的数组中快速查找目标元素。该函数接受两个参数,一个是已排序的数组(arr),另一个是目标元素(target)。它通过不断将查找区间缩小一半来逼近目标元素的位置。如果找到目标元素,则返回其索引;如果未找到目标元素,则返回-1表示未找到。
冒泡排序的基本思想是重复地从数组的起始位置开始,比较相邻的两个元素并交换位置,直到数组末尾。通过多次遍历数组,每次将未排序部分的最大元素移动到已排序部分的末尾,最终实现整个数组的排序。
function bubbleSort(arr) { for (let i = 0; i < arr.length - 1; i++) { for (let j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; }
hash 模式和 history 模式
hash 模式的优缺点:
优点:浏览器兼容性较好,连 IE8 都支持
缺点:路径在井号
#
的后面,比较丑history 模式的优缺点:
优点:路径比较正规,没有井号
#
缺点:兼容性不如 hash,且需要服务端支持,否则一刷新页面就404了
浏览器的缓存机制是一种机制,用于提高网页访问速度,同时减少服务器的负载。当用户访问一个网页时,浏览器会先检查本地是否有该网页的缓存,如果有则直接使用缓存,如果没有则向服务器请求该网页,并将结果缓存到本地。
浏览器的缓存机制主要由以下几个部分组成:
缓存位置:浏览器会将缓存数据存储在不同的位置,包括硬盘缓存(Disk Cache)和内存缓存(Memory Cache)等。
缓存策略:浏览器和服务器之间通过 HTTP 头信息传递缓存策略,包括缓存过期时间、缓存是否可被修改等。
缓存验证:当浏览器需要使用缓存数据时,会先向服务器发送验证请求,以确认缓存数据是否仍然有效。
缓存更新:当服务器返回新的数据时,浏览器会更新缓存,并将新的数据存储到缓存中。
通过使用浏览器的缓存机制,可以减少对服务器的请求次数,提高网页访问速度,同时减少服务器的负载,提高网站的性能和可用性。但是,需要注意缓存带来的副作用,比如缓存会导致用户看到旧的数据,缓存清除不及时可能会导致问题等。
二叉树的广度优先遍历可以使用队列来实现。具体步骤如下:
将根节点入队。
循环直到队列为空:
弹出队首节点,并将节点的值加入结果数组。
如果队首节点的左子节点不为空,则将左子节点入队。
如果队首节点的右子节点不为空,则将右子节点入队。
下面是对应的JavaScript代码实现:
class TreeNode { val; left; right; constructor(val) { this.val = val; this.left = null; this.right = null; } } function广度优先遍历(root) { if (!root) { return []; } let queue = [root]; let result = []; while (queue.length) { let node = queue.shift(); result.push(node.val); if (node.left) { queue.push(node.left); } if (node.right) { queue.push(node.right); } } return result; }使用示例:
const root = new TreeNode(1); root.left = new TreeNode(2); root.right = new TreeNode(3); root.left.left = new TreeNode(4); root.left.right = new TreeNode(5); console.log(广度优先遍历(root)); // Output: [1, 2, 3, 4, 5]这段代码首先创建了一个
TreeNode
类用于表示二叉树的节点。然后定义了广度优先遍历
函数,该函数使用一个队列来存储待遍历的节点,并通过循环不断将节点弹出并加入结果数组中。最后,通过创建一个示例二叉树并调用广度优先遍历
函数来验证代码的正确性。
object.defineProperty
是 ES5 引入的一个方法,它可以动态地给一个对象添加新的属性或修改对象的现有属性。相比obj.prop = value
,使用object.defineProperty
有以下几个优势:
可以指定属性的描述符,包括属性的值、可枚举性、可配置性、可写性等等。
可以定义getter和setter,实现属性的访问器模式。
可以定义属性为不可配置,防止该属性被删除。
当我们使用
object.defineProperty
修改一个对象的属性时,实际上是向该对象的[[OwnPropertyKeys]]
属性列表中添加或修改属性。这个属性列表是由对象内部维护的一个隐藏属性,它包含了对象所有的自有属性,包括使用object.defineProperty
定义的属性。
Object.keys()
方法返回一个由对象自身的所有可枚举属性的名称组成的数组。由于使用object.defineProperty
定义的属性会被计入对象的[[OwnPropertyKeys]]
属性列表中,所以它能够被Object.keys()
方法遍历到。
object.defineProperty
可以修改数据,并且可以被Object.keys()
遍历,是因为它实际上是向对象的[[OwnPropertyKeys]]
属性列表中添加或修改属性,而这个属性列表会被Object.keys()
方法计入结果中。
冻结元素是指使用v-once指令冻结的元素,它会将该元素的DOM结构保存在内存中,以便在后续的渲染中进行比较和优化。冻结元素不可修改内部属性的原因是为了保证在冻结后,该元素的DOM结构不会被意外修改,从而导致Vue无法正确地进行比较和优化。
具体来说,Vue在进行渲染时,会将DOM元素的引用保存在实例的数据中,然后在每次渲染时比较数据和DOM之间的差异,以确定哪些元素需要更新。如果在冻结元素内部添加或删除元素,或者修改元素的属性,这些变化将不会被Vue检测到,因为Vue只比较保存在数据中的DOM结构和实际的DOM结构。这样就会导致Vue在渲染时出现错误,因为Vue预期的DOM结构与实际的DOM结构不一致。
因此,为了确保Vue能够正确地进行渲染和优化,冻结元素内部的属性是不可修改的。如果需要在冻结元素内部添加或删除元素,或者修改元素的属性,应该在冻结之前进行操作。可以通过在模板中使用v-if或v-show指令来实现动态添加或删除元素,或者使用v-bind指令来动态修改元素的属性。
在发送请求时将token作为Authorization头部字段的值进行传递
一种用于处理大量数据的优化方案,它通过只渲染可见部分的数据来提高列表渲染性能
计算列表的可见行数和每一行的高度。
// 根据列表总行数、窗口大小和滚动条位置计算可见行数和每一行的高度 function calculateVisibleRows(totalRows, windowSize, scrollTop) { const visibleRows = Math.floor(windowSize / rowCount); const rowHeight = windowHeight / totalRows; return { visibleRows, rowHeight }; }根据可见行数和每一行的高度创建一个虚拟列表数组。
// 根据可见行数和每一行的高度创建虚拟列表 function createVirtualList(visibleRows, rowHeight) { const virtualList = []; for (let i = 0; i < visibleRows; i++) { virtualList.push({ row: i + 1, height: rowHeight }); } return virtualList; }在窗口大小和滚动条位置改变时,重新计算虚拟列表。
// 监听窗口大小和滚动条位置改变事件 window.addEventListener('resize', reCalculateVirtualList); document.querySelector('.scroll-container').addEventListener('scroll', reCalculateVirtualList); // 重新计算虚拟列表 function reCalculateVirtualList() { const totalRows = 1000; // 假设总行数为1000 const { visibleRows, rowHeight } = calculateVisibleRows(totalRows, windowSize, scrollTop); const virtualList = createVirtualList(visibleRows, rowHeight); renderVirtualList(virtualList); // 渲染虚拟列表 }渲染虚拟列表。
// 渲染虚拟列表 function renderVirtualList(virtualList) { const listContainer = document.querySelector('.list-container'); listContainer.innerHTML = ''; virtualList.forEach((row, index) => { const listItem = document.createElement('li'); listItem.textContent = row.row; listContainer.appendChild(listItem); // 给每个li元素设置高度为每一行的高度 listItem.style.height = row.height + 'px'; }); }
module.exports = { // 其他配置项 build: { // 图标配置项 icon: { // 覆盖原有图标 replace: true, // 图标的路径 src: path.join(__dirname, 'src/icons'), // 需要打包的图标的文件名 name: 'your-icon-name', // 图标的目标大小 size: 16, // 输出路径 dest: path.join(__dirname, 'dist/icons'), }, }, };
对二维数组进行排序,可以先将二维数组的所有行进行排序,然后按照排序后的顺序重新构造二维数组。这样可以方便地判断子集是否包含重复元素。
遍历排序后的二维数组,对于每一行,将其元素存入一个集合中。由于集合中不能有重复元素,所以如果遇到重复元素,可以直接跳过。
判断子集是否包含重复元素时,可以遍历子集中的所有元素,将它们存入一个集合中。如果集合的大小小于子集的长度,说明子集中存在重复元素;否则,不存在重复元素。
function isSubsetWithoutDuplicates(arr, subset) { // 对二维数组进行排序 arr.sort((row1, row2) => row1.join('') > row2.join('')); // 遍历排序后的二维数组 for (let row of arr) { // 将当前行存入集合中 let set = new Set(row); // 如果当前行的集合大小小于当前行的长度,说明当前行存在重复元素 if (set.size < row.length) { return false; } } // 判断子集是否包含重复元素 let subsetSet = new Set(subset); return subsetSet.size < subset.length; } //示例 let arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; let subset = [2, 3, 1]; let result = isSubsetWithoutDuplicates(arr, subset); console.log(result); // 输出:true
some
方法用于判断是否至少有一个元素满足条件,every
方法用于判断所有元素是否都满足条件。如果
some
方法返回true
,则意味着至少有一个元素满足条件;如果every
方法返回true
,则意味着所有元素都满足条件。
some
和every
都是数组的方法,用于对数组中的元素进行判断