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

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

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

1
2
3
.clearfix {
overflow: auto;
}

使用空标签清除浮动:

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

使用伪元素清除浮动:

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 循环的语法结构如下:

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

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

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

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

4.如何实现深拷贝?

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

递归实现

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

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 等属性,转换后就会丢失这些信息。

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

Object.assign()实现

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

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

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

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

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

for 循环

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

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

forEach 方法

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

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

map 方法

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

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 方法是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定条件过滤出符合条件的元素,返回一个新的数组。

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 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定规则累加元素,返回一个累加结果。

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 循环可以遍历数组的索引和属性名,但不推荐使用,因为可能会遍历到数组的原型链上的属性。

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 环境中)。

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

隐式绑定

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

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 的值。

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 的指向就是新创建的对象。

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, …]),数组中的元素会作为参数传入函数中。
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.
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 是一种通过动态创建\标签的方式实现跨域的技术。通过指定回调函数的名称,将要请求的数据作为参数传递给回调函数,服务器返回一段 JS 代码,浏览器执行这段 JS 代码,从而实现跨域。
  2. CORS(Cross-Origin Resource Sharing):CORS 是一种由 W3C 标准化的跨域解决方案,它需要服务器设置 Access-Control-Allow-Origin 等响应头来允许跨域请求。在浏览器端,如果发现请求的资源与当前页面所在的域名、端口、协议不一致,会自动发起一次预检请求 OPTIONS,询问服务器是否允许该请求。
  3. 代理:在同域下通过代理服务器来实现跨域请求。即在服务器端设置代理服务器,在代理服务器上进行跨域请求,并将请求结果返回给前端。
  4. WebSocket:通过 WebSocket 协议实现跨域通信。WebSocket 是一种全双工通信协议,与 HTTP 协议不同的是,它是建立在 TCP 连接上的协议,可以实现客户端和服务器之间的长连接。
  5. postMessage:通过 HTML5 提供的 postMessage 方法实现跨域通信。该方法可以实现跨窗口、跨域通信,可以在不同的窗口、甚至不同的域之间传递数据。

11.常见的状态码

常见的状态码包括:

  • 200 OK:请求成功。
  • 201 Created:已成功创建资源。
  • 204 No Content:请求已成功处理,但响应报文不含实体的主体部分。
  • 301 Moved Permanently:永久性重定向。
  • 302 Found:临时性重定向。
  • 304 Not Modified:服务端资源未改变,可使用客户端缓存。
  • 400 Bad Request:请求报文存在语法错误。
  • 401 Unauthorized:未经授权,需要身份认证。
  • 403 Forbidden:服务器拒绝该请求。
  • 404 Not Found:请求的资源不存在。
  • 500 Internal Server Error:服务器内部错误,无法完成请求。
  • 503 Service Unavailable:服务器当前无法处理请求,一段时间后可能恢复正常。

每种状态码代表了不同的含义,客户端可根据不同的状态码进行不同的处理。

12.对节流与防抖的理解

节流和防抖都是为了解决频繁触发某些事件而产生的性能问题。
防抖:在事件触发 n 秒后再执行回调,如果在这 n 秒内又触发了该事件,则重新计时,直到 n 秒后执行回调。常用于输入框输入验证、搜索框查询等场景。
节流:每隔一段时间执行一次事件回调,即在固定的时间间隔内只执行一次。常用于页面滚动、窗口大小改变等频繁触发的事件。
比如在监听页面滚动时,若每次滚动都触发事件回调函数,会造成性能问题,而使用节流技术可以在滚动过程中一定时间内只执行一次事件回调,有效地降低了频繁触发事件的性能问题。
在实现防抖和节流时,可以通过设置定时器来实现。在防抖中,每次触发事件时都先清除之前的定时器,再重新设置定时器;而在节流中,每次触发事件时若定时器已存在,则不再重新设置,直到定时器回调后再清除定时器。
以下是节流和防抖的代码实现:
节流(throttle)的实现:

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const nowTime = new Date().getTime();
if (nowTime - lastTime > delay) {
fn.apply(this, args);
lastTime = nowTime;
}
};
}

防抖(debounce)的实现:

1
2
3
4
5
6
7
8
9
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

其中,fn 是要执行的函数,delay 是时间间隔,单位是毫秒。节流和防抖都可以使用在事件频繁触发的场景中,比如滚动、输入框输入等。节流是限制事件的执行频率,而防抖是限制事件的触发频率,具体要选择哪一种方式,要根据具体情况而定。

13.Vue 项目优化思路

Vue 项目优化是一个比较复杂的话题,以下是一些常见的优化思路:

  1. 减少 HTTP 请求:合并 CSS 和 JS 文件,使用雪碧图等方式减少图片请求等。
  2. 路由懒加载:将页面的不同部分拆分成不同的代码块,只有在需要时才加载,以减少初始加载时间。
  3. 使用 CDN 加速:将静态资源存放在 CDN 上,加速资源访问。
  4. 使用异步组件:将组件按需加载,减少首屏加载时间。
  5. 使用 Keep-alive 缓存组件:使用缓存机制,避免重复渲染和请求数据。
  6. 使用 Element 等 UI 框架:使用现成的 UI 框架,避免自己编写大量 CSS 代码。
  7. 图片压缩:使用压缩工具对图片进行压缩,减小图片大小。
  8. 代码压缩:使用工具对 JS 和 CSS 代码进行压缩,减小文件大小。
  9. 懒加载图片:将图片延迟加载,当用户需要查看图片时再进行加载,减小初始加载时间。
  10. 使用 Webpack 优化:使用 Webpack 的优化功能,如使用 webpack-bundle-analyzer 来分析包大小、使用 tree shaking 减少无用代码等。

以上是一些常见的 Vue 项目优化思路,具体优化方式要根据具体情况而定。

14.性能优化的方式有哪些

以下是一些常见的性能优化方式:

  1. 减少 HTTP 请求:将多个文件合并为一个文件,压缩文件,使用 CDN 等方式来减少请求次数,从而提高页面加载速度。
  2. 图片优化:使用图片压缩工具,减少图片文件大小,使用图片懒加载,按需加载图片,优化图片格式等方式来提高页面加载速度。
  3. 减少 DOM 操作:DOM 操作是非常消耗性能的操作,应该尽量避免。可以使用批量操作 DOM 的方式,避免频繁操作 DOM。
  4. 使用缓存:使用浏览器缓存、服务器缓存等方式,避免重复请求数据,从而提高页面加载速度。
  5. 代码优化:避免使用全局变量,减少闭包嵌套,使用事件委托,避免不必要的计算等方式,提高 JavaScript 的执行效率。
  6. 使用 Webpack 等构建工具进行优化:使用 Webpack 可以对代码进行压缩、混淆、按需加载等处理,从而提高页面的加载速度。
  7. 服务器优化:对服务器进行优化,包括优化服务器硬件、增加带宽、使用缓存、使用负载均衡等方式,提高页面响应速度。
  8. 数据库优化:对数据库进行优化,包括优化数据库表结构、使用索引、避免频繁连接数据库等方式,提高数据读取速度。

以上是一些常见的性能优化方式,具体要根据实际情况进行选择和实现。

15.谈一下你对前端开发工程师这个职位的理解

作为一个前端开发工程师,其主要职责是负责网站或应用的前端设计和开发,实现用户界面的交互和效果,提升用户体验。具体的职责包括但不限于:

  1. 实现网站或应用的前端设计和开发,编写 HTML、CSS 和 JavaScript 等前端代码;
  2. 与 UI 设计师和产品经理协作,制定网站或应用的交互和效果方案;
  3. 与后端开发人员协作,完成前后端的数据交互和接口对接;
  4. 负责网站或应用的性能优化和调试工作,保证用户体验;
  5. 熟悉前端开发的最新技术和工具,保持学习和更新。

除此之外,前端开发工程师还需要具备良好的沟通能力和团队协作能力,能够与其他团队成员有效地沟通和协作,推动项目的进展和完成。同时,前端开发工程师还需要具备较强的问题解决能力和创新意识,能够独立思考和解决问题,提出创新性的解决方案,为产品提供更好的用户体验。