目录
flex 布局如何使用
说出 space-between 和 space-around 的区别
介绍下粘性布局(sticky)
特点:
结构:
继承:
性能:
居中为什么要使用 transform(为什么不使用 marginLeft/Top)
区别:
说一说transition 和 animation
Transition(过渡)
基本语法:
Animation(动画)
基本语法:
区别
JS原生的Object.definePrototype()的一些属性方法
一个数组,如何使用Math.max()取最大值
1.使用拓展运算符
2.使用apply()
多个数组的拼接
1.拓展运算符:
2.concat() 方法:
3.push() 方法:
4.使用 Array.from() 和合并操作:
v-model的实现原理
基础用法
实现原理
vue2组件间的通信
vuex和pinia的区别
浏览器的事件轮询
事件轮询的基本原理:
事件轮询过程大致分为以下几个阶段:
使用TS声明数组类型
使用元素类型 + 方括号 []:
使用数组泛型 Array:
使用 Array 类型别名:
使用接口定义数组类型:
flex 布局如何使用
flex 是 Flexible Box 的缩写,意为"弹性布局"。指定容器display: flex即可。
容器有以下属性:flex-direction,flex-wrap,flex-flow,justify-content,align-items,align-content。
- flex-direction属性决定主轴的方向;
- flex-wrap属性定义,如果一条轴线排不下,如何换行;
- flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap;
- justify-content属性定义了项目在主轴上的对齐方式。
- align-items属性定义项目在交叉轴上如何对齐。
- align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
项目(子元素)也有一些属性:order,flex-grow,flex-shrink,flex-basis,flex,align-self。
- order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
- flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
- flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。
- flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
- align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
说出 space-between 和 space-around 的区别
这个是 flex 布局的内容,其实就是一个边距的区别,
按水平布局来说,space-between是两端对齐,在左右两侧没有边距,而space-around是每个 子项目左右方向的 margin 相等,所以两个item中间的间距会比较大。
介绍下粘性布局(sticky)
position 中的 sticky 值是 CSS3 新增的,设置了 sticky 值后,在屏幕范围(viewport)时该元素的位置并不受到定位影响(设置是top、left等属性无效),当该元素的位置将要移出偏移范围时,定位又会变成fixed,根据设置的left、top等属性成固定位置的效果。
特点:
元素不脱离文档流:设置了
position: sticky
的元素仍然保留在文档流中,它的位置不会影响其他元素的布局。固定位置效果:当元素在容器中被滚动超过指定的偏移值时,元素会固定在容器内的指定位置。比如,如果设置了
top: 50px
,当粘性元素距离相对定位的容器顶部50px时,它就会固定在这个位置,不再向上移动。相对偏移值的计算:元素固定的相对偏移是相对于离它最近的具有滚动框的祖先元素。如果祖先元素都不具有滚动功能,那么相对偏移值是相对于viewport来计算的,即浏览器窗口。
position: sticky
的这些特点使得它非常适合创建需要在滚动时保持固定位置的元素,比如导航栏、表格头部等。
分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。
结构:
display:none: 元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击
visibility: hidden:不会让元素从渲染树消失,渲染元素占据空间,内容不可见,不能点击
opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,内容不可见,可以点击
继承:
display: none和opacity: 0:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。
visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。
性能:
displaynone : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大
visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容
opacity: 0 : 修改元素会造成重绘,性能消耗较少
居中为什么要使用 transform(为什么不使用 marginLeft/Top)
总的来讲就是transform不会触发页面的重排和重绘,而top/left 会导致重排
区别:
transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。
top/left 属于布局属性,该属性的变化会导致重排(reflow/relayout),所谓重排即指对这些节点以及受这些节点影响的其它节点,进行CSS计算->布局->重绘过程,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。
灵活性:使用
transform
可以实现更多种居中方式,包括水平居中、垂直居中、水平垂直居中等,而且不受父元素宽高的限制。性能:使用
transform
通常比使用marginLeft
和marginTop
更高效,因为transform
不会触发页面的重排和重绘。重排和重绘会消耗更多的性能,特别是在有大量元素需要居中时。简洁性:使用
transform
的代码通常更简洁,更易读,而且不需要考虑父元素的尺寸,因为transform
是相对于元素本身进行定位的。兼容性:
transform
具有良好的兼容性,几乎所有现代浏览器都支持,包括移动端浏览器。
说一说transition 和 animation
Transition(过渡)
transition
允许元素在一种状态变化到另一种状态时平滑地过渡。它可以应用于元素的属性,比如width
,height
,color
,opacity
等。
transition
需要两个状态:初始状态和最终状态,当触发状态变化时,会自动计算过渡中间状态,从而实现平滑的过渡效果。基本语法:
/* 属性 过渡时间 过渡速度 */ transition: property duration timing-function;
property
:要过渡的 CSS 属性,可以是单个属性,也可以是多个属性,用逗号分隔。duration
:过渡的持续时间,以秒(s)或毫秒(ms)为单位。timing-function
:过渡效果的时间函数,包括linear
、ease
、ease-in
、ease-out
、ease-in-out
等。Animation(动画)
animation
允许你创建自定义的动画效果,可以控制动画的每一帧。它比transition
更加灵活,可以定义更复杂的动画效果,包括旋转、缩放、位移等。基本语法:
/* 动画名称 动画时长 过渡速度 延迟 动画次数 动画方向 填充模式 是否暂停 动画播放状态 */ animation: name duration timing-function delay iteration-count direction fill-mode play-state;
name
:定义动画名称,可以通过@keyframes
定义。duration
:动画持续时间,以秒(s)或毫秒(ms)为单位。timing-function
:动画效果的时间函数,与transition
相同。delay
:动画开始之前的延迟时间,以秒(s)或毫秒(ms)为单位。iteration-count
:动画播放次数,可以为数字或infinite
。direction
:动画播放方向,包括normal
、reverse
、alternate
、alternate-reverse
。fill-mode
:动画结束后元素样式的状态,包括none
、forwards
、backwards
、both
。play-state
:动画播放状态,包括running
、paused
。区别
灵活性:
animation
更加灵活,可以实现更复杂的动画效果;而transition
只能在两种状态之间过渡。定义方式:
transition
直接作用于 CSS 属性,而animation
需要先定义关键帧(@keyframes
)。控制:
animation
可以通过 JavaScript 控制播放状态,而transition
无法直接控制。性能:在某些情况下,
transition
比animation
更节省性能,特别是在只需要简单的过渡效果时。
JS原生的Object.definePrototype()的一些属性方法
Object.defineProperty()
是 JavaScript 中用于定义新属性或修改现有属性的方法。它允许我们精确控制属性的行为,包括属性的值、可枚举性、可配置性和可写性等。下面是
Object.defineProperty()
的一些属性方法:
value:要设置的属性的值。可以是任何有效的 JavaScript 值(数值、对象、函数等)。
writable:布尔值,指定属性值是否可写。如果为
true
,属性值可被修改;如果为false
,属性值不可被修改。默认为false
。enumerable:布尔值,指定属性是否可枚举。如果为
true
,属性可以通过for...in
循环遍历到;如果为false
,属性不会被枚举。默认为false
。configurable:布尔值,指定属性是否可配置。如果为
true
,属性的可配置性可以被修改;如果为false
,属性的可配置性不可被修改,且属性不可删除。默认为false
。get:函数,当访问属性时被调用。该函数返回属性的值。如果不存在此方法,则返回
undefined
。set:函数,当属性被修改时被调用。该函数接受被赋予的新值作为参数。
例如,下面是一个使用
Object.defineProperty()
定义新属性的示例:var obj = {};Object.defineProperty(obj, 'name', {value: 'John',writable: true,enumerable: true,configurable: true });console.log(obj.name); // 输出 "John"
在上面的示例中,我们定义了一个名为
name
的属性,其值为'John'
,可写、可枚举和可配置。
一个数组,如何使用Math.max()取最大值
1.使用拓展运算符
var arr = [1, 2, 3, 4, 5]; var maxNumber = Math.max(...arr); console.log(maxNumber); // 输出 5
2.使用apply()
var arr = [1, 2, 3, 4, 5]; var maxNumber = Math.max.apply(null, arr); console.log(maxNumber); // 输出 5
多个数组的拼接
1.拓展运算符:
var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; var arr3 = [7, 8, 9];var concatenatedArray = [...arr1, ...arr2, ...arr3]; console.log(concatenatedArray); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
2.concat() 方法:
var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; var arr3 = [7, 8, 9];var concatenatedArray = arr1.concat(arr2, arr3); console.log(concatenatedArray); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
3.push() 方法:
var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; var arr3 = [7, 8, 9];arr1.push(...arr2); arr1.push(...arr3);console.log(arr1); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
4.使用 Array.from() 和合并操作:
var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; var arr3 = [7, 8, 9];var concatenatedArray = Array.from([arr1, arr2, arr3].reduce((acc, curr) => acc.concat(curr), [])); console.log(concatenatedArray); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
v-model的实现原理
基础用法
v-model 本质上不过是语法糖,可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。
它会根据控件类型自动选取正确的方法来更新元素。
它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
实现原理v-model只不过是一个语法糖而已,真正的实现靠的还是 v-bind:绑定响应式数据
触发oninput 事件并传递数据
例如: 上面的等价于下面的写法<input v-model="searchText"><inputv-bind:value="searchText"v-on:input="searchText = $event.target.value" > <!-- 自html5开始,input每次输入都会触发oninput事件, 所以输入时input的内容会绑定到searchText中,于是searchText的值就被改变; $event 指代当前触发的事件对象; $event.target 指代当前触发的事件对象的dom; $event.target.value 就是当前dom的value值; 在@input方法中,value => searchText; 在:value中,searchText => value; -->
vue2组件间的通信
Props 和 Events: 父组件可以通过 props 向子组件传递数据,子组件可以通过 events 发送消息给父组件。这是最常见的一种组件间通信方式。
Vuex: Vuex 是 Vue.js 的官方状态管理库,用于在组件之间共享状态。可以将需要共享的状态存储在 Vuex 的 store 中,然后在任何组件中通过 getters、mutations 和 actions 进行状态的读取和修改。
$emit 和 $on: Vue.js 提供了 $emit 和 $on 方法用于自定义事件的触发和监听。组件可以通过 $emit 触发一个自定义事件,其他组件可以通过 $on 监听该事件来实现通信。
$parent 和 $children: 可以通过 $parent 和 $children 来访问父组件和子组件的实例,从而进行直接的通信。但这种方式一般不推荐,因为它会使组件的耦合度增加。
provide 和 inject: 父组件可以通过 provide 提供数据,子组件可以通过 inject 来注入父组件提供的数据。这种方式可以实现祖先组件与后代组件之间的通信。
事件总线: 可以使用一个空的 Vue 实例作为事件总线,将事件注册在这个实例上,然后任何组件都可以通过这个实例来触发和监听事件。
vuex和pinia的区别
主要就是体现在修改值这一方面比较直观,
使用vuex时,严格模式下,禁止直接修改 store 中的数据。这是为了确保状态的改变是可追踪的,同时也避免了状态的突变带来的难以调试的问题。如果需要修改 store 中的数据,应该通过提交 mutation 来修改。
在 Pinia 中,默认情况下也是不允许直接修改 store 中的数据的,需要通过提交 mutation 或者调用 action 来修改。不过 Pinia 也提供了一种非常规的方式来修改数据,即使用
store.state
属性直接访问 state 的数据,并且直接修改。这种方式不会触发任何 Pinia 提供的响应式特性,也不会进行任何状态变更的记录和追踪。因此,在开发中,不建议直接使用这种方式来修改 state。所以,使用pinia的弊端也就是我们直接修改变量后无法做到追踪。
浏览器的事件轮询
浏览器的事件轮询(Event Loop)是浏览器中用于处理异步任务和事件的一种机制。它是浏览器保持响应性的核心部分之一。在 JavaScript 引擎中,事件轮询负责管理执行任务队列,并在合适的时机执行任务。
事件轮询的基本原理:
执行栈(Execution Stack): JavaScript 是单线程的语言,代码的执行是在一个执行栈中进行的。当执行 JavaScript 代码时,它们会被按照执行顺序依次压入执行栈中,并执行其中的任务。
任务队列(Task Queue): 除了执行栈外,浏览器还有一个任务队列,用于存放异步任务和事件。当发生异步任务或事件时,它们会被放入任务队列中等待执行。
事件循环(Event Loop): 事件循环是一个持续运行的过程,它不断地检查执行栈和任务队列的状态,根据一定的规则将任务队列中的任务移入执行栈中执行。当执行栈为空时,事件循环会从任务队列中取出任务执行,这个过程会一直重复。
事件轮询过程大致分为以下几个阶段:
执行同步任务: 首先执行执行栈中的同步任务,直到执行栈为空或者遇到异步任务。
执行微任务(Microtask): 在同步任务执行完毕后,会立即执行所有微任务队列中的任务。微任务包括 Promise 的回调函数、MutationObserver 的回调函数等。
执行宏任务(Macrotask): 如果执行栈为空,并且微任务队列中的任务执行完毕,事件循环会从宏任务队列中选择一个任务放入执行栈中执行。常见的宏任务包括 setTimeout、setInterval、requestAnimationFrame、I/O 操作等。
更新渲染(Render): 在执行宏任务后,如果需要进行页面的渲染,浏览器会执行渲染操作,更新页面的视图。
重复上述步骤: 事件循环会一直重复以上步骤,保持页面的响应性和流畅性。
使用TS声明数组类型
使用元素类型 + 方括号 []:
// 声明一个只包含数字类型的数组 let numbers: number[] = [1, 2, 3, 4, 5];// 声明一个只包含字符串类型的数组 let strings: string[] = ['apple', 'banana', 'orange'];
使用数组泛型 Array<elementType>:
// 声明一个只包含数字类型的数组 let numbers: Array<number> = [1, 2, 3, 4, 5];// 声明一个只包含字符串类型的数组 let strings: Array<string> = ['apple', 'banana', 'orange'];
使用 Array 类型别名:
// 使用类型别名定义一个数组类型 type NumberArray = number[];// 声明一个只包含数字类型的数组 let numbers: NumberArray = [1, 2, 3, 4, 5];
使用接口定义数组类型:
// 使用接口定义一个只包含数字类型的数组 interface NumberArray {[index: number]: number; }// 声明一个只包含数字类型的数组 let numbers: NumberArray = [1, 2, 3, 4, 5];