一、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-of、forEach去直接迭代元素,而且遍历顺序是确定的
object: 并没有实现迭代器,需要自行实现,不实现只能通过for-in循环去迭代,遍历顺序是不确定的
6.6 使用场景
- 如果只需要简单的存储key-value的数据,并且key不需要存储复杂类型的,直接用对象
- 如果该对象必须通过JSON转换的,则只能用对象,目前暂不支持Map
- map的阅读性更好,所有操作都是通过api形式去调用,更有编程体验
7. 深拷贝浅拷贝有什么区别
7.1 浅拷贝
在JavaScript中,存在浅拷贝的现象有:
Object.assignArray.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));
这种方式存在弊端,会忽略undefined、symbol和函数
循环递归推荐
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; // 动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放。
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 非相对于浏览器窗口进行定位
当元素祖先的 transform, perspective 或 filter 属性非 none 时,容器由视口改为该祖先。
9. 移动端的样式适配
9.1 响应式设计 - viewport
viewport 表示浏览器的可视区域,也就是浏览器中用来显示网页的那部分区域。存在三种 viewport 分别为 layout viewport、visual 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));
}