面试刷题--1

124 阅读17分钟

一、js

1. 如何判断某个字符串长度(要求支持表情)?

function unicodeLength(str) {
    return Array.from(str).length
}

2. 根据运算优先级添加括号

let text = '11+2-34+5/24+10/5+10/512'; 
text.match(/([0-9]{1,}[*|/]){1,}[0-9]{1,}/g).forEach((item)=>{ 
	text = text.replace(item,`(${item})`) 
})
console.log(text); // '11+2-34+(5/24)+(10/5)+(10/512)'

3. 尾递归的理解

尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数

尾递归在普通尾调用的基础上,多出了2个特征:

  • 在尾部调用的是函数自身
  • 可通过优化,使得计算仅占用常量栈空间

在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,递归次数过多容易造成栈溢出

这时候,就可以使用尾递归,即一个函数中所有递归形式的调用都出现在函数的末尾,对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误

实现一下阶乘,如果用普通的递归,如下:

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

容易造成栈溢出,复杂度为O(n)

3.1 使用尾递归:

function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

尾递归只需要保存一个调用栈,复杂度 O(1)

3.2 应用场景

3.21 数组求和

function sumArray(arr, total) {
    if(arr.length === 1) {
        return total
    }
    return sumArray(arr, total + arr.pop())
}

3.22 使用尾递归优化求斐波那契数列

function factorial2 (n, start = 1, total = 1) {
    if(n <= 2){
        return total
    }
    return factorial2 (n - 1, total, total + start)
}

3.23 数组扁平化

let a = [1,2,3, [1,2,3, [1,2,3]]]
// 变成
let a = [1,2,3,1,2,3,1,2,3]
// 具体实现
function flat(arr = [], result = []) {
    arr.forEach(v => {
        if(Array.isArray(v)) {
            result = result.concat(flat(v, []))
        }else {
            result.push(v)
        }
    })
    return result
}

3.24 数组对象格式化

let obj = {
    a: '1',
    b: {
        c: '2',
        D: {
            E: '3'
        }
    }
}
// 转化为如下:
let obj = {
    a: '1',
    b: {
        c: '2',
        d: {
            e: '3'
        }
    }
}

// 代码实现
function keysLower(obj) {
    let reg = new RegExp("([A-Z]+)", "g");
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            let temp = obj[key];
            if (reg.test(key.toString())) {
                // 将修改后的属性名重新赋值给temp,并在对象obj内添加一个转换后的属性
                temp = obj[key.replace(reg, function (result) {
                    return result.toLowerCase()
                })] = obj[key];
                // 将之前大写的键属性删除
                delete obj[key];
            }
            // 如果属性是对象或者数组,重新执行函数
            if (typeof temp === 'object' || Object.prototype.toString.call(temp) === '[object Array]') {
                keysLower(temp);
            }
        }
    }
    return obj;
};

4. TypeScript 的内置数据类型

  • boolean:表示布尔值,可以是 true 或 false。
  • number:表示数字,包括整数和浮点数。
  • string:表示字符串。可以使用单引号或双引号来表示字符串。
  • void:表示没有任何返回值的函数的返回类型。
  • null 和 undefined:这两个类型是所有类型的子类型。 symbol:表示独特的值,类似于数字或字符串。

除此之外,TypeScript 还支持以下复合类型

  • array:表示一个元素类型为 T 的数组。例如,number[] 表示一个数字数组。
  • tuple:表示已知元素数量和类型的数组。例如,[string, number] 表示一个字符串和数字组成的元组。
  • enum:表示一个命名的常量枚举。
  • any:表示任意类型。
  • unknown:与 any 类似,但是在更严格的类型检查下使用。
  • object:表示非原始类型的对象。
  • 还有一些其他的类型,例如 never、union 和 intersection,它们可以用于描述更复杂的类型。

5. 版本号排序

const arr=['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
arr.sort((a,b)=>a>b?-1:1);
console.log(arr); // ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

6. Object与Map区别

6.1 概念

Object 结构提供了字符串—值的对应,Map 结构提供了值—值的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

  • 通过 const obj = {} 即可得到一个对象。
  • 通过 const m = new Map(); 即可得到一个map实例。

6.2 访问

map: 通过map.get(key)方法去访问属性, 不存在则返回undefined

object: 通过obj.a或者obj['a']去访问一个属性, 不存在则返回undefined

6.3 赋值

map: 通过map.set去设置一个值,key可以是任意类型

object: 通过object.a = 1或者object['a'] = 1,去赋值,key只能是字符串,数字或symbol

6.4 删除

map: 通过map.delete去删除一个值,试图删除一个不存在的属性会返回false

object: 通过delete操作符才能删除对象的一个属性,诡异的是,即使对象不存在该属性,删除也返回true,当然可以通过Reflect.deleteProperty(target, prop)  删除不存在的属性还是会返回true。

var obj = {}; // undefined
delete obj.a // true

6.5 大小

map: 通过map.size即可快速获取到内部元素的总个数

object: 需要通过Object.keys的转换才能将其转换为数组,再通过数组的length方法去获得或者使用Reflect.ownKeys(obj)也可以获取到keys的集合

6.6 迭代

map: 拥有迭代器,可以通过for-offorEach去直接迭代元素,而且遍历顺序是确定的

object: 并没有实现迭代器,需要自行实现,不实现只能通过for-in循环去迭代,遍历顺序是不确定的

6.6 使用场景

  1. 如果只需要简单的存储key-value的数据,并且key不需要存储复杂类型的,直接用对象
  2. 如果该对象必须通过JSON转换的,则只能用对象,目前暂不支持Map
  3. map的阅读性更好,所有操作都是通过api形式去调用,更有编程体验

7. 深拷贝浅拷贝有什么区别

7.1 浅拷贝

JavaScript中,存在浅拷贝的现象有:

  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 使用拓展运算符实现的复制

7.2 深拷贝

深拷贝开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

JSON.stringify()

const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2=JSON.parse(JSON.stringify(obj1));

这种方式存在弊端,会忽略undefinedsymbol函数

循环递归推荐

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    //if (obj.hasOwnProperty(key)) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

注意:因为 no-prototype-builtins 规则不允许Object.prototype直接从对象调用方法,所以会导致这种错误 Do not access Object.prototype method 'hasOwnProperty' from target object

解决 可以通过使用call()函数来调用不属于本身this对象的方法:person.hasOwnProperty.call()

var person = {
  name: 'xiaoliao',
  sex: 'sex'
  show: function(){
    return this.name + '' + this.sex
  }
}
 
person.hasOwnProperty('sex')    //Do not access Object.prototype method 'hasOwnProperty' from target object  no-prototype-builtins
 
//解决方案
person.hasOwnProperty.call('sex')   //true

8. 判断页面是通过PC端还是移动端访问

8.1 navigator.userAgent

if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
  // 当前设备是移动设备
}
 
// 另一种写法
if (
  navigator.userAgent.match(/Mobi/i) ||
  navigator.userAgent.match(/Android/i) ||
  navigator.userAgent.match(/iPhone/i)
) {
  // 当前设备是移动设备
}

8.2 window.orientation

侦测屏幕方向,手机屏幕可以随时改变方向(横屏或竖屏),桌面设备做不到。

window.orientation属性用于获取屏幕的当前方向,只有移动设备才有这个属性,桌面设备会返回undefined

if (typeof window.orientation !== 'undefined') {
 // 当前设备是移动设备 
}

8.3 touch 事件

手机浏览器的 DOM 元素可以通过ontouchstart属性,为touch事件指定监听函数。桌面设备没有这个属性。

function isMobile() { 
 return ('ontouchstart' in document.documentElement); 
}

// 另一种写法
function isMobile() {
try {
   document.createEvent("TouchEvent"); return true;
 } catch(e) {
   return false; 
 }
}

8.4 工具包

推荐 react-device-detect,它支持多种粒度的设备侦测

import {isMobile} from 'react-device-detect';

if (isMobile) {
 // 当前设备是移动设备
}

9. 十进制与二进制互转

  • 十进制转二进制:num.toString(2)
  • 二进制转十进制:parseInt(num, 2)

10. 遍历一个任意长度的list中的元素并依次创建异步任务

  • Promise.all

Promise.all 需要传入一个数组,数组中的元素都是 Promise 对象。当这些对象都执行成功时,则 all 对应的 promise 也成功,且执行 then 中的成功回调。如果有一个失败了,则 all 对应的 promise 失败,且失败时只能获得第一个失败 Promise 的数据。

const p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})
const p2 = Promise.resolve('success')
const p3 = Promise.reject('失败')

Promise.all([p1, p2]).then((result) => {
  console.log(result)  //["成功了", "success"]
}).catch((error) => {
  //未被调用
})

Promise.all([p1, p3, p2]).then((result) => {
  //未被调用
}).catch((error) => {
  console.log(error)  //"失败"
});
  • Promise.allSettled

Promise.allSettled() 可用于并行执行独立的异步操作,并收集这些操作的结果。

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。

Promise.allSettled([p1, p2, p3])
.then(values => {
    console.info(values)
    //[{status: "fulfilled"value: "成功了"},{status: "fulfilled"value: "success"},{status: "rejected"value: "失败"}]
})

二、css

1. z-index属性在什么情况下会失效

z-index属性在下列情况下会失效:

  • 父元素position为relative时,子元素的z-index失效。解决:父元素position改为absolute或static;
  • 元素没有设置position属性为非static属性。解决:设置该元素的position属性为relative,absolute或是fixed中的一种;
  • 元素在设置z-index的同时还设置了float浮动。解决:float去除,改为display:inline-block
  • 在手机端 iOS 13 系统中,-webkit-overflow-scrolling:touch 也会使 z-index 失效,将 touch 换成 unset

2. 单行文本实现两端对齐

text-align: justify;,但justify对最后一行无效。

2.1 方法一:添加一行

.label {
    display: inline-block;
    height: 100%;
    width: 100px;
    text-align: justify;
    vertical-align: top;
    &::after {
        display: inline-block;
        width: 100%;
        content: '';
        height: 0;
    }
}

2.2 方法二: text-align-last

text-align-last,该属性定义的是一段文本中最后一行在被强制换行之前的对齐规则。

text-align: justify;
text-align-last: justify;

3. css实现一个无限循环动画

CSS动画的无限循环,主要就是要使用animation-iteration-count这个属性,将其设置为infinite,动画就会一直循环播放。

.anima {
  animation-name: likes; // 动画名称
  animation-direction: alternate; // 动画在奇数次(135...)正向播放,在偶数次(246...)反向播放。
  animation-timing-function: linear; // 动画执行方式,linear:匀速;ease:先慢再快后慢;ease-in:由慢速开始;ease-out:由慢速结束;ease-in-out:由慢速开始和结束;
  animation-delay: 0s; // 动画延迟时间
  animation-iteration-count: infinite; //  动画播放次数,infinite:一直播放
  animation-duration: 1s; // 动画完成时间
}

@keyframes likes {
  0%{
  	transform: scale(1);
  }
  25%{
  	transform: scale(0.9);
  }
  50%{
  	transform: scale(0.85);
  }
  75%{
  	transform: scale(0.9);
  }
  100%{
  	transform: scale(1);
  }
}

4. Sass、Less

都是 CSS 预处理器,是 CSS 上的一种抽象层。是一种特殊的语法/语言编译成 CSS。 例如 Less 是一种动态样式语言,将 CSS 赋予了动态语言的特性,如变量,继承,运算, 函数,LESS 既可以在客户端上运行 (支持 IE 6+, Webkit, Firefox),也可以在服务端运行 (借助 Node.js)。

  • 结构清晰,便于扩展。 可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理, 减少无意义的机械劳动。
  • 可以轻松实现多重继承。 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。

5. 单行、多行文本溢出隐藏

  • 单行文本溢出
overflow: hidden;            // 溢出隐藏
text-overflow: ellipsis;      // 溢出用省略号显示
white-space: nowrap;         // 规定段落中的文本不进行换行
  • 多行文本溢出
overflow: hidden;            // 溢出隐藏
text-overflow: ellipsis;     // 溢出用省略号显示
display:-webkit-box;         // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3;        // 显示的行数

注意:由于上面的三个属性都是 CSS3 的属性,没有浏览器可以兼容,所以要在前面加一个-webkit- 来兼容一部分浏览器。

6. transition和animation的区别

  • transition是过度属性,强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于flash的补间动画,设置一个开始关键帧,一个结束关键帧。
  • animation是动画属性,它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于flash的补间动画,但是它可以设置多个关键帧(用@keyframe定义)完成动画。

7. 从html元素继承box-sizing

html {
  box-sizing: border-box;
}
*, *:before, *:after {
  box-sizing: inherit;
}

8. position: fixed 非相对于浏览器窗口进行定位

当元素祖先的 transformperspective 或 filter 属性非 none 时,容器由视口改为该祖先。

9. 移动端的样式适配

9.1 响应式设计 - viewport

viewport 表示浏览器的可视区域,也就是浏览器中用来显示网页的那部分区域。存在三种 viewport 分别为 layout viewportvisual viewport 以及 ideal viewport

  • layout viewport 为布局视口,即网页布局的区域,它是 html 元素的父容器,只要不在 css 中修改  元素的宽度, 元素的宽度就会撑满 layout viewport 的宽度。
  • visual viewport 为视觉视口,就是显示在屏幕上的网页区域,它往往只显示 layout viewport 的一部分。
  • ideal viewport 为理想视口,不同的设备有自己不同的 ideal viewport,ideal viewport 的宽度等于移动设备的屏幕宽度,所以其是最适合移动设备的 viewport。

9.2 利用 meta 标签对 viewport 进行控制

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

该 meta 标签的作用是让当前 viewport 的宽度等于设备的宽度,同时不允许用户手动缩放。如果你不这样的设定的话,那就会使用那个比屏幕宽的默认 viewport(layout viewport),也就是说会出现横向滚动条。
相关的属性意义如下所示:

width设置 layout viewport 的宽度,为一个正整数,或字符串 "width-device"
height设置页面的初始缩放值,为一个数字,可以带小数
initial-scale允许用户的最小缩放值,为一个数字,可以带小数
minimum-scale允许用户的最大缩放值,为一个数字,可以带小数
maximum-scale设置 layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes 代表允许

9.3 适配方案选择

9.31 flexible 适配方案

在 rem 方案上进行改进,我们可以使用 js 动态来设置根字体,这种方案的典型代表就是 flexible 适配方案

9.32 viewport 适配方案

由于 viewport 单位得到众多浏览器的兼容,所以目前基于 viewport 的移动端适配方案被各大厂团队所采用。

vw 作为布局单位,从底层根本上解决了不同尺寸屏幕的适配问题,因为每个屏幕的百分比是固定的、可预测、可控制的。 viewport 相关概念如下:

  • vw:是 viewport's width 的简写,1vw 等于 window.innerWidth 的 1%;
  • vh:和 vw 类似,是 viewport's height 的简写,1vh 等于 window.innerHeihgt 的 1%;
  • vmin:vmin 的值是当前 vw 和 vh 中较小的值;
  • vmax:vmax 的值是当前 vw 和 vh 中较大的值;

9.321 设置 meta 标签

在 html 头部设置 mata 标签如下所示,让当前 viewport 的宽度等于设备的宽度,同时不允许用户手动缩放。

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

9.322 px 自动转换为 vw

设计师一般给宽度大小为 375px 或 750px 的视觉稿,我们采用 vw 方案的话,需要将对应的元素大小单位 px 转换为 vw 单位,这是一项影响开发效率(需要手动计算将 px 转换为 vw)且不利于后续代码维护(css 代码中一堆 vw 单位,不如 px 看的直观)的事情;好在社区提供了 postcss-px-to-viewport 插件,来将 px 自动转换为 vw,相关配置步骤如下:

  • 安装插件
npm install postcss-px-to-viewport --save-dev
  • webpack 配置

官网是使用 glup 进行配置,但是我们项目模版中是使用 webpack 进行 postcss 插件以及相关样式插件的配置,所以我们就使用 webpack 进行配置使用,不需要额外引入 gulp 编译;webpack 相关配置如下,且每个属性表示的意义进行了备注:

module.exports = {
    plugins: {
    // ...
        'postcss-px-to-viewport': {
            // options
            unitToConvert: 'px',    // 需要转换的单位,默认为"px"
            viewportWidth: 750,     // 设计稿的视窗宽度
            unitPrecision: 5,       // 单位转换后保留的精度
            propList: ['*', '!font-size'],        // 能转化为 vw 的属性列表
            viewportUnit: 'vw',     // 希望使用的视窗单位
            fontViewportUnit: 'vw', // 字体使用的视窗单位
            selectorBlackList: [],  // 需要忽略的 CSS 选择器,不会转为视窗单位,使用原有的 px 等单位
            minPixelValue: 1,       // 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换
            mediaQuery: false,      // 媒体查询里的单位是否需要转换单位
            replace: true,          // 是否直接更换属性值,而不添加备用属性
            exclude: undefined,     // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
            include: /\/src\//,     // 如果设置了include,那将只有匹配到的文件才会被转换
            landscape: false,       // 是否添加根据 landscapeWidth 生成的媒体查询条件
            landscapeUnit: 'vw',    // 横屏时使用的单位
            landscapeWidth: 1125,   // 横屏时使用的视窗宽度
        },
    },
};

其中需要强调的点为 propList 属性,我们配置了 font-size 不进行转换 vw,也就是说在不同手机屏幕尺寸下的字体大小是一样的。 其中 font-size 是否需要根据屏幕大小做适配,或者怎么做,一直是个争论不休的话题;考虑到我们移动端没有平板的需求,且咨询过团队业务设计师的意见,所以对模版进行以上默认配置;当然如果你的视觉要求你的项目要做字体大小适配,修改 propList 属性的配置即可。

  • 标注不需要转换的属性

在项目中,如果设计师要求某一场景不做自适配,需为固定的宽高或大小,这时我们就需要利用 postcss-px-to-viewport 插件的 Ignoring 特性,对不需要转换的 css 属性进行标注,示例如下所示:

  /* px-to-viewport-ignore-next */ —> 下一行不进行转换.
  /* px-to-viewport-ignore */ —> 当前行不进行转换
  • Retina 屏预留坑位

考虑 Retina 屏场景,可能对图片的高清程度、1px 等场景有需求,所以我们预留判断 Retina 屏坑位。 相关方案如下:在入口的 html 页面进行 dpr 判断,以及 data-dpr 的设置;然后在项目的 css 文件中就可以根据 data-dpr 的值根据不同的 dpr 写不同的样式类;

(1)index.html 文件

// index.html 文件 const dpr = devicePixelRatio >= 3? 3: devicePixelRatio >= 2? 2: 1; document.documentElement.setAttribute('data-dpr', dpr);

(2)样式文件

[data-dpr="1"] .hello {
  background-image: url(image@1x.jpg);
}
    
[data-dpr="2"] .hello {
  background-image: url(image@2x.jpg);
}
  
[data-dpr="3"] .hello {
  background-image: url(image@3x.jpg);
}

9.33 iPhoneX 适配方案

9.331 设置网页在可视窗口的布局方式

新增 viweport-fit 属性,使得页面内容完全覆盖整个窗口,前面也有提到过,只有设置了 viewport-fit=cover,才能使用 env()

<meta name="viewport" content="width=device-width, viewport-fit=cover">

9.332 fixed 完全吸底元素场景的适配

可以通过加内边距 padding 扩展高度:

{
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc 覆盖原来高度:

{
  height: calc(60px(假设值) + constant(safe-area-inset-bottom));
  height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

9.333 fixed 非完全吸底元素场景的适配

像这种只是位置需要对应向上调整,可以仅通过下外边距 margin-bottom 来处理

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

或者,你也可以通过计算函数 calc 覆盖原来 bottom 值:

{
  bottom: calc(50px(假设值) + constant(safe-area-inset-bottom));
  bottom: calc(50px(假设值) + env(safe-area-inset-bottom));
}