logo头像
Snippet 博客主题

【置顶】超级有用的html/css/js/vue等技巧 持续更新中

一、HTML

暂无


二、CSS

1、清除浮动

1
2
3
4
5
6
7
8
9
10
11
12
13
// 清除浮动(双伪元素)
.clearfix:before,
.clearfix:after {
content: '';
display: table;
}
.clearfix:after {
clear: both;
}
// ie6 7 专门清除浮动的样式
.clearfix {
*zoom: 1;
}

清除浮动的原理

clear:both清除浮动的关键

clear是CSS中的定位属性,规定元素的哪一侧不允许其他浮动元素。那么clear:both就是规定在左右两侧均不允许浮动元素。

clear属性只能在块级元素上其作用,这就是清除浮动样式中display:table的作用。


三、JavaScript

1、基于URLSearchParams或URL获取queryString的值

测试链接:

https://cf.cmpay.com/mpl/credit_orderResult.html?orderNo=50620520605&orderDate=2052

URLSearchParams

1
2
3
4
5
6
7
// location.search 取到的是"?orderNo=50620520605&orderDate=2052"

const urlSP = new URLSearchParams(location.search);
function getQueryString(key) {
return urlSP.get(key)
}
getQueryString('orderNo') // 50620520605

URL

1
2
3
4
5
const urlObj = new URL(location.href);
function getQueryString(key) {
return urlObj.searchParams.get(key)
}
getQueryString('orderNo') // 50620520605

2、基于atob和btoa的base64编码和解码

浏览器内置了base64编码和解码的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# encodeURIComponent() 函数可把字符串作为 URI 组件进行编码
# unescape() 函数可对通过 escape() 编码的字符串进行解码
# btoa() 方法用于创建一个 base-64 编码的字符串
# atob() 方法用于解码使用 base-64 编码的字符串

function utf8_to_b64( str ) {
return window.btoa(unescape(encodeURIComponent( str )));
}

function b64_to_utf8( str ) {
return decodeURIComponent(escape(window.atob( str )));
}

utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

3、相对地址转换为绝对地址

1
2
3
4
5
6
7
8
function realativeToAbs(href) {
let aEl = document.createElement("a");
aEl.href = href;
const result = aEl.href;
aEl = null;
return result;
}
realativeToAbs("../a/b/b/index.html") // http://127.0.0.1:5500/a/b/b/index.html

4 、禁止图片拖拽、禁止右键、禁止选择、禁止复制

1
2
3
4
5
6
['dragstart','contextmenu', 'selectstart', 'copy'].forEach(function (ev) {
document.addEventListener(ev, function (ev) {
ev.preventDefault();
ev.returnValue = false;
})
});

5、es6的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const obj = {
a:1,
b:2,
c:3,
d:4,
e:5,
}

#吐槽:
const a = obj.a;
const b = obj.b;
const c = obj.c;
const d = obj.d;
const e = obj.e;

#改进:
const {a,b,c,d,e} = obj;

#其他:
// 属性名和变量名不一样时
let { a: f, b: l } = obj;
f // 1
l // 2

// 指定默认值
let { x = 11 } = {};
let [a = 66] = [];
x // 11
a // 66

注意解构的对象不能为undefinednull。否则会报错,故要给被解构的对象一个默认值。

const {a,b,c,d,e} = obj || {};


6、合并数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#吐槽:
const a = [1,2,3];
const b = [1,5,6];
const c = a.concat(b);//[1,2,3,1,5,6]

const obj1 = { a: 1, b: 33 }
const obj2 = { b: 2, c: 66 }
const obj = Object.assign({}, obj1, obj2) //{a: 1, b: 2, c: 66} 属性名相同最后对象覆盖前面对象


#改进:
const a = [1, 2, 3];
const b = [1, 5, 6];
const c = [...a, ...b] //[1,2,3,1,5,6] 不去重
const c = [...new Set([...a, ...b])] //[1,2,3,5,6] 去重

const obj1 = { a: 1, b: 33 }
const obj2 = { b: 2, c: 66 }
console.log({...obj1, ...obj2}); //{a: 1, b: 2, c: 66}

7、关于拼接字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
const name = '小明';
const score = 59;

#吐槽:
let result = '';
if(score > 60){
result = `${name}的考试成绩及格`;
}else{
result = `${name}的考试成绩不及格`;
}

#改进:
const result = `${name}${score > 60?'的考试成绩及格':'的考试成绩不及格'}`; // 小明的考试成绩不及格

8、扁平化数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const deps = {
'采购部':[1,2,3],
'人事部':[5,8,12],
'行政部':[5,14,79],
'运输部':[3,64,105],
}


#吐槽:
let member = [];
for (let item in deps){
const value = deps[item];
if(Array.isArray(value)){
member = [...member,...value]
}
}
member = [...new Set(member)] // [1, 2, 3, 5, 8, 12, 14, 79, 64, 105]

#改进:
let member = Object.values(deps).flat(Infinity); // [1, 2, 3, 5, 8, 12, 14, 79, 64, 105]

9、添加对象属性

1
2
3
4
5
6
7
8
9
10
#吐槽:
let obj = {};
let index = 1;
let key = `topic${index}`;
obj[key] = '话题内容';

#改进:es6
let obj = {};
let index = 1;
obj[`topic${index}`] = '话题内容';

10、非空的判断

空值合并操作符??

只有当左侧为null和undefined时,才会返回右侧的值

1
2
3
if(a === null || a === undefined) {    
doSomething()
}

也就是如果需要验证一个值是否等于null或者undefined,可以使用null合并操作符来简化上面的代码。

1
a ?? doSomething()

这样,仅当 a 未定义或为空时,才会执行控制合并运算符之后的代码。空合并运算符 ??是一个逻辑运算符,当左侧操作数为 null 或未定义时返回其右侧操作数,否则返回左侧操作数。

例子:

1
2
3
4
5
6
7
8
9
10
11
const nullValue = null;
const emptyText = ""; // 空字符串,是一个假值,Boolean("") === false
const someNumber = 42;

const valA = nullValue ?? "valA 的默认值";
const valB = emptyText ?? "valB 的默认值";
const valC = someNumber ?? 0;

console.log(valA); // "valA 的默认值"
console.log(valB); // ""(空字符串虽然是假值,但不是 null 或者 undefined)
console.log(valC); // 42

11、获取对象属性值

可选链操作符 (?.)

1
2
3
4
5
#吐槽:
const name = obj && obj.name;

#改进:es6
const name = obj?.name;

12、html编码和解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#1.用浏览器内部转换器实现html编码(转义)
htmlEncode: function (html) {
//1.首先动态创建一个容器标签元素,如DIV
var temp = document.createElement("div");
//2.然后将要转换的字符串设置为这个元素的innerText或者textContent
(temp.textContent != undefined) ? (temp.textContent = html) : (temp.innerText = html);
//3.最后返回这个元素的innerHTML,即得到经过HTML编码转换的字符串了
var output = temp.innerHTML;
temp = null;
return output;
}
console.log(htmlEncode("<div class='demo'>测试</div>"));

#2.用浏览器内部转换器实现html解码(反转义)
htmlDecode: function (text) {
//1.首先动态创建一个容器标签元素,如DIV
var temp = document.createElement("div");
//2.然后将要转换的字符串设置为这个元素的innerHTML(ie,火狐,google都支持)
temp.innerHTML = text;
//3.最后返回这个元素的innerText或者textContent,即得到经过HTML解码的字符串了。
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
console.log(htmlDecode("&lt;div class='demo'&gt;测试&lt;/div&gt;"));

13、箭头函数

var func = () => ({ foo: 1 }); 返回的是括号里面的对象

1
2
3
4
let age = prompt("What is your age?", 18);

let welcome = (age < 18) ? () => alert('Hello') : () => alert("Greetings!");
welcome();

重要特性

1)没有arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1、普通函数中的arguments正确使用:
function foo(n) {
// arguments[0] 表示传给foo函数的第一个参数,也就是n
var f = () => arguments[0] + n;
return f();
}
foo(1); // 2
foo(3); // 6
foo(3, 2);//6


# 2、箭头函数中无法使用arguments
// ReferenceError: arguments is not defined
var func = (a, b) => {return arguments[0];}

2)没有prototype属性

1
2
var Foo = () => {};
console.log(Foo.prototype); // undefined

3)不能使用new

箭头函数没有this,不能用作构造函数,也就无法使用 new

1
2
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

14、初始化一个数组

如果你想初始化一个指定长度的一维数组并指定默认值,你可以这样做。

1
2
const array = Array(6).fill(''); 
// ['', '', '', '', '', '']

如果你想初始化一个指定长度的二维数组并指定默认值,你可以这样做。

1
2
3
4
5
6
7
8
9
const matrix = Array(6).fill(0).map(() => Array(5).fill(0)); 
// [
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0],
// ]

15、数组求和/最大值/最小值

1
const array  = [5,4,7,8,9,2];

1)求和 

1
array.reduce((a,b) => a+b);

2)寻找最大值

1
array.reduce((a,b) => a > b ? a : b);  Math.max(...array)

2)寻找最小值

1
array.reduce((a,b) => a < b ? a : b);  Math.min(...array)

请记住:数组的reduce方法可以用来解决很多数组求值问题。


16、滤错误值

如果要过滤数组中的值,例如 false、0、null、undefined 等,可以这样做。

1
2
3
const array = [1, 0, undefined, 6, 7, '', false];
array.filter(Boolean);
// [1, 6, 7]

17、空数组

如果要清空数组,可以将数组的长度设置为 0。

1
2
3
let array = ["A", "B", "C", "D", "E", "F"]
array.length = 0
console.log(array) // []

18、计算性能

以下操作可用于计算代码的性能。

1
2
3
4
5
6
7
const startTime = performance.now(); 
for(let i = 0; i < 1000; i++) {
console.log(i)
}
const endTime = performance.now();
const totaltime = endTime - startTime;
console.log(totaltime); // 10.3333333

19、将数组元素转换为数字

如果有一个数组,并且你想将数组的元素转换为数字,你可以使用 map 方法来完成。

1
2
const array = ['12', '1', '3.1415', '-10.01'];
array.map(Number); // [12, 1, 3.1415, -10.01]

这样,map 对数组的每个元素执行 Number 构造函数,并在遍历数组时返回结果。


20、将类数组转换为数组

可以使用以下方法将类数组转换为数组。

1
Array.prototype.slice.call(arguments);

此外,还可以使用扩展运算符来实现。

1
[...arguments]

21、对象属性的动态声明

如果你想动态地为一个对象声明属性,你可以这样做。

1
2
3
4
5
6
const dynamic = 'color';
var item = {
brand: 'Ford',
[dynamic]: 'Blue'
}
console.log(item); // { brand: "Ford", color: "Blue" }

22、缩短console.log()

每次debug都要写很多console.log()会比较麻烦,可以用下面的形式来简化这段代码。

1
2
3
const c = console.log.bind(document) 
c(222)
c("hello world")

这将每次只执行 c 方法。


23、删除数组元素

如果我们想删除一个数组的元素,可以使用delete来完成,但是删除后元素会变成undefined,不会消失,而且执行会消耗很多时间,所以大部分情况下不会满足我们的需求。所以我们可以使用数组的 slice() 方法来删除数组的元素。

1
2
3
4
const array = ["a", "b", "c", "d"]
const arrayRemoveed = array.splice(0, 2)
console.log(array) // ["c", "d"]
console.log(arrayRemoveed) // ["a", "b"]

24、检查对象是否为空

如果我们想检查对象是否为空,我们可以使用以下内容。

1
2
Object.keys({}).length  // 0
Object.keys({key: 1}).length // 1

Object.keys() 方法用于获取对象的键,它将返回一个包含这些键值的数组。如果返回数组的长度为 0,则该对象必须为空。


25、获取数组中的最后一项

1
const arr = [1, 2, 3, 4, 5];

1、如果要获取数组中的最后一项,通常会这样编写代码。

1
arr[arr.length - 1]  // 5

2、我们也可以使用数组的 slice 方法来获取最后一个元素。

1
arr.slice(-1) // 5

当我们将 slice 方法的参数设置为负值时,它会从数组后面开始截取数组值,如果要截取最后两个值,则传入参数-2。

3、使用“at方法”读取

1
2
array.at(-1) // 5
array.at(0) // 1

26、数组元素的随机排序

1
2
3
4
5
const arr = [2,3,6,9,7,4,11,99,7,1,6,5,7,]
const newArr = arr.map(val => ({ v: val, r: Math.random() }));
newArr.sort((a, b) => a.r - b.r);
const showList = newArr.map(val => val.v).slice(0, 6); // 截取长度未6的随机数组
console.log(showList)

四、Vue

1、computed中使用this?

我们通过this能访问到的数据,在computed的第一个参数上都能结构出来

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
data() {
return {
address: '黄山',
}
},
computed: {
haha({ address, $attrs, $route, $store, $listeners, $ref }) {
console.log(address) // 黄山
return 111
}
}
}

2、避免v-if和v-for一起使用

在vue的源码中有一段代码是对指令的优先级的处理

不好的

1
2
3
4
5
6
7
8
9
10
#吐槽:
# 这段代码是先处理v-for再处理v-if
// 比如这个列表有一百条数据,再某种情况下,它们都不需要显示,当vue还是会循环这个100条数据显示,再去判断v-if,因此,我们应该避免这种情况的出现。

<h3 v-if="status" v-for="item in 100" :key="item">{{item}}</h3>

#优化
<template v-if="status" >
<h3 v-for="item in 100" :key="item">{{item}}</h3>
</template>

3、修饰符sync

vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 父组件
template>
<div>
<Toggle :show.sync = 'isShow'></Toggle>
</div>
</template>
<script>
export default {
data () {
return {
isShow: true
}
}
}
<script>

# 等同于上面的写法

// 父组件
template>
<div>
<Toggle :show = 'isShow' @update:show="change"></Toggle>
</div>
</template>
<script>
export default {
props:{
isShow: {
type: Boolean
}
},
data () {
return {
isShow: true
}
},
method: {
change(val) { // 子组件传来的false
this.isShow = val
}
}
}
<script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 子组件
<template>
<div>
<div v-if="show">
展示和隐藏组件
</div>
<button @click="test">隐藏组件</button>
</div>
</template>
<script>

export default {
props:['show'],
methods: {
test(){
this.$emit('update:show',false) //触发 input 事件,并传入新值
}
}
}
</script>

五、IOS和安卓踩坑

1、IOS滑动不流畅

表现

上下滑动页面会产生卡顿,手指离开页面,页面立即停止运动。整体表现就是滑动不流畅,没有滑动惯性。


为什么 iOS 的 webview 中 滑动不流畅,它是如何定义的?

原来在 iOS 5.0 以及之后的版本,滑动 -webkit-overflow-scrolling 有定义有两个值 autotouch,默认值为 auto

1
2
3
-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */

-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */

解决方案

1)在 滚动容器上 增加滚动 touch 方法 

-webkit-overflow-scrolling 值设置为 touch

1
2
3
.wrapper {
-webkit-overflow-scrolling: touch;
}

设置滚动条隐藏: .container ::-webkit-scrollbar {display: none;}

可能会导致使用position:fixed; 固定定位的元素,随着页面一起滚动

2)设置 overflow

设置外部 overflowhidden,设置内容元素 overflowauto。内部元素超出 body 即产生滚动,超出的部分 body 隐藏。

1
2
3
4
5
6
body {
overflow-y: hidden;
}
.wrapper {
overflow-y: auto;
}

两者结合使用更佳!


2、click 点击事件延时与穿透

表现

监听元素 click 事件,点击元素触发时间延迟约 300ms。点击蒙层,蒙层消失后,下层元素点击触发。


为什么会产生 click 延时?

iOS 中的 safari,为了实现双击缩放操作,在单击 300ms 之后,如果未进行第二次点击,则执行 click 单击操作。也就是说来判断用户行为是否为双击产生的。但是,在 App 中,无论是否需要双击缩放这种行为,click 单击都会产生 300ms 延迟。


为什么会产生 click 点击穿透?

双层元素叠加时,在上层元素上绑定 touch 事件,下层元素绑定 click 事件。由于 click 发生在 touch 之后,点击上层元素,元素消失,下层元素会触发 click 事件,由此产生了点击穿透的效果


原理与解决方案

1)使用 touchstart 替换 click

移动设备不仅支持点击还支持触摸事件。那么我们现在基本思路就是用 touch 事件代替click 事件。

click 替换成 touchstart 不仅解决了 click 事件延时问题,还解决了穿透问题。因为穿透问题是在 touchclick 混用时产生。

在原生中使用

1
el.addEventListener("touchstart", () => { console.log("ok"); }, false);

在 vue 中使用

1
<button @touchstart="handleTouchstart()">点击</button>

开源解决方案中,也是既提供了 click 事件,又提供了touchstart 事件。如 vant 中的 button 组件

2)使用 fastclick 库

使用 npm/yarn 安装后使用

1
2
3
import FastClick from 'fastclick';

FastClick.attach(document.body, options);

同样,使用fastclick库后,click 延时和穿透问题都没了


3、软键盘将页面顶起来、收起未回落问题

表现

Android 手机中,点击 input 框时,键盘弹出,将页面顶起来,导致页面样式错乱。

移开焦点时,键盘收起,键盘区域空白,未回落。


产生原因

我们在app 布局中会有个固定的底部。安卓一些版本中,输入弹窗出来,会将解压 absolutefixed 定位的元素。导致可视区域变小,布局错乱。


原理与解决方案

软键盘将页面顶起来的解决方案,主要是通过监听页面高度变化,强制恢复成弹出前的高度。

1
2
3
4
5
6
7
8
9
10
11
// 记录原有的视口高度
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;

window.onresize = function(){
var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
if(resizeHeight < originalHeight ){
// 恢复内容区域高度
// const container = document.getElementById("container")
// 例如 container.style.height = originalHeight;
}
}

键盘不能回落问题出现在 iOS 12+ 和 wechat 6.7.4+ 中,而在微信 H5 开发中是比较常见的 Bug。

兼容原理,1.判断版本类型 2.更改滚动的可视区域

1
2
3
4
5
6
7
8
9
const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);
if (!isWechat) return;
const wechatVersion = wechatInfo[1];
const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);

// 如果设备类型为iOS 12+ 和wechat 6.7.4+,恢复成原来的视口
if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) {
window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}

window.scrollTo(x-coord, y-coord),其中window.scrollTo(0, clientHeight)恢复成原来的视口