DIFF算法的原理
DIFF算法用于比较两棵虚拟DOM树的差异并更新真实DOM,其基本原理如下:
-
分层比较:
DIFF算法假设不同层级的节点不会互相影响,因此只比较同一层级的节点,避免全树比较的高成本。 -
三种操作:
DIFF算法根据新旧虚拟DOM的差异,生成三种操作:创建节点、删除节点和更新节点。 -
同层比较优化:
- 标记相同节点: 通过节点的
key
属性标记节点身份,提升对比效率。 - 按顺序对比: 比较新旧子节点列表,记录增删改操作。
- 标记相同节点: 通过节点的
-
类型对比:
- 类型相同:递归对比子节点和属性。
- 类型不同:直接替换节点及其子树。
-
局部更新:
只更新有差异的部分,避免大规模的 DOM 重排和重绘,提升性能。
Vue 自定义事件中父组件如何接收子组件传递的多个参数
-
子组件触发事件:
子组件通过$emit
方法触发事件并传递多个参数:this.$emit('custom-event', param1, param2);
-
父组件监听事件:
父组件通过v-on
(或简写@
)监听子组件的事件:<child-component @custom-event="handleEvent"></child-component>
-
方法接收参数:
父组件的事件处理方法可以接收子组件传递的多个参数:methods: { handleEvent(param1, param2) { console.log(param1, param2); }, },
Vue Router 全局路由守卫
概念和用法
路由守卫是 Vue Router 提供的拦截导航的机制,用于在导航进入、离开某个页面时执行特定逻辑。
-
全局守卫:
beforeEach
: 在每次导航前执行,可用于权限校验。beforeResolve
: 在导航被确认之前(但在组件内守卫和异步路由组件解析之后)执行。afterEach
: 在每次导航完成后执行,用于清理或日志记录。
-
局部守卫:
- 在路由配置中使用
beforeEnter
。
- 在路由配置中使用
-
组件内守卫:
beforeRouteEnter
,beforeRouteUpdate
,beforeRouteLeave
。
实际应用场景
-
权限校验:
根据用户角色或权限,决定是否允许访问特定页面:router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isAuthenticated()) { next('/login'); } else { next(); } });
-
加载动画:
在路由跳转时展示加载动画:router.beforeEach((to, from, next) => { showLoading(); next(); }); router.afterEach(() => { hideLoading(); });
-
数据预加载:
在导航到目标路由前预加载数据:router.beforeResolve(async (to, from, next) => { try { await fetchData(to.params.id); next(); } catch (error) { next('/error'); } });
解决页面请求接口大规模并发问题
-
限流(Throttle):
控制请求的并发量,分批次发送请求:const limit = 5; // 每批最大并发请求数 const queue = []; // 请求队列 const results = []; function sendRequest(requests) { if (requests.length === 0) return Promise.resolve(); return Promise.all(requests.splice(0, limit).map(req => req())) .then(res => { results.push(...res); return sendRequest(requests); }); } sendRequest(queue).then(() => { console.log('All requests completed', results); });
-
去重和缓存:
避免重复请求,缓存已有结果:const cache = new Map(); function fetchWithCache(url) { if (cache.has(url)) return Promise.resolve(cache.get(url)); return fetch(url).then(response => { cache.set(url, response); return response; }); }
-
延迟重试:
对失败的请求增加延迟重试机制:function retryRequest(requestFn, retries = 3, delay = 1000) { return new Promise((resolve, reject) => { function attempt(n) { requestFn() .then(resolve) .catch(err => { if (n === 0) reject(err); else setTimeout(() => attempt(n - 1), delay); }); } attempt(retries); }); }
-
服务端聚合:
通过服务端接口合并请求,减少前端并发数量。 -
使用 Web Workers:
将并发处理逻辑移到 Web Workers 中,避免阻塞主线程。
通过以上方法,可以有效缓解大规模并发对接口和前端性能的压力。
什么是 Vue 中的 slot?它有什么作用?
Vue.js 中的 slot(插槽)是 Vue 组件系统中一个非常重要的概念,它提供了一种灵活的方式来分发内容到组件的模板中。插槽使得组件更加可重用和灵活,能够根据不同的使用场景来定制组件的显示内容。具体来说,插槽允许你在子组件的模板中预留一些占位符,然后在父组件中使用这个子组件时,向这些占位符中填充内容。这样,父组件就可以向子组件传递 HTML 结构,从而实现更复杂的布局和交互。
在 Vue 渲染模板时,如何保留模板中的 HTML 注释?
在 Vue 渲染模板时,通常 HTML 注释会被视为模板的一部分,但不会被渲染到最终的 DOM 中。Vue 的模板编译器会处理模板中的指令和表达式,并生成相应的渲染函数,但注释通常会被忽略。因此,在 Vue 渲染模板时,无法直接保留模板中的 HTML 注释。
如果要实现一个 Vue3 的弹窗组件,你会如何设计?
要实现一个 Vue3 的弹窗组件,可以考虑以下设计思路:
- 组件结构:设计一个弹窗组件,包含标题、内容、按钮等部分。
- 状态管理:使用 Vue 的响应式状态管理来控制弹窗的显示和隐藏。
- 插槽:利用插槽机制,允许父组件向弹窗组件传递自定义内容。
- 样式:为弹窗组件添加适当的样式,包括背景、边框、阴影等。
- 动画:使用 Vue 的过渡和动画功能,为弹窗的显示和隐藏添加动画效果。
- 事件处理:为弹窗的按钮等交互元素添加事件处理函数,实现弹窗的关闭、确认等操作。
Vue 的 v-cloak 和 v-pre 指令有什么作用?
- v-cloak:v-cloak 指令用于在 Vue 实例加载完成之前隐藏元素或组件。它的作用是确保在 Vue 实例加载完成之前,元素或组件不会显示在屏幕上,从而消除了页面闪烁的问题。在 Vue 实例加载完成后,v-cloak 指令将被 Vue 自动删除,并显示元素或组件。
- v-pre:v-pre 指令用于告诉 Vue 编译器跳过对指定元素及其子元素的解析和编译过程。当元素上添加了 v-pre 指令时,Vue 会将这些元素及其内容视为静态内容,直接输出到最终的 HTML 中,而不会进行任何编译或数据绑定。这意味着,包含 v-pre 的元素内的 Vue 指令(如 v-if、v-for)和表达式(如{{ message }})将不会被解析,而是直接以文本形式展示在页面上。
Vue Router 中如何获取路由传递过来的参数?
在 Vue Router 中,可以通过多种方式获取路由传递过来的参数,包括:
- params 参数:通过路由配置中的动态段来获取。例如,在路由配置中定义一个动态段
user/:id
,然后在组件中通过this.$route.params.id
来获取传递的id
参数。 - query 参数:通过 URL 的查询字符串来获取。例如,在 URL 中添加一个查询字符串
?name=vue
,然后在组件中通过this.$route.query.name
来获取传递的name
参数。
你了解 Vue 中的过滤器吗?它有哪些应用场景?
Vue 中的过滤器用于文本的格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式。然而,需要注意的是,在 Vue 3.x 中,过滤器已被移除,官方建议使用方法或计算属性来代替。
在 Vue 2.x 中,过滤器的应用场景包括:
- 格式化文本:将文本转换为特定的格式,如日期格式化、货币格式化等。
- 数据预处理:在将数据显示到页面上之前,对数据进行预处理和格式化。
Vue Router 如何配置 404 页面?
在 Vue Router 中配置 404 页面,可以通过定义一个通配符路由来实现。具体来说,可以在路由配置中添加一个路由规则,其路径设置为 *
,这样当访问未匹配的路由时,就会跳转到这个通配符路由对应的组件,即 404 页面。
如何在 Vue 3 中实现一个复杂的表单验证和提交逻辑?
在 Vue 3 中实现一个复杂的表单验证和提交逻辑,可以考虑以下步骤:
- 定义表单模型:使用 Vue 的响应式数据来定义表单模型,包括各种表单字段和它们的初始值。
- 创建验证规则:定义表单字段的验证规则,可以使用第三方验证库(如 VeeValidate)或自定义验证函数。
- 绑定表单字段:在模板中使用 v-model 指令将表单字段绑定到表单模型上。
- 显示验证错误:根据验证规则检查表单字段的值,并在模板中显示相应的验证错误信息。
- 处理表单提交:在表单提交时,先执行验证逻辑,如果验证通过,则执行提交操作(如发送请求到服务器)。
Vue 的 template 标签有什么用?
Vue 的 <template>
标签用于声明性地渲染 DOM 结构。在 Vue 中,<template>
标签不会渲染成真实的 DOM 元素,而是作为包含插槽内容或条件渲染内容的占位符。它允许开发者在组件中定义模板结构,而不必担心这些结构会污染最终的 DOM 树。
为什么 Vue 中的 data 属性是一个函数而不是一个对象?
在 Vue 中,data 属性被定义为一个函数而不是一个对象,是为了确保每个组件实例都能维护一份被返回对象的独立的拷贝。如果 data 是一个对象,那么由于对象是通过引用传递的,这会导致所有的组件实例共享同一份 data 对象,从而造成数据污染和状态混乱。而将 data 定义为一个函数,并在函数中返回一个对象,就可以确保每次创建一个新的组件实例时,都会调用这个函数并返回一个新的对象作为该实例的 data,从而保持数据的独立性和隔离性。
为什么不建议在 Vue 中同时使用 v-if 和 v-for?
在 Vue 中,不建议同时使用 v-if 和 v-for 指令在同一个元素上,因为这可能会导致性能问题。具体来说,v-for 的优先级高于 v-if,这意味着在每次渲染循环中,都会先执行 v-for 指令,然后再执行 v-if 指令。如果 v-if 的条件依赖于 v-for 迭代的项,那么每次渲染循环都会执行一次条件判断,这会导致不必要的计算开销。因此,为了优化性能,建议将 v-if 和 v-for 指令分开使用,例如在 v-for 循环的外部使用一个父元素来包裹 v-if 条件判断。
在 Vue 组件中写 name 选项有什么作用?
在 Vue 组件中写 name 选项有以下作用:
- 调试:在开发过程中,组件名称可以帮助开发者更容易地识别和定位问题。例如,在 Vue DevTools 浏览器扩展中,组件名称会显示在组件树中,使得开发者可以轻松地浏览和检查每个组件的状态和属性。
- 递归组件:在某些情况下,可能需要一个组件在其自身内部递归地调用自己。例如,树形结构的数据展示非常适合使用递归组件。为了实现这一点,组件名称是必须的。
- 全局注册:当需要在整个应用程序中多次使用一个组件时,可以通过全局注册来实现。这时候,组件名称变得非常重要。通过全局注册,可以在任何地方使用该组件,而无需单独导入。
- 动态组件:Vue.js 允许根据条件动态地切换组件。使用组件名称可以更方便地实现这一点。
如何在 Vue 3 中使用 defineAsyncComponent 实现异步组件加载?
在 Vue 3 中,可以使用 defineAsyncComponent
函数来定义一个异步组件。这个函数接受一个加载组件的工厂函数作为参数,并返回一个异步组件。当使用这个异步组件时,Vue 会在需要的时候动态地加载和渲染它。
使用 defineAsyncComponent
的基本步骤如下:
- 导入 defineAsyncComponent:从
vue
包中导入defineAsyncComponent
函数。 - 定义异步组件:使用
defineAsyncComponent
函数定义一个异步组件。在工厂函数中,可以返回一个 Promise,该 Promise 解析为一个组件定义。例如,可以使用动态 import(import()
)来异步加载组件。 - 使用异步组件:在模板中使用定义的异步组件,就像使用普通组件一样。
Vue 的 v-show 和 v-if 有什么区别?使用场景分别是什么?
-
区别:
- 渲染时机:v-if 在条件为 true 时才会渲染元素到 DOM 中,而 v-show 只是通过 CSS 的 display 属性来控制元素的显示和隐藏,元素始终存在于 DOM 中。
- 性能:由于 v-if 在条件为 false 时会销毁元素并移除其 DOM 节点,因此在条件频繁切换时会有一定的性能开销。而 v-show 只是切换元素的 CSS display 属性,性能消耗较小。
-
使用场景:
- v-if:适用于需要根据条件动态地添加或移除元素的场景。例如,当某个功能模块的显示与否依赖于某个条件时,可以使用 v-if 来控制该功能模块的渲染。
- v-show:适用于需要频繁切换显示状态的场景。例如,当需要在用户点击按钮时显示或隐藏某个元素时,可以使用 v-show 来实现。
Vue 计算属性的函数名和 data 中的属性可以同名吗?为什么?
在 Vue 中,计算属性的函数名和 data 中的属性不建议同名。这是因为同名会导致数据覆盖和命名冲突的问题。
具体来说,Vue 在初始化实例时,会按照特定的顺序(如 props、methods、data、computed、watch 等)来初始化各种属性和方法。如果计算属性的函数名和 data 中的属性同名,那么后者会覆盖前者,导致计算属性无法正常工作。此外,这种命名方式也违反了 Vue 的最佳实践,因为清晰的命名和避免命名冲突是保持代码可读性和可维护性的关键。
因此,为了避免潜在的问题和提高代码质量,建议避免在计算属性的函数名和 data 中的属性之间使用相同的名称。
如何监听 Vuex 数据的变化?
在 Vue 中,监听 Vuex 数据的变化可以通过多种方式实现,包括但不限于以下几种:
-
使用 Vuex 提供的 subscribe 方法:
Vuex 提供了 store.subscribe 方法,允许你在每次 mutation 提交后执行回调函数。这种方法适用于全局监听状态变化,例如进行日志记录或调试。 -
使用 Vue 组件的 watch 选项:
在 Vue 组件中,你可以使用 watch 选项来监听 Vuex 状态的变化。具体做法是,在 computed 属性中定义一个计算属性,该属性返回需要监听的 Vuex 状态,然后在 watch 选项中监听这个计算属性的变化。 -
使用 Vuex 的 mapState 辅助函数:
Vuex 提供了 mapState 辅助函数,可以将 Vuex 状态映射到组件的计算属性中。这样,你就可以在组件中直接访问这些状态,并使用 watch 选项来监听它们的变化。 -
使用 Vuex 的插件机制:
Vuex 支持插件机制,允许你编写自定义的插件来监听状态变化。通过插件,你可以在状态变化时执行自定义逻辑,例如发送网络请求或更新其他状态。
以上方法各有优缺点,具体选择哪种方法取决于你的需求和场景。在实际开发中,可以根据具体需求和场景灵活运用这些方法,以确保应用状态管理的高效性和灵活性。
为什么 0.1 + 0.2 !== 0.3
,如何让其相等?
原因
0.1 + 0.2 !== 0.3
是因为 JavaScript 中采用 IEEE 754 双精度浮点数表示法,会导致某些十进制数在二进制中无法精确表示。例如:
0.1
的二进制表示是一个无限循环小数:0.0001100110011...
- 计算时会发生精度丢失,导致
0.1 + 0.2 = 0.30000000000000004
。
解决方法
-
使用
toFixed()
或toPrecision()
:console.log((0.1 + 0.2).toFixed(1) === "0.3"); // true
-
使用 EPSILON 比较:
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
-
通过整数运算避免浮点数:
console.log((0.1 * 10 + 0.2 * 10) / 10 === 0.3); // true
typeof
和 instanceof
的区别
-
typeof
:- 返回一个字符串,表示操作数的数据类型。
- 用于基本数据类型检测:
console.log(typeof 123); // "number" console.log(typeof "abc"); // "string" console.log(typeof null); // "object" (特殊情况)
-
instanceof
:- 检测一个对象是否是某个构造函数的实例。
- 用于引用数据类型检测:
console.log([] instanceof Array); // true console.log({} instanceof Object); // true
typeof null
的结果是什么?为什么?
typeof null
的结果是 "object"
。这是因为 JavaScript 早期的实现中,null 被设计为空对象的占位符,其底层值是 0x00
,而 typeof
判断中将 0x00
误判为对象类型。
虽然是历史遗留问题,但为了兼容性,规范未修复。
JavaScript 中 null
和 undefined
的区别是什么?
-
null
:- 表示空值或空对象的占位符。
- 需要显式赋值,常用于表明“刻意没有值”。
- 类型是
object
。
-
undefined
:- 表示变量已声明但未赋值,或对象属性不存在。
- 是一种默认值。
- 类型是
undefined
。
示例:
let a;
console.log(a); // undefined
let b = null;
console.log(b); // null
如何判断 JavaScript 变量是数组?
-
Array.isArray()
(推荐):console.log(Array.isArray([])); // true
-
instanceof
检查:console.log([] instanceof Array); // true
-
Object.prototype.toString
:console.log(Object.prototype.toString.call([]) === '[object Array]'); // true
JavaScript 有哪些数据类型?它们的区别是什么?
-
基本数据类型(存储值本身,按值访问):
undefined
null
boolean
number
bigint
string
symbol
-
引用数据类型(存储引用地址,按引用访问):
object
(包括数组、函数、日期等)
说说你对 JS 作用域的理解
作用域是变量、函数和对象的访问范围,分为:
-
全局作用域:
- 在任何地方都可访问的变量。
- 通过
var
声明的全局变量会挂载到window
对象。
-
函数作用域:
- 在函数内部定义的变量只能在函数内访问。
- 通过
var
声明的变量具有函数作用域。
-
块级作用域:
- 使用
let
和const
声明的变量具有块级作用域,仅在块内有效。
- 使用
-
词法作用域:
- JavaScript 的作用域在编译时确定,嵌套函数可以访问外层作用域的变量。
let
、const
和 var
的区别是什么?
-
var
:- 声明的变量存在提升,且作用域为函数或全局。
- 可以重复声明。
console.log(x); // undefined var x = 10;
-
let
:- 声明的变量有块级作用域,不提升,且不能重复声明。
{ let y = 20; } console.log(y); // ReferenceError
-
const
:- 同
let
,但声明后必须赋值,且值不可变(对象除外)。
- 同
使用 let
全局声明变量,能通过 window
对象取到吗?
不能。通过 let
和 const
声明的全局变量不会挂载到 window
对象,而是存在于块级作用域。
let a = 10;
console.log(window.a); // undefined
JavaScript 中 Object.keys
的返回值是无序的吗?
-
对象:
- 返回所有可枚举属性的键,以插入顺序为主,但数字键按升序排列。
-
数组:
- 返回索引键,顺序与数组的索引一致。
说说你对 fetch
的理解
fetch
是 JavaScript 提供的原生方法,用于发起 HTTP 请求。它基于 Promise
,更加现代化。
优点
- Promise 支持: 简化异步操作。
- 支持流: 可处理文件下载和逐步读取。
- 灵活性: 支持各种请求方法和自定义配置。
不足
- 不自动处理
4xx
和5xx
: 需要手动处理错误。 - 老旧浏览器不支持: 需使用 Polyfill。
- 不支持进度监听: 上传或下载进度需其他方案。
示例
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
### JavaScript 的 BigInt 和 Number 类型有什么区别?
JavaScript中的BigInt和Number是表示数字的两种不同的数据类型,它们之间存在以下主要区别:
1. **表示范围**:Number类型可以表示有限范围内的数字(根据实现而异,通常是-2^53到2^53)。而BigInt类型可以表示任意大的整数,能够处理超出Number类型范围的整数。
2. **精度**:Number类型是浮点数,这意味着它可以表示小数和指数。而BigInt类型只能是整数,不能表示小数或指数。
3. **运算**:BigInt类型支持所有整数运算(加、减、乘、除、模)。而Number类型除了整数运算外,还支持浮点数运算。
### 什么是 JavaScript 的尾调用? 使用尾调用有什么好处?
尾调用是指在函数执行的最后一步调用另一个函数。使用尾调用有以下好处:
1. **节省内存**:尾调用可以避免保留当前函数的执行上下文,从而节省内存。因为尾调用是函数的最后一步操作,没有必要保留执行上下文,这样就可以释放内存空间,减少内存占用。
2. **提高代码执行效率**:由于尾调用不需要保留当前函数的执行上下文,因此可以避免不必要的操作,从而提高代码的执行效率。
3. **避免递归算法中的递归问题**:在一些递归算法中,如果递归调用自身的过程中出现尾调用优化,可以避免递归问题的发生,从而提高算法的效率。
需要注意的是,尾调用优化只在严格模式下开启,正常模式是无效的。
### ES6 有哪些新特性?
ES6(ECMAScript 6)是JavaScript的第六版标准,于2015年正式发布。相对于之前的版本,ES6引入了许多新的语法和特性,包括:
1. **块级作用域**:引入let和const关键字,可以在块级作用域中声明变量。
2. **箭头函数**:一种新的函数声明方式,使用箭头(=>)取代了传统的function关键字。
3. **解构赋值**:一种从数组或对象中提取值并赋值给变量的语法。
4. **默认参数**:允许在函数定义时为参数提供默认值。
5. **扩展运算符**:可以将数组或对象展开,提取出其中的元素。
6. **模板字符串**:一种更方便的字符串拼接方式,使用反引号(`)定义字符串,并可以在其中插入变量和表达式。
7. **类和模块**:引入类(class)的语法糖和模块化的概念,通过import和export关键字可以方便地导入和导出模块。
8. **迭代器和生成器**:可以简化处理集合和异步编程的复杂度。
9. **Promise对象**:是异步编程的一种解决方案,可以避免回调地狱和提供更好的错误处理。
此外,ES6还引入了Map和Set数据结构、迭代和解构的新语法、字符串和数组的新方法等等。
### ES6 箭头函数能当构造函数吗?
ES6的箭头函数不能作为构造函数使用。这是因为箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。而构造函数在创建实例时需要绑定自己的this,以便在实例的方法中访问实例的属性。由于箭头函数无法绑定自己的this,因此不能作为构造函数使用。
### ES6 箭头函数和普通函数有什么区别?
ES6的箭头函数和普通函数存在以下主要区别:
1. **定义方式**:箭头函数的定义要比普通函数定义简洁、清晰得多。它使用箭头(=>)取代了传统的function关键字。
2. **this绑定**:箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。而普通函数会创建自己的this,其值取决于函数的调用方式(如作为对象的方法调用时,this指向对象)。
3. **arguments对象**:箭头函数没有自己的arguments对象。如果需要访问函数的参数列表,可以使用剩余参数(...args)代替。而普通函数有自己的arguments对象,可以访问函数的参数列表以及未命名的参数。
4. **构造函数**:箭头函数不能作为构造函数使用,而普通函数可以。
5. **.call()/.apply()/.bind()方法**:这些方法可以用来动态修改普通函数执行时this的指向。但由于箭头函数的this定义时就已经确定且永远不会改变,所以使用这些方法永远也改变不了箭头函数this的指向。
### 什么是 TypeScript 的对象类型? 怎么定义对象类型?
TypeScript中的对象类型是指使用花括号{}定义的类型,它可以包含多个属性,每个属性都有一个名称和类型。在TypeScript中,可以通过以下方式定义对象类型:
1. **匿名对象类型**:在定义变量时直接使用花括号{}来定义一个对象类型。例如:
```typescript
const person: { name: string, age: number } = { name: 'John', age: 25 };
- 接口:使用接口来定义对象类型,可以使代码更加可读、易于维护。例如:
interface Person {
name: string;
age: number;
}
const person: Person = { name: 'John', age: 25 };
- 类型别名:使用类型别名可以为对象类型定义简短、易读的名称。例如:
type Person = { name: string; age: number };
const person: Person = { name: 'John', age: 25 };
Typescript 有哪些常用类型?
TypeScript的常用类型包括:
- 布尔类型(boolean):表示true或false两个值。
- 数字类型(number):表示所有的数字,包括整数和浮点数。
- 字符串类型(string):表示文本数据,可以使用单引号(')、双引号(")或反引号(`)来定义字符串。
- 数组类型(Array):表示一组有序的值,可以使用泛型来指定数组中元素的类型。
- 元组类型(Tuple):表示一个已知元素数量和类型的数组,各元素的类型不必相同。
- 枚举类型(Enum):表示一组命名的常量,枚举成员可以是数字或字符串。
- 任意类型(Any):表示可以是任何类型的值,为那些在编译时还不清楚类型的变量提供方便。
- 空类型(Void):表示没有任何类型,通常用于表示函数没有返回值。
- Null和Undefined:表示空值和未定义的值。在TypeScript中,默认情况下null和undefined是所有类型的子类型,但可以通过设置strictNullChecks编译选项来使它们成为独立的类型。
- Never类型:表示的是那些永不存在的值的类型。
- 对象类型(Object):表示非原始类型,即除number,string,boolean,symbol,null或undefined之外的类型。
此外,TypeScript还支持交叉类型、联合类型、类型别名、类型断言等高级类型特性。
使用 link 和 @import 引用 CSS 的区别
在HTML或CSS文件中引用CSS样式时,通常使用<link>
标签或@import
规则。它们之间存在以下主要区别:
- 应用位置:
<link>
标签通常放在HTML文档的<head>
部分,用于链接外部CSS文件。而@import
规则通常放在CSS文件的顶部或内部样式表的<style>
标签内,用于导入其他CSS文件或样式规则。 - 加载顺序:使用
<link>
标签引用的CSS文件在页面加载时会与HTML文档一起被下载和解析。而使用@import
规则导入的CSS文件则会在外部CSS文件被下载和解析完成后再进行。因此,@import
可能会导致页面加载速度变慢。 - 作用域:
<link>
标签引用的CSS文件具有全局作用域,可以应用于整个HTML文档。而@import
规则导入的CSS文件则具有局部作用域,只能应用于包含它的CSS文件或内部样式表。 - 兼容性:
<link>
标签是HTML标准的一部分,因此所有现代浏览器都支持它。而@import
规则虽然也是CSS标准的一部分,但在某些情况下可能存在兼容性问题(例如,在某些旧的浏览器版本中可能不被支持)。
css 中 display 属性的值及其作用
CSS中的display
属性用于控制元素的显示类型,其常见的值及其作用如下:
- block:将元素显示为块级元素。块级元素会独占一行,其宽度默认填满其父元素的宽度,且可以设置宽度、高度、内边距、外边距等属性。
- inline:将元素显示为内联元素。内联元素不会独占一行,其宽度只与其内容宽度一致,且不能设置宽度、高度属性(但可以设置内边距和外边距的左右值)。
- inline-block:将元素显示为内联块级元素。内联块级元素结合了块级元素和内联元素的特点,它不会独占一行,但可以设置宽度、高度属性。
- none:隐藏元素,不占据页面空间。设置
display: none;
后,元素及其子元素都会被隐藏,且不会在页面上占据任何空间。 - flex:将元素设置为弹性盒容器(flex container),其子元素将成为弹性盒项(flex items)。弹性盒布局允许容器内的元素在必要时增大或缩小其尺寸,以最佳方式填充可用空间。
- grid:将元素设置为网格容器(grid container),其子元素将成为网格项(grid items)。网格布局允许开发者以二维方式布局网页元素,通过定义行和列来创建复杂的页面布局。
- **