常面常新-1021
问题汇总
上午场:
position
有几种Css
盒子模型v-for
为什么要有key
- 什么是闭包
- 父子组件传参
Vuex
有什么Vue
底层双向绑定逻辑Vue
生命周期钩子- 前端性能优化有什么
下午场:
box-size
- 怪异盒子模型的长宽是怎么计算的
- 闭包
position
有几种- 垂直居中的方式(只让说了绝对定位和
flex
) js
的数据类型有哪些typeof
、instanceof
- 深拷贝、浅拷贝(深拷贝实现方式)
- 变量提升、暂时性死区
call
、apply
、bind
区别map
、filter
、reduce
区别new
的过程Vue
底层双向绑定逻辑- 两道代码考察
项目相关:
- 大文件上传是怎么做到的
- 请求合并是怎么做到的
上午场
Css 盒子模型
CSS
盒子模型(Box Model)
是网页设计和布局的基础概念之一。它描述了一个元素在网页中所占据的空间,包括内容、内边距(padding)
、边框(border)
和外边距(margin)
。理解盒子模型对于正确地布局和样式化网页元素至关重要。
盒子模型的计算
默认情况下,元素的总宽度和总高度计算如下:
- 总宽度 = 内容宽度 + 左右内边距 + 左右边框 + 左右外边距
- 总高度 = 内容高度 + 上下内边距 + 上下边框 + 上下外边距
v-for
为什么要有key
高效的
DOM
更新:Vue.js
使用虚拟DOM
来追踪和更新实际DOM
。当列表数据发生变化时,Vue.js
需要确定哪些元素发生了变化、哪些元素需要被添加或删除。key
属性帮助Vue.js
更高效地识别每个列表项,从而进行最小化的DOM
更新操作。如果没有key
,Vue.js
会使用默认的就地复用策略,可能会导致不必要的DOM
操作和渲染错误。
保持组件状态:
- 当列表项是组件时,
key
属性可以确保组件在更新时保持其内部状态。如果没有key
,组件可能会被错误地复用,导致状态丢失或混乱。
- 当列表项是组件时,
避免渲染错误:
- 在某些情况下,缺少
key
可能会导致渲染错误或性能问题。使用唯一的key
可以确保每个列表项在更新时都能正确地被识别和处理。
- 在某些情况下,缺少
什么是闭包
闭包(Closure)
是JavaScript
中的一个重要概念,它允许函数访问其词法作用域(Lexical Scope)
中的变量,即使这个函数在其词法作用域之外执行。闭包使得函数可以“记住”并访问定义它们时的环境。
定义
闭包是指那些能够访问自由变量的函数。自由变量是指在函数中使用的,但既不是函数参数也不是函数内部声明的变量。
特性
函数嵌套:闭包通常是通过在一个函数内部定义另一个函数来创建的。
词法作用域:闭包可以访问其词法作用域中的变量,即使在其外部执行时也是如此。
持久化变量:闭包可以使得函数内部的局部变量在函数执行完毕后依然存在。
下午场
box-size
box-sizing
是CSS
中的一个属性,用于定义元素的盒子模型的计算方式。它有两个常用值:content-box
和 border-box
。
content-box
(默认值):
仅内容区域的宽度和高度由width
和height
属性设置,内边距和边框不包括在内。 这是W3C
标准盒子模型的默认行为。
border-box
:
内容区域、内边距和边框的总宽度和总高度由width
和height
属性设置,内边距和边框包括在内。 这种计算方式更符合直觉,特别是在处理复杂布局时。
怪异盒子模型的长宽是怎么计算的
在怪异盒子模型(即 box-sizing: border-box
)中,元素的宽度和高度包括内容、内边距和边框。具体计算方式如下:
总宽度 = width
(包括内容宽度、内边距和边框) 总高度 = height
(包括内容高度、内边距和边框)
js
的数据类型有哪些
基本数据类型
number
、string
、boolean
、undefined
、null
、symbol
、bigint
引用数据类型
object
typeof、instanceof
typeof
typeof
运算符可以用来判断基本数据类型和函数类型,但对于null
和数组等引用类型的判断有局限性。jsconsole.log(typeof 42); // 输出: "number" console.log(typeof "hello"); // 输出: "string" console.log(typeof true); // 输出: "boolean" console.log(typeof undefined); // 输出: "undefined" console.log(typeof null); // 输出: "object" (这是一个历史遗留的 bug) console.log(typeof {}); // 输出: "object" console.log(typeof []); // 输出: "object" console.log(typeof function () {}); // 输出: "function" console.log(typeof Symbol("sym")); // 输出: "symbol" console.log(typeof 123n); // 输出: "bigint"
instanceof
instanceof
运算符可以用来判断对象是否是某个构造函数的实例。jsconsole.log([] instanceof Array); // 输出: true console.log({} instanceof Object); // 输出: true console.log(function () {} instanceof Function); // 输出: true console.log(new Date() instanceof Date); // 输出: true
instanceof
底层实现
获取构造函数的
prototype
属性。获取对象的原型链。
沿着对象的原型链向上查找,看是否能找到与构造函数的
prototype
属性相同的对象。如果找到,返回
true
;否则,返回false
。
map、filter、reduce 区别
map
方法用于创建一个新数组,其结果是原数组中的每个元素调用一个提供的函数后的返回值。filter
方法用于创建一个新数组,其中包含所有通过提供函数实现的测试的元素。reduce
方法对数组中的每个元素执行一个提供的函数(从左到右),将其结果汇总为单个返回值。
方法 | 返回值类型 | 作用 | 是否改变原数组 |
---|---|---|---|
map | 新数组 | 对数组中的每个元素执行提供的函数,并返回结果组成的新数组 | 否 |
filter | 新数组 | 对数组中的每个元素执行提供的函数,返回值为 true 的元素组成的新数组 | 否 |
reduce | 单一值 | 对数组中的每个元素执行提供的函数,并将其结果汇总为单个返回值 | 否 |
new 的过程
创建一个空对象:创建一个新的空对象,并将其
**proto**
属性设置为构造函数的prototype
属性。绑定 this:将构造函数的作用域赋给新对象(即
this
指向这个新对象)。执行构造函数:执行构造函数中的代码,为新对象添加属性和方法。
返回新对象:如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象。
- 手动实现
new
操作符
function myNew(constructor, ...args) {
// 创建一个空对象,并将其 __proto__ 属性设置为构造函数的 prototype 属性
const obj = Object.create(constructor.prototype);
// 将构造函数的作用域赋给新对象,并执行构造函数
const result = constructor.apply(obj, args);
// 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象
return result instanceof Object ? result : obj;
}
// 测试
const bob = myNew(Person, "Bob", 25);
console.log(bob); // 输出: Person { name: 'Bob', age: 25 }
闭包代码考察
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
var list = document.querySelectorAll("li");
for (var i = 0; i < list.length; i++) {
list[i].onclick = function () {
console.log(i); //点击每个列表项的时候,控制台会输出 6
};
}
</script>
解决方法一:使用闭包
需要创建一个新的作用域,这样每次迭代的
i
值才能被独立地保存
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
var list = document.querySelectorAll("li");
for (var i = 0; i < list.length; i++) {
(function (index) {
list[index].onclick = function () {
console.log(index);
};
})(i);
}
</script>
- 解决方法二:使用
let
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
var list = document.querySelectorAll("li");
for (let i = 0; i < list.length; i++) {
list[i].onclick = function () {
console.log(i);
};
}
</script>
变量提升、暂时性死区
变量提升(Hoisting)
变量提升 是JavaScript
中的一个行为,它将变量和函数声明提升到其作用域的顶部。变量提升意味着在代码执行之前,变量和函数声明已经在内存中被分配了空间
变量提升的特点
变量声明提升:变量声明会被提升到其作用域的顶部,但变量赋值不会被提升。
函数声明提升:函数声明会被提升到其作用域的顶部,并且整个函数体都会被提升。
暂时性死区
暂时性死区,是指在使用let
和const
声明变量时,从变量进入作用域到变量声明之间的这段时间。在这段时间内,访问这些变量会导致引用错误(ReferenceError)
。
暂时性死区的特点
- 块级作用域:
let
和const
声明的变量具有块级作用域。 - 在声明之前不可访问:在变量声明之前访问它们会导致引用错误。
- 块级作用域:
代码考察
function test(m) {
// 函数的参数按值传递
// m -> {k: 30}
m = { v: 5 }; // m 重写了, 不再跟外层的var m指向同一个地址
// m -> {v: 5}
}
var m = { k: 30 };
test(m);
alert(m.v); // undefined m -> {k:30}
//---下面为百度的另一个题---//
function test(m) {
// 函数的参数按值传递
// m -> {k: 30}
m.q = 6;
// m -> {k: 30, q: 6}
}
var m = { k: 30 };
test(m);
alert(m.q); //6
Vue
底层双向绑定逻辑
响应式系统
(Reactivity System)
:Vue
使用了一个高效的响应式系统来追踪数据的变化。在Vue 2.x
中,这个系统依赖于Object.defineProperty
来劫持数据对象的属性访问器。当数据变化时,Vue
会自动更新视图。在Vue 3.x
中,响应式系统得到了重写,使用了基于 Proxy 的更通用和强大的方法。虚拟
DOM(Virtual DOM)
:Vue
维护了一个虚拟DOM
树,它是一个轻量级的JavaScript
对象,代表了真实DOM
树的状态。当数据变化时,Vue
会创建一个新的虚拟DOM
树,并与旧的虚拟DOM
树进行比较(这个比较过程称为Diffing
)。Vue
会计算出最小的更新操作来更新真实DOM
,从而提高性能。数据劫持
(Data Hijacking)
: 在Vue 2.x
中,Vue
实例被创建时,它会递归遍历数据对象的所有属性,并使用Object.defineProperty
来劫持这些属性的getter
和setter
。这样,每当数据变化时,Vue
都能检测到变化并触发视图更新。依赖收集
(Dependency Collection)
: 当组件被渲染时,Vue
会记录哪些数据被用到了(即依赖收集)。这些信息被存储在一个依赖列表中。当数据变化时,只有依赖于这些数据的组件才会重新渲染。异步更新队列
(Async Update Queue)
:Vue
将数据变化的更新操作放入一个异步队列中,然后批量执行。这样可以避免不必要的重复渲染,提高性能。