2023-03-07面试题
1.为什么需要清除浮动,写出清除浮动的方式
浮动元素在文档流中脱离了正常的位置,因此会导致父元素高度塌陷,影响布局的正确性和美观性。所以通常需要清除浮动。
以下是常见的清除浮动的方式:
父元素设置 overflow: auto 或 overflow: hidden 属性:
1 | .clearfix { |
使用空标签清除浮动:
1 | <div class="parent"> |
1 | .clear { |
使用伪元素清除浮动:
1 | .clearfix::after { |
在父元素上添加一个伪元素,设置 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 | for (variable in object) { |
其中,variable 是一个变量名,用来代表每个属性名的变量;object 是要遍历的对象。
而 for…of 循环的语法结构如下:
1 | for (variable of iterable) { |
其中,variable 是一个变量名,用来代表每个元素的变量;iterable 是要遍历的可迭代对象。
总的来说,for…in 循环主要用来遍历对象属性名,for…of 循环主要用来遍历数组、字符串等可迭代对象的元素。在使用这两种循环语句时,需要根据具体的需求来选择合适的遍历方式。
4.如何实现深拷贝?
递归实现
递归是实现深拷贝最常用的方法。具体实现方法是,遍历需要拷贝的对象的每一个属性,如果属性是一个对象,则递归调用深拷贝方法,直到拷贝完所有的属性。
1 | function deepClone(obj) { |
JSON.parse()和 JSON.stringify()实现
这种方法的原理是,将对象先转为 JSON 字符串,再将 JSON 字符串转为新的对象。这种方法的缺点是,如果对象中有函数或 undefined 等属性,转换后就会丢失这些信息。
1 | function deepClone(obj) { |
Object.assign()实现
这种方法的原理是,使用 Object.assign() 方法将原对象的属性复制到一个新的对象中,达到深拷贝的效果。需要注意的是,Object.assign() 方法只能拷贝对象的可枚举属性,无法拷贝不可枚举属性和原型链上的属性。
1 | function deepClone(obj) { |
需要根据具体的情况选择合适的实现方法来实现深拷贝。在使用递归方法实现深拷贝时,需要注意循环引用的问题,否则可能会导致死循环。
5.数组的遍历方法有哪些
for 循环
使用 for 循环可以遍历数组的每一个元素。
1 | const arr = [1, 2, 3]; |
forEach 方法
forEach 方法是数组原型上的一个方法,可以遍历数组的每一个元素并执行指定的操作,无法中途终止循环。
1 | const arr = [1, 2, 3]; |
map 方法
map 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并对其进行操作,返回一个新的数组。可以用来对数组进行转换或映射。
1 | const arr = [1, 2, 3]; |
filter 方法
filter 方法是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定条件过滤出符合条件的元素,返回一个新的数组。
1 | const arr = [1, 2, 3]; |
reduce 方法
reduce 方法也是数组原型上的一个方法,可以遍历数组的每一个元素并根据指定规则累加元素,返回一个累加结果。
1 | const arr = [1, 2, 3]; |
for…in 循环
for…in 循环可以遍历数组的索引和属性名,但不推荐使用,因为可能会遍历到数组的原型链上的属性。
1 | const arr = [1, 2, 3]; |
这些遍历数组的方法各有特点,需要根据具体情况选择合适的方法来遍历数组。
6.对 this 对象的理解
在 JavaScript 中,this 是一个关键字,用来表示当前对象的引用。
this 的指向会根据函数的调用方式和上下文的不同而发生变化。当一个函数被调用时,this 的指向会动态地绑定到不同的对象上。
具体来说,this 的指向有以下几种情况:
默认绑定
如果函数独立调用,即没有被绑定到任何对象上,那么 this 的指向就是全局对象 window(在浏览器中),或者是 global 对象(在 Node.js 环境中)。
1 | function test() { |
隐式绑定
如果函数作为对象的方法被调用,那么 this 的指向就是该对象。
1 | const obj = { |
显式绑定
如果使用 call 或 apply 方法调用函数,或者使用 bind 方法创建一个新的函数时,可以显式地指定 this 的值。
1 | function test() { |
new 绑定
如果使用 new 关键字调用构造函数,this 的指向就是新创建的对象。
1 | function Person(name) { |
需要注意的是,箭头函数中的 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 | function greeting(name, age) { |
1 | function greeting(name, age) { |
除了传参方式不同,call() 和 apply() 的作用是相同的,都是用来明确设置函数的执行上下文。在一些特定的场景下,它们可以被用来实现一些非常有用的功能,比如实现继承、修改 this 指向等。
8.双向数据绑定的原理
双向数据绑定是指当数据模型中的数据发生改变时,视图会自动更新;当用户操作视图时,数据模型也会自动更新。在 Vue 中,双向数据绑定是通过数据劫持和发布订阅模式来实现的。
具体来说,Vue 中双向数据绑定的原理如下:
- Vue 在初始化时会对数据对象进行递归遍历,使用 Object.defineProperty() 方法将数据对象的属性转换为“响应式对象”,并在内部为每个“响应式对象”创建一个依赖收集器 Dep,用于收集和管理依赖于该“响应式对象”的视图组件。
- 当视图组件访问到“响应式对象”的某个属性时,会触发 getter 函数,这个函数会将当前视图组件作为依赖,添加到该属性对应的依赖收集器 Dep 中。
- 当“响应式对象”中的属性发生变化时,会触发 setter 函数,这个函数会将新值赋给该属性,并通知该属性对应的依赖收集器 Dep 中的所有视图组件进行更新操作。
- 在 Vue 中,实现双向数据绑定还需要结合指令 v-model,v-model 会监听表单元素的 input 事件,并将表单元素的值赋给数据模型中指定的属性,这个过程也是通过数据劫持实现的。
- 当数据模型中指定的属性发生变化时,依赖于该属性的所有视图组件都会进行更新操作,并将新的属性值赋给表单元素,从而实现双向数据绑定。
总的来说,双向数据绑定的原理就是通过数据劫持和依赖收集来实现的,当数据模型中的数据发生变化时,会通知依赖于该数据的所有视图组件进行更新操作,而视图组件中的用户操作也会通过数据劫持同步到数据模型中,从而实现数据的双向绑定。
9.对虚拟 DOM 的理解?
虚拟 DOM(Virtual DOM)是一个程序概念,用于描述一个虚拟的、内存中的 DOM 对象,它是由 JavaScript 对象构成的,它将真实的 DOM 树映射到虚拟的 DOM 树上,并通过比较前后两个虚拟 DOM 树的差异,最终只更新真实 DOM 树中需要变化的部分,从而提高性能和效率。
在 Vue 和 React 等现代 Web 框架中,当应用的状态发生变化时,会自动更新虚拟 DOM,然后将新的虚拟 DOM 和旧的虚拟 DOM 进行对比,找出需要更新的部分,并只更新这些部分,从而减少了对实际 DOM 的操作,提高了应用的性能和效率。
虚拟 DOM 的优点包括:
- 提高性能:虚拟 DOM 可以减少对实际 DOM 的操作,从而提高性能。
- 简化开发:通过使用虚拟 DOM,可以更方便地组织和管理应用的状态,使得应用开发更加简单和高效。
- 支持跨平台:虚拟 DOM 可以运行在不同的平台和环境中,从而使得应用可以更容易地适应不同的场景和需求。
虚拟 DOM 的缺点包括:
- 存在一定的学习成本:需要开发者熟悉虚拟 DOM 的相关概念和使用方法。
- 代码体积较大:使用虚拟 DOM 需要引入额外的代码库,从而增加了应用的代码体积。
- 可能会引起一些问题:虚拟 DOM 可能会引起一些问题,比如可能会导致性能问题,或者可能会引起一些不必要的复杂度。
10.如何解决跨越问题
跨域问题指的是在浏览器上,当请求的资源与当前页面所在的域名、端口、协议不一致时,就会发生跨域。由于浏览器的同源策略,跨域请求会被浏览器拦截,导致请求失败。
解决跨域问题的方法有多种,下面列举几种常用的方法:
- JSONP(JSON with Padding):JSONP 是一种通过动态创建\
























