锐明 前端 一面面经
3.7 一面
说说数据类型,引用数据类型的数组
数组常用方法有哪些
数组去重方式
数据类型是编程语言中用来表示数据的种类或类型。在 JavaScript 中,数据类型包括以下几种:
-
基本数据类型(Primitive Data Types): 存储简单数据值的类型。
- 字符串(String)
- 数字(Number)
- 布尔值(Boolean)
- 空(Null)
- 未定义(Undefined)
- 符号(Symbol)(ES6 新增)
-
引用数据类型(Reference Data Types): 存储对象的引用或复杂数据值的类型。
- 对象(Object)
- 数组(Array)
- 函数(Function)
- 日期(Date)
- 正则表达式(RegExp)
- 等等
引用数据类型的数组是存储引用类型数据的容器,可以同时存储多个不同类型的对象或其他数组。数组常用的方法包括:
- push(): 在数组末尾添加一个或多个元素,并返回新数组的长度。
- pop(): 删除并返回数组的最后一个元素。
- shift(): 删除并返回数组的第一个元素。
- unshift(): 在数组开头添加一个或多个元素,并返回新数组的长度。
- concat(): 连接两个或多个数组,并返回新数组。
- slice(): 返回数组的一部分,不修改原数组。
- splice(): 在指定位置添加或删除元素,并返回被删除的元素组成的数组。
- forEach(): 遍历数组的每个元素,并对其执行提供的函数。
- map(): 遍历数组的每个元素,并使用提供的函数创建一个新数组。
- filter(): 使用提供的函数测试数组的每个元素,并创建一个包含所有通过测试的元素的新数组。
- reduce(): 对数组中的每个元素执行一个提供的函数,将其结果汇总为单个值。
数组去重的常用方式包括:
- 使用 Set 数据结构:利用 Set 对象的特性,将数组转换为 Set,然后再转换回数组。
- 使用 filter 方法:遍历数组,通过 filter 方法筛选出不重复的元素。
- 使用 reduce 方法:利用 reduce 方法遍历数组,将不重复的元素添加到新数组中。
- 使用对象属性:遍历数组,利用对象属性的唯一性来进行去重。
- 使用 includes 或 indexOf 方法:遍历数组,利用数组方法检查元素是否已经存在于新数组中。
说说事件循环
说说原型和原型链
事件循环(Event Loop)是 JavaScript 中处理异步操作的机制。它负责处理事件队列中的事件,并将其分发到执行栈中执行。事件循环确保 JavaScript 在单线程环境下能够处理异步操作,而不会阻塞主线程。
事件循环的主要步骤如下:
-
执行同步任务: 首先,事件循环会执行执行栈中的同步任务,直到执行栈为空。
-
处理微任务(Microtask): 接着,事件循环会处理微任务队列中的所有任务,直到微任务队列为空。微任务通常包括 Promise 的回调函数、MutationObserver 和 process.nextTick 等。
-
处理宏任务(Macrotask): 最后,事件循环会处理宏任务队列中的一个任务。宏任务通常包括 setTimeout、setInterval、setImmediate、I/O 操作和 UI 渲染等。处理完一个宏任务后,事件循环会再次处理微任务队列,然后继续处理下一个宏任务,如此循环。
通过这种方式,事件循环确保 JavaScript 在执行异步操作时能够保持响应,并且不会阻塞主线程的执行。
原型和原型链是 JavaScript 中非常重要的概念,用于实现对象之间的继承关系。在 JavaScript 中,每个对象都有一个原型(prototype),原型是一个对象,其他对象可以通过原型继承其中的属性和方法。
当访问一个对象的属性或方法时,JavaScript 引擎会先在对象本身查找,如果找不到,则会沿着原型链向上查找,直到找到属性或方法为止。原型链的顶端是 Object.prototype,它是所有对象的原型。
以下是原型和原型链的主要特点:
-
每个对象都有一个原型(prototype): 原型是一个对象,包含着共享的属性和方法。
-
通过原型链实现继承: JavaScript 中的继承是通过原型链实现的,子对象可以继承父对象的属性和方法。
-
原型链的结构: 每个对象都有一个原型,原型又有自己的原型,形成一个链式结构,最终指向 Object.prototype。
-
原型链的查找: 当访问一个对象的属性或方法时,JavaScript 引擎会先在对象本身查找,如果找不到,则沿着原型链向上查找,直到找到为止。
原型和原型链是 JavaScript 中非常重要的概念,理解它们有助于更好地理解 JavaScript 中的对象和继承机制。
new操作符中干了什么
new 操作符用于创建一个新对象,并将构造函数(constructor)绑定到该对象。当使用 new 操作符调用构造函数时,会执行以下几个步骤:
-
创建一个新对象: 首先,创建一个新的空对象,该对象会成为构造函数的实例。
-
将构造函数的作用域赋给新对象: 在创建新对象后,将构造函数的作用域(即
this关键字)绑定到新对象上,以便在构造函数中使用this来引用新对象。 -
执行构造函数: 在将构造函数的作用域绑定到新对象后,会执行构造函数内部的代码,其中可能会对新对象进行初始化操作。
-
返回新对象: 如果构造函数没有显式地返回一个对象,则
new操作符会隐式地返回新创建的对象。如果构造函数显式地返回一个对象(非原始值),则new操作符会返回该对象而不是新创建的对象。
下面是一个简单的示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
// 使用 new 操作符创建一个 Person 对象
const person1 = new Person('John', 30);
console.log(person1); // { name: 'John', age: 30 }
在上面的示例中,使用 new 操作符调用了 Person 构造函数,并传入了参数 'John' 和 30。new 操作符创建了一个新的空对象,并将构造函数 Person 的作用域绑定到该对象上,然后执行构造函数内部的代码,最后返回新创建的对象 person1。
从url到渲染页面的过程
从 URL 到渲染页面的过程涉及多个步骤,包括 DNS 解析、建立 TCP 连接、发送 HTTP 请求、接收 HTTP 响应、解析 HTML、构建 DOM 树、渲染页面等。以下是从 URL 到渲染页面的简要过程:
-
DNS 解析: 浏览器首先需要将 URL 解析为服务器的 IP 地址,这个过程称为 DNS 解析。浏览器会先检查本地缓存,如果找不到,则向 DNS 服务器发送查询请求,获取服务器的 IP 地址。
-
建立 TCP 连接: 浏览器根据解析得到的服务器 IP 地址,通过 TCP/IP 协议向服务器发起连接请求。TCP 连接的建立包括三次握手,确保客户端和服务器之间建立可靠的通信连接。
-
发送 HTTP 请求: 一旦建立了 TCP 连接,浏览器就会向服务器发送 HTTP 请求。请求中包括请求方法(GET、POST 等)、URL、请求头(User-Agent、Cookie 等)、请求体(POST 请求时),服务器接收到请求后进行处理。
-
接收 HTTP 响应: 服务器接收到请求后,会根据请求内容生成相应的 HTTP 响应,并将响应内容发送回客户端。响应包括响应状态码(200、404 等)、响应头(Content-Type、Cache-Control 等)、响应体(HTML、CSS、JavaScript 等)。
-
解析 HTML: 浏览器接收到 HTTP 响应后,会开始解析 HTML 文件。解析过程包括词法分析和语法分析,将 HTML 文件转换为 DOM(文档对象模型)树。
-
构建 DOM 树: 解析 HTML 文件后,浏览器会根据解析得到的 DOM 元素构建 DOM 树。DOM 树表示 HTML 文档的层次结构,每个 HTML 元素都是树中的一个节点。
-
渲染页面: 构建完 DOM 树后,浏览器会根据 DOM 树和 CSS 样式信息计算出每个节点的渲染样式,并将页面内容渲染到屏幕上。这个过程包括布局(Layout)、绘制(Painting)和合成(Compositing)等步骤。
-
执行 JavaScript: 如果 HTML 中包含了 JavaScript 代码,浏览器会在渲染过程中解析和执行 JavaScript。JavaScript 的执行可能会改变 DOM 结构、样式或页面内容,从而触发重新渲染。
-
完成页面加载: 页面渲染完成后,浏览器会触发
load事件,表示页面加载完成。此时页面已经可以与用户进行交互。
以上是从 URL 到渲染页面的简要过程,其中涉及了多个步骤和技术。
说说缓存
html,css,js解析流程,想让js先与页面解析,后于页面解析该怎么做
要让 JavaScript 在页面解析之前先执行,可以通过将 JavaScript 放置在 HTML 页面中的合适位置或者使用异步加载的方式来实现。
- 将 JavaScript 放置在
<head>标签中: 将 JavaScript 放置在<head>标签中可以确保在页面其他内容加载和解析之前先加载和执行 JavaScript。但是要注意,这样可能会阻塞页面的渲染,因为浏览器会在执行 JavaScript 之前等待 JavaScript 文件的加载和执行完成。
<!DOCTYPE html>
<html>
<head>
<title>页面标题</title>
<script src="your-script.js"></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
- 使用
defer属性: 将 JavaScript 放置在<head>标签中,并使用defer属性可以延迟脚本的执行,使其在页面解析完成后执行。使用defer属性可以确保 JavaScript 在页面解析过程中不会阻塞页面的渲染,但会按照顺序在 DOMContentLoaded 事件触发前依次执行。
<!DOCTYPE html>
<html>
<head>
<title>页面标题</title>
<script src="your-script.js" defer></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
- 使用
async属性: 如果不需要保证 JavaScript 的执行顺序,可以使用async属性将 JavaScript 异步加载,这样 JavaScript 文件会在加载完成后立即执行,不会阻塞页面的解析和渲染。但是要注意,使用async属性加载的 JavaScript 文件执行顺序不确定。
<!DOCTYPE html>
<html>
<head>
<title>页面标题</title>
<script src="your-script.js" async></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
通过上述方式,可以控制 JavaScript 在页面解析之前或之后执行,根据实际需求选择合适的方式。
浏览器发送请求时有没有限制数量,大概是多少,怎么进行优化
浏览器发送请求时通常存在以下限制:
-
并发请求限制: 浏览器对同一域名下的并发请求数量有限制。不同浏览器的限制数量不同,一般为 6 到 8 个并发请求。这意味着当页面中有超过这个数量的资源需要加载时,部分资源会被阻塞,直到前面的请求完成。
-
同一域名下的并发连接限制: 浏览器对同一域名下的并发连接数量也有限制,这个数量通常为 6 到 8 个。这意味着当页面中需要加载大量资源时,即使这些资源来自不同的路径,也会受到同一域名下的并发连接限制。
-
总请求数量限制: 虽然没有硬性规定,但是过多的请求也会对性能产生负面影响。因此,通常建议尽量减少页面中的请求数量,合并和压缩静态资源,使用懒加载和分页加载等技术来优化页面加载性能。
针对以上限制,可以进行如下优化:
-
资源合并和压缩: 将多个小文件合并为一个大文件,并对静态资源进行压缩,减少请求数量和资源大小。
-
使用 CDN: 将静态资源部署到 CDN 上,利用 CDN 的分布式网络提供快速的资源访问速度,减少对同一域名下的并发连接限制的影响。
-
延迟加载和懒加载: 将页面中不必要的资源延迟加载或懒加载,只在需要时再进行加载,减少初始加载时的请求数量。
-
资源预加载: 对于即将需要的资源,可以通过预加载的方式提前加载到缓存中,以减少未来的请求延迟。
-
使用 HTTP/2: HTTP/2 支持多路复用,可以在同一连接上同时发送多个请求,从而减少了并发请求和连接数量的限制。
通过以上优化措施,可以提高页面加载性能,减少页面加载时间,提升用户体验。
css中position的属性
css实现文本溢出…省略
CSS 中的 position 属性
position 属性用于指定元素在文档流中的定位方式。常用的属性值包括:
-
static(默认值): 元素在文档流中的位置由元素在 HTML 中的位置决定,不会被偏移或浮动。
-
relative: 元素在文档流中的位置保持不变,但可以通过
top、right、bottom和left属性进行偏移,偏移不会影响其他元素的布局。 -
absolute: 元素的位置相对于其最近的非 static 定位的祖先元素进行定位,如果不存在这样的祖先元素,则相对于最初的包含块进行定位。元素脱离了文档流,不会影响其他元素的布局。
-
fixed: 元素相对于浏览器窗口进行定位,即使页面滚动,元素也会保持固定的位置。元素脱离了文档流,不会影响其他元素的布局。
-
sticky: 元素先在正常文档流中占据位置,然后在特定的阈值滚动容器(例如,窗口或滚动容器)的视口中变为固定定位。它会在其父元素内垂直滚动时始终保持位置不变,直到达到指定的偏移量。
CSS 实现文本溢出省略
当文本内容超出容器的尺寸时,可以通过 CSS 实现文本溢出省略,常用的属性包括:
-
white-space: 用于控制文本的空白处理方式。
normal:默认值,合并连续的空白字符,并折叠换行符。nowrap:不换行。pre:保留空白字符序列,但合并换行符。pre-wrap:保留空白字符序列,保留换行符。pre-line:合并空白字符序列,保留换行符。
-
overflow: 用于控制元素内容溢出时的处理方式。
visible:默认值,内容不会被修剪,会呈现在元素框之外。hidden:内容会被修剪,超出部分不可见。scroll:显示滚动条,即使内容未溢出。auto:只有当内容溢出时才显示滚动条。
-
text-overflow: 用于控制文本溢出时的处理方式,只能在
overflow属性为hidden或scroll时生效。clip:默认值,内容被修剪,不显示省略号。ellipsis:显示省略号(...)来代表被修剪的文本。
例如,可以通过设置 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 来实现文本内容超出容器时显示省略号。
css实现垂直水平居中
实现元素在父容器中垂直水平居中有多种方法,以下是其中一些常用的方法:
1. 使用 Flexbox(弹性盒子布局)
.parent {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
2. 使用 Grid(网格布局)
.parent {
display: grid;
place-items: center; /* 水平垂直居中 */
}
3. 使用绝对定位和偏移量(知道元素的宽高)
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
4. 使用绝对定位和负边距(不需要知道元素的宽高)
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50%;
margin-left: -50%;
}
5. 使用 Grid 和 Auto Margins(不需要知道元素的宽高)
.parent {
display: grid;
}
.child {
justify-self: center;
align-self: center;
}
6. 使用 Flexbox 和 Auto Margins(不需要知道元素的宽高)
.parent {
display: flex;
}
.child {
margin: auto; /* 水平垂直居中 */
}
7. 使用 table-cell(不推荐,语义不够清晰)
.parent {
display: table-cell;
text-align: center;
vertical-align: middle;
}
以上是一些常用的方法,根据具体情况选择合适的方法来实现元素的垂直水平居中。
vue中的reactive如何实现
Vue.js 中的响应式数据是通过 Object.defineProperty 方法来实现的。Vue 会在初始化时递归地将所有的属性转换为 getter/setter,并且在适当的时候触发更新。
下面是一个简单的示例,演示了如何使用 Object.defineProperty 来实现响应式数据:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get', key);
return val;
},
set: function reactiveSetter(newVal) {
console.log('set', key, newVal);
if (newVal !== val) {
val = newVal;
// 触发更新
// 例如,可以在这里通知 Vue 更新视图
}
}
});
}
const obj = {};
defineReactive(obj, 'foo', 'bar');
console.log(obj.foo); // 输出: 'bar'
obj.foo = 'baz'; // 输出: 'set foo baz'
console.log(obj.foo); // 输出: 'baz'
在上面的示例中,我们定义了一个 defineReactive 函数,用于将对象的属性转换为响应式属性。该函数通过 Object.defineProperty 来定义属性的 getter 和 setter 方法,从而实现对属性的监听和更新。当属性被访问时,会调用 getter 方法,当属性被修改时,会调用 setter 方法,并在 setter 方法中进行新值的更新和触发更新的操作。
在 Vue.js 内部,它会递归地对对象的所有属性进行这样的操作,以实现整个组件树的响应式数据更新。这样,在修改数据时,Vue 就能够监听到数据的变化,并在适当的时候更新视图,实现了数据驱动视图的机制。
vue-router的生命周期
vue中自定义组件
vue组件通信
Vue-Router 的生命周期
Vue Router 是 Vue.js 的官方路由管理器,它提供了在单页面应用中管理路由的能力。Vue Router 中的生命周期钩子与 Vue 组件的生命周期钩子有些类似,但又有一些不同之处。
-
beforeEach(to, from, next):在导航触发之前调用,可以用于全局的路由守卫,用来拦截导航,进行一些权限验证或其他操作。
-
beforeResolve(to, from, next):在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用。
-
afterEach(to, from):导航成功完成后被调用,没有
next函数,不能改变导航。 -
beforeEnter(to, from, next):在路由独享的守卫中(即单个路由配置中)生效,在路由进入该组件前触发。
-
beforeRouteEnter(to, from, next):在路由进入该组件前触发,此时该组件尚未创建,因此无法访问组件实例
this,需要通过next回调函数访问组件实例。 -
beforeRouteUpdate(to, from, next):在当前路由改变,但是该组件被复用时触发,比如从 A 组件跳转到 B 组件,再从 B 组件跳转回到 A 组件。
-
beforeRouteLeave(to, from, next):导航离开该组件的对应路由时触发,可以用来确认是否离开当前页面,常用于防止用户在还未保存修改的情况下离开页面。
-
scrollBehavior(to, from, savedPosition):此函数会在每次导航后被调用,可以用来控制页面滚动行为。
Vue 中自定义组件
在 Vue 中,组件是可复用的 Vue 实例,可以扩展 HTML 元素,封装可重用的代码。自定义组件可以通过全局或局部注册,然后在模板中使用。
- 全局注册自定义组件:
Vue.component('my-component', {
// 组件选项
})
- 局部注册自定义组件:
var MyComponent = {
// 组件选项
}
new Vue({
components: {
'my-component': MyComponent
}
})
自定义组件的选项包括数据、模板、方法、生命周期钩子等,可以根据实际需求定义组件的行为和外观。在模板中使用自定义组件时,可以像使用普通 HTML 元素一样,以标签的形式引入。
Vue 组件通信
在 Vue 中,组件之间的通信主要有以下几种方式:
-
**Props / emit 触发事件,向父组件发送消息。
-
children: 可以通过
$parent和$children直接访问父组件和子组件的实例,但这种方式不够灵活,不推荐使用。 -
事件总线: 可以创建一个空的 Vue 实例作为事件总线,用于组件之间的通信,任何组件都可以通过事件总线来发送和接收事件。
-
Vuex: 对于大型应用或者需要跨组件通信的场景,推荐使用 Vuex 进行状态管理。Vuex 是一个专门为 Vue.js 应用开发的状态管理库,提供了集中式存储管理应用的所有组件的状态。
-
Provide / Inject: 祖先组件通过 provide 来提供数据,后代组件通过 inject 来注入数据,这种方式适用于祖先组件向后代组件传递数据的场景。
以上是 Vue 组件通信的常见方式,根据具体场景和需求选择合适的通信方式。
v-model如何实现
v-model 是 Vue.js 中常用的指令,用于在表单元素(如输入框、复选框、单选框等)和 Vue 实例的数据之间创建双向绑定关系。
在 Vue 中,要使用 v-model 指令,需要满足以下两个条件:
- 表单元素的值需要绑定到 Vue 实例中的一个数据属性。
- 当用户输入时,表单元素的值会自动更新到 Vue 实例的数据属性中;同时,当数据属性的值发生变化时,表单元素的值也会相应地更新。
下面是一个简单的示例,演示了如何使用 v-model:
<div id="app">
<input type="text" v-model="message">
<p>{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: ''
}
})
</script>
在上面的示例中,我们创建了一个 Vue 实例,并将其挂载到了 id 为 app 的 DOM 元素上。在模板中,我们使用 v-model 指令将输入框的值绑定到了 Vue 实例中的 message 数据属性上。这样,当用户在输入框中输入内容时,message 的值会自动更新为输入框中的内容,并且在下方的 <p> 元素中实时显示出来。
总的来说,v-model 实现了表单元素和 Vue 实例数据之间的双向绑定,简化了开发者处理表单输入的逻辑。
ts中interface和type的应用场景,区别是什么
在 TypeScript 中,interface 和 type 都用于定义自定义类型,它们有着一些相似之处,但也有一些区别。
应用场景:
-
interface 的应用场景:
- 定义对象的形状(Shape):
interface主要用于描述对象的结构,包括属性名、属性类型以及方法。 - 对象的扩展:
interface可以被继承和扩展,用于组织和管理代码中的对象结构。
- 定义对象的形状(Shape):
-
type 的应用场景:
- 定义联合类型、交叉类型、元组和其他更复杂的类型。
- 创建类型别名:
type关键字可以用来创建自定义类型别名,方便重复使用复杂的类型。 - 限制允许的值:
type可以用于创建字符串字面量类型、联合类型和交叉类型等,用于限制变量的可能取值。
区别:
-
语法不同:
interface使用interface关键字进行定义,通常用于描述对象的形状。type使用type关键字进行定义,通常用于创建自定义类型别名。
-
可被扩展:
interface可以被继承和扩展,允许在已有接口基础上添加新成员。type不支持继承,但是可以通过联合类型、交叉类型等方式实现相似的功能。
-
兼容性不同:
- 对于扩展方面,
interface之间可以相互合并,而type不能合并。 - 对于描述对象的形状,
interface在检查属性是否兼容时更加严格,而type更加灵活。
- 对于扩展方面,
-
适用场景不同:
- 如果需要描述对象的形状,特别是在定义类或者面向对象编程时,更倾向于使用
interface。 - 如果需要创建复杂的类型别名,或者对现有类型进行扩展和限制,更倾向于使用
type。
- 如果需要描述对象的形状,特别是在定义类或者面向对象编程时,更倾向于使用
总的来说,interface 和 type 在功能上有一些重叠,但也存在一些不同之处,根据具体情况选择合适的工具更有利于代码的编写和维护。
ts中一个接口有a,b,c三个属性,定义一个新类,要求只有a,b两个属性
在 TypeScript 中,你可以使用接口继承来实现这样的需求。首先定义一个拥有 a、b、c 三个属性的接口,然后定义一个新类,只包含 a 和 b 两个属性,同时继承原始接口。
下面是示例代码:
// 定义原始接口
interface OriginalInterface {
a: number;
b: string;
c: boolean;
}
// 定义新类,只包含 a 和 b 属性,并继承原始接口
class NewClass implements Pick<OriginalInterface, 'a' | 'b'> {
constructor(public a: number, public b: string) {}
}
// 创建新类实例
const newObj = new NewClass(1, 'hello');
// 输出新类实例的属性
console.log(newObj.a); // 1
console.log(newObj.b); // "hello"
在上面的示例中,我们使用了 TypeScript 提供的 Pick 泛型工具类型,将原始接口中的 a 和 b 属性挑选出来,然后新类实现了这个挑选出来的子集。这样就实现了新类只包含 a 和 b 两个属性的需求。
两个非技术问题:怎么学习,遇到问题如何排查
最后没有反问,感觉没戏
作者:KeyError44
链接:www.nowcoder.com/feed/main/d…
来源:牛客网