腾讯前端一面必会面试题(边面边更)
扩展运算符的作用及使用场景
(1)对象扩展运算符
对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
上述方法实际上等价于:
Object.assign
方法用于对象的合并,将源对象(source)
的所有可枚举属性,复制到目标对象(target)
。Object.assign
方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。
同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
利用上述特性就可以很方便的修改对象的部分属性。在redux
中的reducer
函数规定必须是一个纯函数,reducer
中的state
对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。
需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。
(2)数组扩展运算符
数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。
下面是数组的扩展运算符的应用:
将数组转换为参数序列
复制数组
要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
合并数组
如果想在数组内合并数组,可以这样:
扩展运算符与解构赋值结合起来,用于生成数组
需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
将字符串转为真正的数组
任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组
比较常见的应用是可以将某些数据结构转为数组:
用于替换es5
中的Array.prototype.slice.call(arguments)
写法。
使用
Math
函数获取数组中特定的值
选择器权重计算方式
!important > 内联样式 = 外联样式 > ID 选择器 > 类选择器 = 伪类选择器 = 属性选择器 > 元素选择器 = 伪元素选择器 > 通配选择器 = 后代选择器 = 兄弟选择器
属性后面加
!import
会覆盖页面内任何位置定义的元素样式作为
style
属性写在元素内的样式id
选择器类选择器
标签选择器
通配符选择器(
*
)浏览器自定义或继承
同一级别:后写的会覆盖先写的
css 选择器的解析原则:选择器定位 DOM 元素是从右往左的方向,这样可以尽早的过滤掉一些不必要的样式规则和元素
判断数组的方式有哪些
通过 Object.prototype.toString.call()做判断
通过原型链做判断
通过 ES6 的 Array.isArray()做判断
通过 instanceof 做判断
通过 Array.prototype.isPrototypeOf
对 line-height 的理解及其赋值方式
(1)line-height 的概念:
line-height 指一行文本的高度,包含了字间距,实际上是下一行基线到上一行基线距离;
如果一个标签没有定义 height 属性,那么其最终表现的高度由 line-height 决定;
一个容器没有设置高度,那么撑开容器高度的是 line-height,而不是容器内的文本内容;
把 line-height 值设置为 height 一样大小的值可以实现单行文字的垂直居中;
line-height 和 height 都能撑开一个高度;
(2)line-height 的赋值方式:
带单位:px 是固定值,而 em 会参考父元素 font-size 值计算自身的行高
纯数字:会把比例传递给后代。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px
百分比:将计算后的值传递给后代
::before 和 :after 的双冒号和单冒号有什么区别?
(1)冒号(:
)用于CSS3
伪类,双冒号(::
)用于CSS3
伪元素。(2)::before
就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom
之中,只存在在页面之中。
注意: :before
和 :after
这两个伪元素,是在CSS2.1
里新出现的。起初,伪元素的前缀使用的是单冒号语法,但随着Web
的进化,在CSS3
的规范里,伪元素的语法被修改成使用双冒号,成为::before
、::after
。
伪元素和伪类的区别和作用?
伪元素:在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:
伪类:将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:
总结: 伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。
Compositon api
Composition API
也叫组合式 API,是 Vue3.x 的新特性。
通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要
Vue2.0 中,随着功能的增加,组件变得越来越复杂,越来越难维护,而难以维护的根本原因是 Vue 的 API 设计迫使开发者使用
watch,computed,methods
选项组织代码,而不是实际的业务逻辑。另外 Vue2.0 缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以
minxis
完成逻辑复用,但是当mixin
变多的时候,会使得难以找到对应的data、computed
或者method
来源于哪个mixin
,使得类型推断难以进行。所以
Composition API
的出现,主要是也是为了解决 Option API 带来的问题,第一个是代码组织问题,Compostion API
可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。第二个是实现代码的逻辑提取与复用,当然
mixin
也可以实现逻辑提取与复用,但是像前面所说的,多个mixin
作用在同一个组件时,很难看出property
是来源于哪个mixin
,来源不清楚,另外,多个mixin
的property
存在变量命名冲突的风险。而Composition API
刚好解决了这两个问题。
通俗的讲:
没有Composition API
之前 vue 相关业务的代码需要配置到 option 的特定的区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的
compositon api 提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
生命周期的
hooks
都说 Composition API 与 React Hook 很像,说说区别
从 React Hook 的实现角度看,React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState,所以出现了以下限制
不能在循环、条件、嵌套函数中调用 Hook
必须确保总是在你的 React 函数的顶层调用 Hook
useEffect、useMemo
等函数必须手动确定依赖关系
而 Composition API 是基于 Vue 的响应式系统实现的,与 React Hook 的相比
声明在
setup
函数内,一次组件实例化只调用一次setup
,而 React Hook 每次重渲染都需要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也相对于 Vue 来说也较慢Compositon API
的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用响应式系统自动实现了依赖收集,进而组件的部分的性能优化由 Vue 内部自己完成,而
React Hook
需要手动传入依赖,而且必须必须保证依赖的顺序,让useEffect
、useMemo
等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。
虽然
Compositon API
看起来比React Hook
好用,但是其设计思想也是借鉴React Hook
的。
参考:前端进阶面试题详细解答
层叠上下文
元素提升为一个比较特殊的图层,在三维空间中 (z 轴) 高出普通元素一等。
触发条件
根层叠上下文(
html
)position
css3
属性flex
transform
opacity
filter
will-change
webkit-overflow-scrolling
层叠等级:层叠上下文在 z 轴上的排序
在同一层叠上下文中,层叠等级才有意义
z-index
的优先级最高
数组有哪些原生方法?
数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。
数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。
数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。
数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。
数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法
数组归并方法 reduce() 和 reduceRight() 方法
对 Flex 布局的理解及其使用场景
Flex 是 FlexibleBox 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局。行内元素也可以使用 Flex 布局。注意,设为 Flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效。采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),项目默认沿水平主轴排列。
以下 6 个属性设置在容器上:
flex-direction 属性决定主轴的方向(即项目的排列方向)。
flex-wrap 属性定义,如果一条轴线排不下,如何换行。
flex-flow 属性是 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap。
justify-content 属性定义了项目在主轴上的对齐方式。
align-items 属性定义项目在交叉轴上如何对齐。
align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
以下 6 个属性设置在项目上:
order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为 0。
flex-grow 属性定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。
flex-shrink 属性定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。
flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为 auto,即项目的本来大小。
flex 属性是 flex-grow,flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。
align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto,表示继承父元素的 align-items 属性,如果没有父元素,则等同于 stretch。
简单来说: flex 布局是 CSS3 新增的一种布局方式,可以通过将一个元素的 display 属性值设置为 flex 从而使它成为一个 flex 容器,它的所有子元素都会成为它的项目。一个容器默认有两条轴:一个是水平的主轴,一个是与主轴垂直的交叉轴。可以使用 flex-direction 来指定主轴的方向。可以使用 justify-content 来指定元素在主轴上的排列方式,使用 align-items 来指定元素在交叉轴上的排列方式。还可以使用 flex-wrap 来规定当一行排列不下时的换行方式。对于容器中的项目,可以使用 order 属性来指定项目的排列顺序,还可以使用 flex-grow 来指定当排列空间有剩余的时候,项目的放大比例,还可以使用 flex-shrink 来指定当排列空间不足时,项目的缩小比例。
use strict 是什么意思 ? 使用它区别是什么?
use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:
消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
消除代码运行的不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的 Javascript 做好铺垫。
区别:
禁止使用 with 语句。
禁止 this 关键字指向全局对象。
对象不能有重名的属性。
nextTick
nextTick
可以让我们在下次DOM
更新循环结束之后执行延迟回调,用于获得更新后的DOM
nextTick
主要使用了宏任务和微任务。根据执行环境分别尝试采用
Promise
MutationObserver
setImmediate
如果以上都不行则采用
setTimeout
定义了一个异步方法,多次调用
nextTick
会将方法存入队列中,通过这个异步方法清空当前队列
什么是 DOM 和 BOM?
DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
清除浮动
在浮动元素后面添加
clear:both
的空div
元素
给父元素添加
overflow:hidden
或者auto
样式,触发BFC
使用伪元素,也是在元素末尾添加一个点并带有
clear: both
属性的元素实现的。
推荐使用第三种方法,不会在页面新增 div,文档结构更加清晰
左右两边定宽,中间自适应
float,
float + calc
, 圣杯布局(设置 BFC,margin 负值法),flex
对对象与数组的解构的理解
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 1)数组的解构 在解构数组时,以元素的位置为匹配条件来提取想要的数据的:
最终,a、b、c 分别被赋予了数组第 0、1、2 个索引位的值:
数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:
通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量:
2)对象的解构 对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:
假如想要解构它的两个自有属性,可以这样:
这样就得到了 name 和 age 两个和 stu 平级的变量:
注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:
vue 实现双向数据绑定原理是什么?
箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?
普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)
箭头函数使用被称为 “胖箭头” 的操作
=>
定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。箭头函数常用于回调函数中,包括事件处理器或定时器
箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
没有原型、没有 this、没有 super,没有 arguments,没有 new.target
不能通过 new 关键字调用
一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上
当直接调用时,执行 [[Call]] 方法,直接执行函数体
箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
Object.is 实现
题目描述:
实现代码如下:
垃圾回收
对于在 JavaScript 中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当 JavaScript 的解释器消耗完系统中所有可用的内存时,就会造成系统崩溃。
内存泄漏,在某些情况下,不再使用到的变量所占用内存没有及时释放,导致程序运行中,内存越占越大,极端情况下可以导致系统崩溃,服务器宕机。
JavaScript 有自己的一套垃圾回收机制,JavaScript 的解释器可以检测到什么时候程序不再使用这个对象了(数据),就会把它所占用的内存释放掉。
针对 JavaScript 的来及回收机制有以下两种方法(常用):标记清除,引用计数
标记清除
v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。
新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。
新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。
这个算法分为三步:
首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。
如果对象不存活,则释放对象的空间。
最后将 From 空间和 To 空间角色进行交换。
新生代对象晋升到老生代有两个条件:
第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。
第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。
老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。
由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行
评论