2024年1月4日

一 盒模型

一个盒子由四个部分组成:contentpaddingbordermargin

盒子模型可以分成:

  • W3C 标准盒子模型

  • IE 怪异盒子模型

标准盒模型:

  • 盒子总宽度 = width + padding + border + margin;

  • 盒子总高度 = height + padding + border + margin

width/height 只是内容高度,不包含 paddingborder

怪异盒模型:

  • 盒子总宽度 = 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怎么改变主轴方向

flex-direction

  • row(默认值):主轴方向为水平方向,项目从左到右排列。

  • row-reverse:主轴方向为水平方向,项目从右到左排列。

  • column:主轴方向为垂直方向,项目从上到下排列。

  • column-reverse:主轴方向为垂直方向,项目从下到上排列。

三 回流和重绘

回流(reflow)和重绘(repaint)是浏览器渲染页面时的两个关键过程。

回流是指当DOM结构发生变化或者元素样式发生改变时,浏览器重新计算元素的几何属性(位置、大小等),然后重新布局整个页面。回流会涉及到重新计算元素的位置和大小,以及其他相关元素的相互影响,因此是一种比较耗费性能的操作。

重绘是指当元素的样式发生改变,但不影响其几何属性时,浏览器会重新绘制这个元素。重绘不会改变元素的布局,只是重新绘制元素的外观。

回流和重绘的区别在于,回流会触发重绘,但重绘不一定会触发回流。回流的成本比重绘高,因为回流会涉及到整个页面的重新布局,而重绘只需要重新绘制受影响的部分。

四 es6新特性

let const

箭头函数

  • 1、简化了函数的写法

  • 2、没有 this 机制,this 继承自上一个函数的上下文,如果上一层没有函数,则指向 window

  • 3、作为异步回调函数时,可解决 this 指向问题

Promise

数组的方法

解构赋值

扩展运算符(...)等

五 es6新增的高阶的数组方法
  1. map():对数组中的每个元素进行操作,并返回一个新的数组,新数组的元素是原数组经过操作后的结果。

const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(num => num * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
  1. filter():根据指定的条件筛选数组中的元素,并返回一个新的数组,新数组包含符合条件的元素。

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
  1. reduce():对数组中的元素进行累积操作,返回一个累积结果。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
  1. forEach():对数组中的每个元素执行指定的操作,没有返回值。

const numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => console.log(num));
// 1
// 2
// 3
// 4
// 5
  1. find():根据指定的条件查找数组中的元素,并返回第一个符合条件的元素。

const numbers = [1, 2, 3, 4, 5];
const evenNumber = numbers.find(num => num % 2 === 0);
console.log(evenNumber); // 2
  1. 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); // 1

unshift():向数组开头添加一个或多个元素。

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中,有全局作用域和局部作用域(函数作用域和块级作用域)。

  1. 全局作用域:全局作用域是在整个程序中都可访问的作用域。在全局作用域中定义的变量可以在程序的任何地方被访问。

const globalVariable = 'I am in global scope';
​
function foo() {
 console.log(globalVariable); // 可以访问全局作用域中的变量
}
​
foo();
  1. 函数作用域:函数作用域是在函数内部定义的作用域。在函数作用域中定义的变量只能在函数内部访问,外部无法访问。

function foo() {
 const functionVariable = 'I am in function scope';
 console.log(functionVariable); // 可以访问函数作用域中的变量
}
​
foo();
console.log(functionVariable); // 报错,无法访问函数作用域中的变量
  1. 块级作用域:块级作用域是在代码块(通常是由花括号 {} 包围的代码)内部定义的作用域。在ES6之前,JavaScript只有全局作用域和函数作用域,没有块级作用域。但是在ES6中引入了letconst关键字,它们可以在块级作用域中定义变量。

function foo() {
 if (true) {
   const blockVariable = 'I am in block scope';
   console.log(blockVariable); // 可以访问块级作用域中的变量
 }
 console.log(blockVariable); // 报错,无法访问块级作用域中的变量
}
​
foo();

作用域链是指在嵌套的作用域中查找变量的过程。当访问一个变量时,JavaScript引擎会先在当前作用域中查找,如果找不到,就会向上一级作用域继续查找,直到找到该变量或者到达全局作用域。如果在全局作用域中仍然找不到该变量,则会抛出一个引用错误。

八 函数的执行上下文

函数执行上下文是指在函数执行期间创建的一个执行环境,它包含了函数的变量、函数的参数和函数的作用域链。当函数被调用时,JavaScript引擎会创建一个函数执行上下文,并将其推入执行上下文栈中。

函数执行上下文包含以下几个重要的组成部分:

  1. 变量对象(Variable Object):变量对象是函数执行上下文中的一个内部对象,用于存储函数内部定义的变量、函数声明和函数的形参。它包括函数的所有局部变量、函数声明和形参的标识符。在函数执行过程中,变量对象会被创建并初始化。

  2. 作用域链(Scope Chain):作用域链是一个指向变量对象的链表,它用于解析变量的访问。当函数被创建时,它的作用域链就被创建,并且包含了函数自身的变量对象。当函数被调用时,会创建一个新的执行上下文,并将其作用域链指向父级执行上下文的作用域链。

  3. 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 是一个对象,从它可以获取异步操作的消息。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):使用 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 的结果返回。

十二 ts中的Interface, interface和type区别

接口(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中非常重要的概念,它可以帮助我们编写出更加健壮、可维护的代码。

interfacetype都是用于定义类型,但它们之间有一些重要的区别。

interface用于定义对象的结构,它可以在对象上声明属性和方法,并且可以定义索引签名(index signature)。interface定义的类型可以被实现或继承,也可以与其他interface合并。

例如,下面的代码定义了一个Person接口,它有两个属性nameage

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,weakset和weakmap

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]}

APIWeakSetSet有两个区别:

  • 没有遍历操作的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结构类似,也是用于生成键值对的集合

APIWeakMapMap有两个区别:

  • 没有遍历操作的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;
}
十六 vue路由模式,有什么区别

hash 模式和 history 模式

hash 模式的优缺点:

  • 优点:浏览器兼容性较好,连 IE8 都支持

  • 缺点:路径在井号 # 的后面,比较丑

history 模式的优缺点:

  • 优点:路径比较正规,没有井号 #

  • 缺点:兼容性不如 hash,且需要服务端支持,否则一刷新页面就404了

十七 浏览器的缓存机制

浏览器的缓存机制是一种机制,用于提高网页访问速度,同时减少服务器的负载。当用户访问一个网页时,浏览器会先检查本地是否有该网页的缓存,如果有则直接使用缓存,如果没有则向服务器请求该网页,并将结果缓存到本地。

浏览器的缓存机制主要由以下几个部分组成:

  1. 缓存位置:浏览器会将缓存数据存储在不同的位置,包括硬盘缓存(Disk Cache)和内存缓存(Memory Cache)等。

  2. 缓存策略:浏览器和服务器之间通过 HTTP 头信息传递缓存策略,包括缓存过期时间、缓存是否可被修改等。

  3. 缓存验证:当浏览器需要使用缓存数据时,会先向服务器发送验证请求,以确认缓存数据是否仍然有效。

  4. 缓存更新:当服务器返回新的数据时,浏览器会更新缓存,并将新的数据存储到缓存中。

通过使用浏览器的缓存机制,可以减少对服务器的请求次数,提高网页访问速度,同时减少服务器的负载,提高网站的性能和可用性。但是,需要注意缓存带来的副作用,比如缓存会导致用户看到旧的数据,缓存清除不及时可能会导致问题等。

十八 二叉树广度遍历js实现思路

二叉树的广度优先遍历可以使用队列来实现。具体步骤如下:

  1. 将根节点入队。

  2. 循环直到队列为空:

    • 弹出队首节点,并将节点的值加入结果数组。

    • 如果队首节点的左子节点不为空,则将左子节点入队。

    • 如果队首节点的右子节点不为空,则将右子节点入队。

下面是对应的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为什么可以修改数据,可以被Object.key遍历

object.defineProperty 是 ES5 引入的一个方法,它可以动态地给一个对象添加新的属性或修改对象的现有属性。相比 obj.prop = value,使用 object.defineProperty 有以下几个优势:

  1. 可以指定属性的描述符,包括属性的值、可枚举性、可配置性、可写性等等。

  2. 可以定义getter和setter,实现属性的访问器模式。

  3. 可以定义属性为不可配置,防止该属性被删除。

当我们使用 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指令来动态修改元素的属性。

二十一 axios封装 token在何时调用

在发送请求时将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';
  });
}
二十三 electrob打包图标 配置项
module.exports = {
  // 其他配置项
​
  build: {
    // 图标配置项
    icon: {
      // 覆盖原有图标
      replace: true,
      // 图标的路径
      src: path.join(__dirname, 'src/icons'),
      // 需要打包的图标的文件名
      name: 'your-icon-name',
      // 图标的目标大小
      size: 16,
      // 输出路径
      dest: path.join(__dirname, 'dist/icons'),
    },
  },
};
二十四 二维数组去重,子集包含
  1. 对二维数组进行排序,可以先将二维数组的所有行进行排序,然后按照排序后的顺序重新构造二维数组。这样可以方便地判断子集是否包含重复元素。

  2. 遍历排序后的二维数组,对于每一行,将其元素存入一个集合中。由于集合中不能有重复元素,所以如果遇到重复元素,可以直接跳过。

  3. 判断子集是否包含重复元素时,可以遍历子集中的所有元素,将它们存入一个集合中。如果集合的大小小于子集的长度,说明子集中存在重复元素;否则,不存在重复元素。

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方法用于判断是否至少有一个元素满足条件,every方法用于判断所有元素是否都满足条件。

  • 如果some方法返回true,则意味着至少有一个元素满足条件;如果every方法返回true,则意味着所有元素都满足条件。

someevery都是数组的方法,用于对数组中的元素进行判断

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱猪头的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值