1.为什么需要清除浮动,写出清除浮动的方式

浮动元素在文档流中脱离了正常的位置,因此会导致父元素高度塌陷,影响布局的正确性和美观性。所以通常需要清除浮动。
以下是常见的清除浮动的方式:

父元素设置 overflow: auto 或 overflow: hidden 属性:

plaintext
1
2
3
.clearfix {
overflow: auto;
}

使用空标签清除浮动:

plaintext
1
2
3
4
5
<div class="parent">
<div class="child"></div>
<div class="child"></div>
<div class="clear"></div>
</div>
plaintext
1
2
3
.clear {
clear: both;
}

使用伪元素清除浮动:

plaintext
1
2
3
4
5
.clearfix::after {
content: '';
display: block;
clear: both;
}

在父元素上添加一个伪元素,设置 clear: both 属性,这样就可以清除浮动。
需要注意的是,以上清除浮动的方式可能会对其他布局产生影响,例如 overflow: hidden 可能会导致内容被裁剪,空标签和伪元素可能会产生多余的 DOM 元素。因此需要根据实际情况选择合适的方式。

2.对原型、原型链的理解

在 JavaScript 中,每个对象都有一个指向另一个对象的引用,这个被引用的对象就是该对象的原型(prototype)。对象通过原型继承属性和方法,这样可以实现代码的复用和减少代码量。
具体来说,原型是一个普通的对象,它拥有一些属性和方法。在创建一个新的对象时,JavaScript 会自动将该对象的原型指向构造函数的原型,形成原型链。
当访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 会沿着原型链一级一级地查找,直到找到该属性或方法为止。如果一直查找到最顶层的 Object.prototype 对象,仍然没有找到该属性或方法,那么返回 undefined。
对原型链的理解,可以理解为一个对象与其他对象之间形成的链式关系。每个对象的原型都指向它的父对象的原型,最终形成一个链条。在访问对象的属性或方法时,如果该对象自身没有该属性或方法,就会一级一级地沿着这个链条向上查找,直到找到该属性或方法或者查找到最顶层的 Object.prototype 对象。
总的来说,原型和原型链是 JavaScript 中的一个核心概念,它们是实现继承和代码复用的重要手段,对于深入理解 JavaScript 的面向对象编程非常重要。

3. for…in 和 for…of 的区别

for…in 和 for…of 都是 JavaScript 中用来遍历对象和数组的循环语句,但它们的遍历方式有所不同。
for…in 循环是用来遍历对象的属性名的,它会遍历对象及其原型链上的所有可枚举属性,包括字符串类型的属性名和 Symbol 类型的属性名,但不包括数组的索引值。
for…of 循环则是用来遍历可迭代对象的元素的,包括数组、字符串、Map、Set、Generator 等,它遍历的是对象的属性值,而不是属性名。对于普通对象来说,它并不是可迭代对象,因此不能使用 for…of 进行遍历。
具体来说,for…in 循环的语法结构如下:

plaintext
1
2
3
for (variable in object) {
// statements
}

其中,variable 是一个变量名,用来代表每个属性名的变量;object 是要遍历的对象。
而 for…of 循环的语法结构如下:

plaintext
1
2
3
for (variable of iterable) {
// statements
}

其中,variable 是一个变量名,用来代表每个元素的变量;iterable 是要遍历的可迭代对象。
总的来说,for…in 循环主要用来遍历对象属性名,for…of 循环主要用来遍历数组、字符串等可迭代对象的元素。在使用这两种循环语句时,需要根据具体的需求来选择合适的遍历方式。

4.如何实现深拷贝?

实现深拷贝的方法有多种,这里介绍几种常用的方法:

递归实现

递归是实现深拷贝最常用的方法。具体实现方法是,遍历需要拷贝的对象的每一个属性,如果属性是一个对象,则递归调用深拷贝方法,直到拷贝完所有的属性。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) {
// 如果 obj 不是对象或为 null,直接返回 obj
return obj;
}
let result;
if (obj instanceof Array) {
result = [];
for (let i = 0; i < obj.length; i++) {
result[i] = deepClone(obj[i]);
}
} else {
result = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
}
return result;
}

JSON.parse()和 JSON.stringify()实现

这种方法的原理是,将对象先转为 JSON 字符串,再将 JSON 字符串转为新的对象。这种方法的缺点是,如果对象中有函数或 undefined 等属性,转换后就会丢失这些信息。

plaintext
1
2
3
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}

Object.assign()实现

这种方法的原理是,使用 Object.assign() 方法将原对象的属性复制到一个新的对象中,达到深拷贝的效果。需要注意的是,Object.assign() 方法只能拷贝对象的可枚举属性,无法拷贝不可枚举属性和原型链上的属性。

plaintext
1
2
3
function deepClone(obj) {
return Object.assign({}, obj);
}

需要根据具体的情况选择合适的实现方法来实现深拷贝。在使用递归方法实现深拷贝时,需要注意循环引用的问题,否则可能会导致死循环。

5.数组的遍历方法有哪些

JavaScript 中数组的遍历方法有以下几种:

for 循环

使用 for 循环可以遍历数组的每一个元素。

plaintext
1
2
3
4
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}

forEach 方法

forEach 方法是数组原型上的一个方法,可以遍历数组的每一个元素并执行指定的操作,无法中途终止循环。

plaintext
1
2
3
4
const arr = [1, 2, 3];
arr.forEach((item) => {
console.log(item);
});

map 方法

map 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并对其进行操作,返回一个新的数组。可以用来对数组进行转换或映射。

plaintext
1
2
3
4
5
const arr = [1, 2, 3];
const newArr = arr.map((item) => {
return item * 2;
});
console.log(newArr); // [2, 4, 6]

filter 方法

filter 方法是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定条件过滤出符合条件的元素,返回一个新的数组。

plaintext
1
2
3
4
5
const arr = [1, 2, 3];
const newArr = arr.filter((item) => {
return item > 1;
});
console.log(newArr); // [2, 3]

reduce 方法

reduce 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定规则累加元素,返回一个累加结果。

plaintext
1
2
3
4
5
const arr = [1, 2, 3];
const sum = arr.reduce((total, item) => {
return total + item;
}, 0);
console.log(sum); // 6

for…in 循环

for…in 循环可以遍历数组的索引和属性名,但不推荐使用,因为可能会遍历到数组的原型链上的属性。

plaintext
1
2
3
4
const arr = [1, 2, 3];
for (let index in arr) {
console.log(arr[index]);
}

这些遍历数组的方法各有特点,需要根据具体情况选择合适的方法来遍历数组。

6.对 this 对象的理解

在 JavaScript 中,this 是一个关键字,用来表示当前对象的引用。
this 的指向会根据函数的调用方式和上下文的不同而发生变化。当一个函数被调用时,this 的指向会动态地绑定到不同的对象上。
具体来说,this 的指向有以下几种情况:

默认绑定

如果函数独立调用,即没有被绑定到任何对象上,那么 this 的指向就是全局对象 window(在浏览器中),或者是 global 对象(在 Node.js 环境中)。

plaintext
1
2
3
4
function test() {
console.log(this); // window(在浏览器中)
}
test();

隐式绑定

如果函数作为对象的方法被调用,那么 this 的指向就是该对象。

plaintext
1
2
3
4
5
6
7
const obj = {
name: 'Alice',
sayHello() {
console.log(`Hello, ${this.name}!`);
},
};
obj.sayHello(); // Hello, Alice!

显式绑定

如果使用 call 或 apply 方法调用函数,或者使用 bind 方法创建一个新的函数时,可以显式地指定 this 的值。

plaintext
1
2
3
4
5
6
7
8
9
10
function test() {
console.log(this.name);
}
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };
test.call(obj1); // Alice
test.apply(obj2); // Bob
const bound = test.bind(obj1);
bound(); // Alice

new 绑定

如果使用 new 关键字调用构造函数,this 的指向就是新创建的对象。

plaintext
1
2
3
4
5
6
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(person.name); // Alice

需要注意的是,箭头函数中的 this 的指向是在定义函数时确定的,而不是在运行时确定的。箭头函数中的 this 指向是外层作用域的 this,无法通过 call、apply、bind 方法来改变其指向。

7.call()和 apply()的区别

call()apply() 是 JavaScript 中的函数方法,它们都可以用来显式地设置函数的执行上下文(即函数中的 this 指向)。
它们的主要区别在于传参的方式不同:

  • call() 方法的参数是一个一个列举出来的,比如 function.call(thisArg, arg1, arg2, arg3, …),它们会依次传入函数中。
  • apply() 方法的第二个参数是一个数组,比如 function.apply(thisArg, [arg1, arg2, arg3, …]),数组中的元素会作为参数传入函数中。
plaintext
1
2
3
4
5
function greeting(name, age) {
console.log(`Hello, my name is ${name} and I'm ${age} years old.`);
}

greeting.call(null, 'Alice', 25); // Hello, my name is Alice and I'm 25 years old.
plaintext
1
2
3
4
5
function greeting(name, age) {
console.log(`Hello, my name is ${name} and I'm ${age} years old.`);
}

greeting.apply(null, ['Alice', 25]); // Hello, my name is Alice and I'm 25 years old.

除了传参方式不同,call()apply() 的作用是相同的,都是用来明确设置函数的执行上下文。在一些特定的场景下,它们可以被用来实现一些非常有用的功能,比如实现继承、修改 this 指向等。

8.双向数据绑定的原理

双向数据绑定是指当数据模型中的数据发生改变时,视图会自动更新;当用户操作视图时,数据模型也会自动更新。在 Vue 中,双向数据绑定是通过数据劫持和发布订阅模式来实现的。
具体来说,Vue 中双向数据绑定的原理如下:

  1. Vue 在初始化时会对数据对象进行递归遍历,使用 Object.defineProperty() 方法将数据对象的属性转换为“响应式对象”,并在内部为每个“响应式对象”创建一个依赖收集器 Dep,用于收集和管理依赖于该“响应式对象”的视图组件。
  2. 当视图组件访问到“响应式对象”的某个属性时,会触发 getter 函数,这个函数会将当前视图组件作为依赖,添加到该属性对应的依赖收集器 Dep 中。
  3. 当“响应式对象”中的属性发生变化时,会触发 setter 函数,这个函数会将新值赋给该属性,并通知该属性对应的依赖收集器 Dep 中的所有视图组件进行更新操作。
  4. 在 Vue 中,实现双向数据绑定还需要结合指令 v-modelv-model 会监听表单元素的 input 事件,并将表单元素的值赋给数据模型中指定的属性,这个过程也是通过数据劫持实现的。
  5. 当数据模型中指定的属性发生变化时,依赖于该属性的所有视图组件都会进行更新操作,并将新的属性值赋给表单元素,从而实现双向数据绑定。

总的来说,双向数据绑定的原理就是通过数据劫持和依赖收集来实现的,当数据模型中的数据发生变化时,会通知依赖于该数据的所有视图组件进行更新操作,而视图组件中的用户操作也会通过数据劫持同步到数据模型中,从而实现数据的双向绑定。

9.对虚拟 DOM 的理解?

虚拟 DOM(Virtual DOM)是一个程序概念,用于描述一个虚拟的、内存中的 DOM 对象,它是由 JavaScript 对象构成的,它将真实的 DOM 树映射到虚拟的 DOM 树上,并通过比较前后两个虚拟 DOM 树的差异,最终只更新真实 DOM 树中需要变化的部分,从而提高性能和效率。
在 Vue 和 React 等现代 Web 框架中,当应用的状态发生变化时,会自动更新虚拟 DOM,然后将新的虚拟 DOM 和旧的虚拟 DOM 进行对比,找出需要更新的部分,并只更新这些部分,从而减少了对实际 DOM 的操作,提高了应用的性能和效率。
虚拟 DOM 的优点包括:

  1. 提高性能:虚拟 DOM 可以减少对实际 DOM 的操作,从而提高性能。
  2. 简化开发:通过使用虚拟 DOM,可以更方便地组织和管理应用的状态,使得应用开发更加简单和高效。
  3. 支持跨平台:虚拟 DOM 可以运行在不同的平台和环境中,从而使得应用可以更容易地适应不同的场景和需求。

虚拟 DOM 的缺点包括:

  1. 存在一定的学习成本:需要开发者熟悉虚拟 DOM 的相关概念和使用方法。
  2. 代码体积较大:使用虚拟 DOM 需要引入额外的代码库,从而增加了应用的代码体积。
  3. 可能会引起一些问题:虚拟 DOM 可能会引起一些问题,比如可能会导致性能问题,或者可能会引起一些不必要的复杂度。

10.如何解决跨越问题

跨域问题指的是在浏览器上,当请求的资源与当前页面所在的域名、端口、协议不一致时,就会发生跨域。由于浏览器的同源策略,跨域请求会被浏览器拦截,导致请求失败。
解决跨域问题的方法有多种,下面列举几种常用的方法:

  1. JSONP(JSON with Padding):JSONP 是一种通过动态创建\