1.解析 url 获取键(name)值对

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
function getSearchParam(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); //在这里匹配的,name是变量
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return decodeURIComponent(r[2]);
} else {
return null;
}
}

// 返回对象
function getUrlParams(url) {
if (typeof url != 'string') {
//若参数缺失或类型错误,返回
return {};
}
var query = url.split('#')[0].split('?')[1];
if (!query) {
return {};
}
var params = {};
var paramStrArr = query.split('&');
for (var i = 0; i < paramStrArr.length; ++i) {
var keyValArr = paramStrArr[i].split('=');
var key = decodeURIComponent(keyValArr[0]);
var val =
keyValArr[1] == undefined ? true : decodeURIComponent(keyValArr[1]);
if (key) {
// 考虑 `?a=1&=3` 情况
params[key] = val;
}
}

return params;
}

// 返回对象
function queryParams() {
var params = {};
decodeURIComponent(location.search)
.replace('?', '')
.split('&')
.forEach(function (paramStr) {
var kv = paramStr.split('=');
params[kv[0]] = kv[1];
});
return params;
}
// 返回对象-考虑参数带?的情况
function getUrlParams() {
var params = {};
decodeURIComponent(location.search)
.substring(1)
.split('&')
.forEach(function (paramStr) {
var kv = paramStr.split('=');
params[kv[0]] = kv[1];
});
return params;
}

// 正则实现
const getURLParameters = (url) =>
url
.match(/([^?=&]+)(=([^&]*))/g)
.reduce(
(a, v) => (
(a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a
),
{}
);

2.merge 实现

1
2
3
4
5
6
7
8
9
10
11
12
function mergeParam(array) {
var newJsonObj = {};
for (var i = 0; i < array.length; i++) {
var jsonObj = array[i];
if (jsonObj instanceof Object) {
for (var key in jsonObj) {
newJsonObj[key] = jsonObj[key];
}
}
}
return newJsonObj;
}

3.offsetTop 的稳妥写法

1
2
3
4
5
6
7
8
9
getTop:function(elements ){
var top = elements.offsetTop;
var parent = elements.offsetParent;
while( parent != null ){
top += parent.offsetTop;
parent = parent.offsetParent;
};
return top;
}

4.在即将进入屏幕范围的时候开始加载逻辑

1
2
3
4
5
6
7
8
9
10
showimg: function() {
var preView = 200;
if (this.flag) return;
var position = this.getTop(this.setele) ;
var h = document.body.scrollTop + window.screen.height;
//主要就是下边这个判断:块元素到顶部<滚动距离+窗口高度+设定值(放在这里好理解) 和 后边这句其实就是内容至少要有一屏+设定值
if (position - this.preView <= h && position + this.preView >= document.body.scrollTop) {
this.flag = true;
this.setsrc();
}

5.下拉加载更多

步骤:
一、事件触发加载事件

二、判断 1.滚到底部 2.没有加载完成字样
3.loading 图标没有显示

三、初始化
让 loading 显示(display:block)
让 pageNum +=1;

四、ajax

6.移动端系统判断

1
2
3
4
5
6
7
8
9
var deviceType = (function () {
return {
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //移动端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios
iPhone: u.indexOf('iPhone') > -1, //iphone
iPad: u.indexOf('iPad') > -1, //ipad
android: u.indexOf('Android') > -1, //android
};
})();
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// getCookies
function getCookies() {
var cookies = {};
var all = document.cookie;
if (all === '') return cookies;
var list = all.split('; ');
for (var i = 0; i < list.length; i++) {
var cookie = list[i];
var p = cookie.indexOf('=');
var name = cookie.substring(0, p);
var value = cookie.substring(p + 1);
value = decodeURIComponent(value);
cookies[name] = value;
}
return cookies;
}

// getCookie
function getCookie(name) {
var arr,
reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
if ((arr = document.cookie.match(reg))) return unescape(arr[2]);
else return null;
}

// setCookie
function setCookie(name, value) {
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
document.cookie =
name + '=' + escape(value) + ';expires=' + exp.toGMTString();
}

// setCookie -针对zhuanzhaun
if (cookie != '') {
var cookieArr = cookie.split(';');
var dd = new Date();
var uid = '';
dd.setTime(dd.getTime() + 3600 * 24 * 1000);
if (cookie.indexOf('PPU') > -1) {
uid = cookie.split('PPU=')[1].split(';')[0].split('UID=')[1].split('&')[0];
document.cookie =
'uid=' + uid + ';domain=58.com; path=/; expires=' + dd.toGMTString();
}
cookieArr.map(function (item) {
if (!!item) {
document.cookie =
item + ';domain=58.com; path=/; expires=' + dd.toGMTString();
}
});
}

// delCookie
function delCookie(name) {
var exp = new Date();
exp.setTime(exp.getTime() - 1);
var cval = getCookie(name);
if (cval != null)
document.cookie = name + '=' + cval + ';expires=' + exp.toGMTString(); // 思路是设置过期时间
}

// 删除所有cookie
document.cookie.split(';').forEach(function (c) {
document.cookie = c
.replace(/^ +/, '')
.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
});

//清除所有cookie函数
function clearAllCookie() {
var keys = document.cookie.match(/[^ =;]+(?=\=)/g);
if (keys) {
for (var i = keys.length; i--; )
document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
}
}

8.写死 uid

注意:必须写上 domain 和 path 才行,这样才是对整个域名进行重写
代码内操作:

1
2
3
import Cookies from 'js-cookie';
Cookies.remove('zz_uid');
Cookies.set('zz_uid', '39099770641173', { domain: '58.com', path: '/' });

浏览器内操作:

1
2
3
4
5
document.cookie = `uid=52;domain=zhuanzhuan.com;path=/;expires=Sat, 19 May 2118 03:16:29 GMT`;
或者;
document.cookie =
'rrtr=3133;domain=192.168.149.14;path=/;expires=' +
new Date(Date.now() + 3600 * 24 * 1000 * 3).toGMTString();

9.文本选择器

1
2
3
4
5
6
7
8
9
10
var getParents = document.querySelectorAll('.frequentQ');
for (var i = 0; i < getParents.length; i++) {
if (getParents[i].innerHTML.trim() == '快递费用') {
// innerHTML.trim()这个挺重要的
var that = getParents[i];
that.addEventListener('click', function () {
alert('调用成功');
});
}
}

10.判断是否有网络

1
2
3
if (!navigator.onLine) {
// 假如无网络则执行
}

还有一种方式是事件:

1
2
3
4
5
6
element.addEventListener(window, 'online', function () {
alert('Online');
});
element.addEventListener(window, 'offline', function () {
alert('Offline');
});

11.小数点操作

1
2
3
4
5
6
7
8
9
10
11
12
13
function clearNoNum(obj) {
obj.value = obj.value.replace(/[^\d.]/g, ''); //清除“数字”和“.”以外的字符
obj.value = obj.value.replace(/\.{2,}/g, '.'); //只保留第一个. 清除多余的
obj.value = obj.value
.replace('.', '$#$')
.replace(/\./g, '')
.replace('$#$', '.');
obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3'); //只能输入两个小数
if (obj.value.indexOf('.') < 0 && obj.value != '') {
//以上已经过滤,此处控制的是如果没有小数点,首位不能为类似于 01、02的金额
obj.value = parseFloat(obj.value);
}
}

emoji 转换成字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
utf16toEntities = (str) => {
var patt = /[\ud800-\udbff][\udc00-\udfff]/g; // 检测utf16字符正则
str = str.replace(patt, function (char) {
var H, L, code;
if (char.length === 2) {
H = char.charCodeAt(0); // 取出高位
L = char.charCodeAt(1); // 取出低位
code = (H - 0xd800) * 0x400 + 0x10000 + L - 0xdc00; // 转换算法
return '&#' + code + ';';
} else {
return char;
}
});
return str;
};

锁屏显示页面触发/焦点在标签页上切换触发事件

1
document.addEventListener('webkitvisibilitychange', onVisibilityChanged, false);

onpopstate 事件

1
2
3
4
5
6
7
8
9
10
11
12
window.onpopstate = function (event) {
alert(
'location: ' + document.location + ', state: ' + JSON.stringify(event.state)
);
};
//绑定事件处理函数.
history.pushState({ page: 1 }, 'title 1', '?page=1'); //添加并激活一个历史记录条目 http://example.com/example.html?page=1,条目索引为1
history.pushState({ page: 2 }, 'title 2', '?page=2'); //添加并激活一个历史记录条目 http://example.com/example.html?page=2,条目索引为2
history.replaceState({ page: 3 }, 'title 3', '?page=3'); //修改当前激活的历史记录条目 http://ex..?page=2 变为 http://ex..?page=3,条目索引为3
history.back(); // 弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // 弹出 "location: http://example.com/example.html, state: null
history.go(2); // 弹出 "location: http://example.com/example.html?page=3, state: {"page":3}

这个事件只有单页面路由切换触发,但是真正切换标签页不触发,要用上边的方法
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/onpopstate

判断是否支持事件

1
2
3
4
5
var div = document.createElement('div'),
//是否支持触摸事件
supportTouch = 'ontouchstart' in div,
//是否支持方向转换事件
supportOtc = 'onorientationchange' in window;

判断是否支持样式

1
2
3
4
5
6
7
var element = document.createElement('div');
if (attr in element.style) {
element.style[attr] = value;
return element.style[attr] === value;
} else {
return false;
}

url 规范化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function correctHashRoute() {
let url = location.href,
queryArr = [],
queryStr = '',
splitQuery = (str) => str.substring(1).split('&'),
reg = /^(https?\:\/\/[\w\.\/]+)(\?[^#]+)?(#[^\?&]+)?(.+)?$/,
res = reg.exec(url);

if (!res || !res[3]) return;

if (res[4]) queryArr = queryArr.concat(splitQuery(res[4]));
if (res[2]) queryArr = queryArr.concat(splitQuery(res[2]));
if (!queryArr.length) return;

queryArr = [...new Set(queryArr)];
queryStr = '?' + queryArr.join('&');
location.hash = res[3] + queryStr;
}

判断手机号

1
/^1[34578]\d{9}/g.test(num);

单行写一个评级组件

1
'★★★★★☆☆☆☆☆'.slice(5 - rate, 10 - rate); //rate是变量

用法:

优雅的取随机字符串

1
2
Math.random().toString(16).substring(2); // 13位
Math.random().toString(36).substring(2); // 11位

金钱格式化

1
2
3
var test1 = '1234567890';
var format = test1.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
console.log(format); // 1,234,567,890

曾经提问版本

1
2
'12334565632'.replace(/(\d)(?=(\d{3})+$)/g, '$1,');
//结果:"12,334,565,632"

实现标准 JSON 的深拷贝

1
2
3
4
5
var a = {
a: 1,
b: { c: 1, d: 2 },
};
var b = JSON.parse(JSON.stringify(a));

不过对于undefinedfunction的会忽略掉

最短数组去重

1
[...new Set([1, '1', 2, 1, 1, 3])];

短路表达式

1
2
var a = b && 1;
var a = b || 1;

替换空格和回车

1
2
3
var resultStr = testStr.replace(/\ +/g, ''); //去掉空格
resultStr = testStr.replace(/[ ]/g, ''); //去掉空格
resultStr = testStr.replace(/[\r\n]/g, ''); //去掉回车换行

时间转换

1
2
3
4
let date = new Date(parseInt(1503487641772)); //  必须用new Date 不能用Date.now()
this.dealTime = `${date.getFullYear()}-${
date.getMonth() + 1
}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;

decodeURIComponent 加号不能转换成空格问题

原因:
urlencode 将空格转换成+号
rowurlencode 将空格转换成%20

解决办法:
在调用 decodeURIComponent 函数之前要先把+替换为%20

1
decodeURIComponent(str.replace(/\+/g, '%20'));

解决办法二:
传送之前前端先 encodeURIComponent 一次

vue 转义字符

v-html 可以显示转义字符&nbsp;

给数组添加默认值

1
Array(30).fill(4); // 30个4

参考文献

去掉两端双引号

1
a = a.substring(1, a.length - 1);

JSONP 库的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import jsonp from 'jsonp';

export const JSONP = (url, params) => {
let paramArr = [];
Object.keys(params).forEach(function (key) {
paramArr.push(key + '=' + params[key]);
});
let toStringParam = JSON.stringify(paramArr.join('&'));
let delQuote = toStringParam.substring(1, toStringParam.length - 1);
// let toStringParam = encodeURIComponent(paramArr.join('&'));
return new Promise((resolve, reject) => {
jsonp(
'https://zhuan.58.com/zz/transfer/' + url + '?' + delQuote,
null,
(err, res) => {
if (err) {
reject(Error(err));
} else if (res) {
resolve(res);
}
}
);
});
};

关闭页面发送请求

事件:beforeunload / pagehide 下个说明做了兼容
方法一:通过请求 img

1
2
3
4
window.onbeforeunload = function () {
var img = new Image();
img.src = url; // url进行拼接字符
};

补充:onpagehide 存在兼容问题,有些手机不支持,document.querySelector(‘body’)有些机型不能及时出发
方法二:将 ajax 进行同步执行

1
xmlHttpReq.open('GET', url, false); //第三个参数改成false,使用同步方法

参考文献

兼容关闭页面事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当用户关闭页面时发送数据
if (OS_TYPE().isiOS) {
window.onpagehide = () => {
this.beforeUnloadFun();
};
} else {
window.onbeforeunload = () => {
this.beforeUnloadFun();
};
}
// 以上还是会有问题 -以下完美解决
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.unLoadLego();
}
});

当后台运行时,客户端提供方法

图片等比缩放填充区域

1
2
3
4
<div
class="goods"
style="{{backgroundImage:`url(${util.getHeadImgSize(item.pic)})`}}"
></div>
1
2
3
background-size: cover;
background-repeat: no-repeat;
background-position: center center;

判断输入字节数

方法一:

1
2
3
4
5
String.prototype.len = function () {
return this.replace(/[^\x00-\xff]/g, 'xx').length;
};

alert('文字abc'.len());

方法二:

1
2
3
4
5
6
7
8
9
10
function chkstrlen(str) {
var strlen = 0;
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255)
//如果是汉字,则字符串长度加2 正则[\u4e00-\u9fa5]
strlen += 2;
else strlen++;
}
return strlen;
}

自己开发的字节限制

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
dealChAndEn(str,max,endChar){
let strlen = 0;
let strResult;
let realyLen = 0;
for(var i = 0;i < str.length; i++) {
if(str.charCodeAt(i) > 255){ //如果是汉字,则字符串长度加2
strlen += 2;
}else {
strlen++;
}
if(strlen>=max){
realyLen = i;
strResult = str.substring(0,i+1);
break;
}
}
if(strlen<max){
strResult = str
}
return {
strResult: endChar ? strResult + endChar : strResult,
realyLen: realyLen,
strlen: strlen
}
}

break,continue,return 区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// break 直接跳出
for (var i = 1; i <= 10; i++) {
if (i == 8) {
break;
}
document.write(i); // 1234567
}

// continue 跳出本句之后继续迭代
for (var i = 1; i <= 10; i++) {
if (i == 8) {
continue;
}
document.write(i); // 1234567910
}

// return 必须在函数内,否则报错

渐变兼容模式

1
2
background: linear-gradient(-17deg, #8cd7fe 0%, #a5dafc 100%);
background: -webkit-linear-gradient(-17deg, #8cd7fe 0%, #a5dafc 100%);

参考文献

json-server 监听模式

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
44
45
46
47
48
49
50
var fs = require('fs');
var path = require('path');

var db = {};

module.exports = function () {
var files = scanDir(path.join(__dirname, './data')); // 获取文件列表
files.forEach((file) => {
// 构建数据库
db[path.basename(file).split('.')[0]] = require(file);
});

files.forEach((file) => {
// 监听文件变化
fs.watch(file, () => {
delete require.cache[path.resolve(file)]; // 清除require中对应缓存,以获取文件最新内容
try {
db[path.basename(file).split('.')[0]] = require(file); // 更新数据库中对应数据
} catch (e) {
// 文件正在修改时可能存在语法错误,不能正常require,此时不予处理
}
});
});

return db;
};

/**
* 遍历文件夹
* @param dir
* @returns {Array}
*/
function scanDir(dir) {
var files = [];
scan(dir);

function scan(_dir) {
if (fs.statSync(_dir).isFile() && _dir.indexOf('.DS_Store') < 0) {
return files.push(_dir);
}
return (
fs.statSync(_dir).isDirectory() &&
fs.readdirSync(_dir).forEach((file) => {
scan(_dir + '/' + file);
})
);
}

return files;
}

获取文本宽度

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
document.getElementById('span').onclick = function () {
var objRectList = this.getClientRects(),
length = objRectList.length;
console.log(objRectList);
var string = '有' + length + '个矩形\r\n';
for (var i = 0; i < length; i += 1) {
string +=
'第' +
(i + 1) +
'个矩形: top:' +
objRectList[i].top +
', right:' +
objRectList[i].right +
', bottom:' +
objRectList[i].bottom +
', left:' +
objRectList[i].left +
', width:' +
objRectList[i].width +
', height:' +
objRectList[i].height +
'\r\n';
}
alert(string);
};

参考文献 1
参考文献 2

折叠全文库-溢出省略号显示

https://github.com/josephschmitt/Clamp.js/blob/master/clamp.js

1
2
3
4
5
6
$clamp(node, {
clamp: 3,
useNativeClamp: false,
truncationChar: ' ',
truncationHTML: '<span class="setColor">Read more.这里可以是连接</span>',
});

纯 css 方法:

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
44
<html lang="zh-CN">
<head>
<title>省略号</title>
</head>
<style>
.demo {
background: #099;
max-height: 40px;
line-height: 20px;
overflow: hidden;
}
.demo::before {
float: left;
content: '';
width: 20px;
height: 40px;
background: green;
}

.demo .text {
float: right;
width: 100%;
margin-left: -20px;
word-break: break-all;
}
.demo::after {
float: right;
content: '...';
width: 20px;
height: 20px;
background: red;
position: relative;
left: 100%;
transform: translate(-100%, -100%);
}
</style>
<body>
<div class="demo">
<div class="text">
这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本
</div>
</div>
</body>
</html>

可能是最全的 “文本溢出截断省略” 方案合集
用法参考

浮窗被键盘挡住

前提 1:必须是 absolute,不能是 fixed
前提 2:暂不支持 IOS9.3.5 和 11.1 会出现 bug

1
2
3
4
5
6
7
8
<input
@focus="focusInput"
@blur="blurInput"
v-model="replyContent"
type="text"
class="input"
placeholder="我来帮助TA"
/>
1
2
3
4
5
focusInput () {
this.setIntervalScroll = setInterval(function () {
document.body.scrollTop = document.body.scrollHeight;
}, 200);
}

其他人写法

1
2
3
4
setTimeout(() => {
window.scrollTo(0, 1000000);
this.$refs.chatWrap.scrollTop = 999999;
}, 500);

长按复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
div,
form,
html,
li,
p,
span,
ul,
h2,
h3,
input {
user-select: initial;
-webkit-user-select: initial;
-webkit-touch-callout: initial;
touch-callout: initial;
}

推荐还是在要复制的部分加上,否则如果是单页面应用的话,所有页面都可以复制就不太好了

1
2
3
4
.title {
user-select: text;
-webkit-user-select: text;
}

底部浮窗实现方法

方法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.wrap {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
display: flex;
flex-direction: column;
.top {
flex: 1;
overflow-y: auto; // 重要
-webkit-overflow-scrolling: touch;
}
.bottom {
flex: auto;
height: 2.56rem;
}
}

方法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.wrap {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
display: flex;
flex-direction: column;
.top {
top: 0;
left: 0;
right: 0;
bottom: 110px;
position: absolute;
overflow-y: auto; // 重要
-webkit-overflow-scrolling: touch;
}
.bottom {
position: absolute;
bottom: 0;
}
}

横向不换行,超出滚动

1
2
3
4
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
-webkit-overflow-scrolling: touch; // 回弹效果

bord 和 border 区别

bold 粗体
bolder 比粗体更加粗
lighter 比正常字体淡
100 至少和 200 一样淡
200 至少和 100 一样粗,至少和 300 一样淡
300 至少和 200 一样粗,至少和 400 一样淡
400 字体正常显示,相当于 normal。
500 至少和 400 一样粗,至少和 600 一样淡
600 至少和 500 一样粗,至少和 700 一样淡
700 粗体,相当于 bold。
800 至少和 700 一样粗,至少和 800 一样淡
900 至少和 800 一样粗

移动端控制台 vConsole

1
2
3
4
<script src="https://wechatfe.github.io/vconsole/lib/vconsole.min.js?v=3.0.0.0"></script>
<script>
var vConsole = new VConsole();
</script>

IOS11 底部固定浮动窗不浮动问题

办法一:
当底部按钮为一个 input 输入框的时候,底部浮窗会跟随输入法一起弹起
如果为 div 则不会跟随弹起

办法二: 不可用-因为这个方法是让高度超过 100%的时候,才用到,而 ios11.1 屏幕挡住部分也算在 100%内
通过 scrollIntoView 方法使元素强制显示到窗口内

1
document.querySelector('#result-area').scrollIntoView(true);

注意:如果上述方法仍不能实现
可能你用了 scrollTop 类代码~请去掉

判断 IOS 版本

1
2
3
4
5
6
7
8
9
10
11
12
let IOS_V = (a) => {
let v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
if (v) {
v = parseFloat(v[1] + '.' + v[2], 10);
if (v - a > 0) {
return 1; // 返回1为大于指定的版本
} else if (v == a) {
return 0; // 等于
}
return -1; // 小于
}
};

反击爬虫,前端工程师的脑洞可以有多大?

1
http://litten.me/2017/07/09/prevent-spiders/

// 没有加域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function setCookie(name, value) {
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
document.cookie =
name + '=' + escape(value) + ';expires=' + exp.toGMTString();
}
setCookie('id58', 'c5/nT1mv37MErFV/BBRRAg==');
setCookie('t', '15');
setCookie('tk', '1FC7096BB25439E8134C48641EE33734');
setCookie('v', '3.6.99');
setCookie('channelid', 'unknown');
setCookie('lat', '39.987035');
setCookie('lon', '116.504727');
setCookie('osv', '25');
setCookie('model', 'MI+5X');
setCookie('brand', 'xiaomi');

-webkit-overflow-scrolling: touch;的 BUG

当你给一个元素设置过 position:absolute;或者 position:relative;后再增加-webkit-overflow-scrolling: touch;属性后,你会发现,滑动几次后可滚动区域会卡主,不能在滑动,这时给元素增加个 z-index 值就可以了。

1
2
3
-webkit-overflow-scrolling: touch;
position: absolute;
z-index: 1;

vue-resource 跨域

1
Vue.http.post(API.REPLAY_COMMENT, { params }, { credentials: true });

初始化字体

1
2
3
4
5
6
7
body {
font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC',
'Hiragino Sans GB', 'Heiti SC', 'Microsoft YaHei', 'WenQuanYi Micro Hei',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

清除选中文本

1
2
3
4
5
6
7
8
9
10
11
12
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}

禁止键盘输入

1
2
3
4
if (event.keyCode == 101) {
// 禁止输入e
event.returnValue = false;
}

禁用参考文档

禁止切换输入法

有兼容问题

1
<input type="tel" style="ime-mode:disabled;" />

时间格式化

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
/*
* @description 格式化日期
* @param {String} format -必填项,显示格式
* @param {String} yyyy-MM-dd hh:mm:ss --分别表示 年-月-日 时-分-秒
* @return {Undefined}
* @example
* new Date().Format("yyyy年MM月dd日")
* new Date().Format("MM/dd/yyyy")
* new Date().Format("yyyyMMdd")
* new Date().Format("yyyy-MM-dd hh:mm:ss")
* */
const formatTimes = () => {
Date.prototype.Format = function (format) {
var o = {
'M+': this.getMonth() + 1, //month
'd+': this.getDate(), //day
'h+': this.getHours(), //hour
'm+': this.getMinutes(), //minute
's+': this.getSeconds(), //second
'q+': Math.floor((this.getMonth() + 3) / 3), //quarter
S: this.getMilliseconds(), //millisecond
};
if (/(y+)/.test(format)) {
format = format.replace(
RegExp.$1,
(this.getFullYear() + '').substr(4 - RegExp.$1.length)
);
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(
RegExp.$1,
RegExp.$1.length == 1
? o[k]
: ('00' + o[k]).substr(('' + o[k]).length)
);
}
}
return format;
};
};

超长字符串转数字

1
2
(1.4000000000000001).toPrecision(12);
// toFixed

保留小数后几位+转万

harmony
1
2
3
if (val > 10000) {
return (val / 10000).toFixed(1) + '万';
}

参考

Vue 出现删除光标前有空格 Bug

computed 造成的,去掉 computed 属性

Array.from 应用

harmony
1
Array.from(arrayLike, mapFn, thisArg);

arrayLike
想要转换成数组的伪数组对象或可迭代对象。
mapFn (可选参数)
如果指定了该参数,新数组中的每个元素会执行该回调函数。
thisArg (可选参数)
可选参数,执行回调函数 mapFn 时 this 对象。

harmony
1
2
3
4
5
6
// 方法一:
Array.from([1, 2, 3], (x) => x + x);
// [2, 4, 6]
// 方法二:
Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]

Array.prototype.reduce()

harmony
1
arr.reduce([callback, initialValue]);

callback
执行数组中每个值的函数,包含四个参数:
previousValue
上一次调用回调函数返回的值,或者是提供的初始值(initialValue)
currentValue
数组中当前被处理的元素
currentIndex
当前被处理元素在数组中的索引, 即 currentValue 的索引.如果有 initialValue 初始值, 从 0 开始.如果没有从 1 开始.
array
调用 reduce 的数组
initialValue
可选参数, 作为第一次调用 callback 的第一个参数。

harmony
1
2
3
[0, 1, 2, 3, 4].reduce((previousValue, currentValue, currentIndex, array) => {
return previousValue + currentValue;
}, 10);

getBoundingClientRect

获取盒子的坐标

style、getComputedStyle

style 只能获取行间样式,但能设置样式。

getComputedStylecurrentStyle 能够获取 行间样式/非行间样式/浏览器默认样式,但存在浏览器兼容问题,且不能设置样式

分组正则

harmony
1
2
url.match(/([^?=&]+)(=([^&]*))/g);
// 'http://url.com/page?name=Adam&surname=Smith&aa=&bb='.match(/([^?=&]+)(=([^&]*))/g)

这个是从做到右看

1
([^?=&]+)

匹配到

1
 ["http://url.com/page", "name", "Adam", "surname", "Smith", "aa", "bb"]

然后又会让上述这些匹配到的进行向后匹配

1
(=())

会匹配到

1
 ["name=", "surname=", "aa=", "bb="]

如果后边再加上([^&]*)内容的话就会匹配的更多

1
 ["name=Adam", "surname=Smith", "aa=", "bb="]

字符串乱序

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const anagrams = (str) => {
if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str]; // 这个判断去掉也没有问题-加这句只是让运算更快,因为两位以下互换意义不大
return str
.split('')
.reduce(
(acc, letter, i) =>
acc.concat(
anagrams(str.slice(0, i) + str.slice(i + 1)).map(
(val) => letter + val
)
),
[]
);
};
// anagrams('abc') -> ['abc','acb','bac','bca','cab','cba']

数字精度处理

harmony
1
2
num.toPrecision('1~21之间'); // 是处理精度,精度是从左至右第一个不为0的数开始数起。
num.toFixed('0~20之间'); // 是小数点后指定位数取整,从小数点开始数起。

Big 插件
JavaScript 浮点数陷阱及解法

精度丢失原因

计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926…,1.3333… 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。如图

意义:
1 位用来表示符号位
11 位用来表示指数
52 位表示尾数

浮点数转化成二进制出现无限循环的情况,而 52 位尾数不够用,出现舍入问题,造成精度丢失
例如:

0.1 >> 0.0001 1001 1001 1001…(1001 无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011 无限循环)

此时只能模仿十进制进行四舍五入了,但是二进制只有 0 和 1 两个,于是变为 0 舍 1 入。这即是计算机中部分浮点数运算时出现误差,丢失精度的根本原因。

以上,可以知道看似有穷的数字, 在计算机的二进制表示里却是无穷的,由于存储位数限制因此存在“舍去”,精度丢失就发生了。

浏览器内核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//判断访问终端
var browser = {
versions: (function () {
var u = navigator.userAgent,
app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端
iPhone: u.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
qq: u.match(/\sQQ/i) == ' qq', //是否QQ
};
})(),
language: (navigator.browserLanguage || navigator.language).toLowerCase(),
};

svg 或 canvas 保存为 png

HTMLCanvasElement.toDataURL()
d3-downloadable

tr td 无法设置边框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
方法1:

table{
border-collapse: collapse;
}

tr{
border-bottom: 1px solid red;
}

解释,border-collapse: collapse; 将table,th和td的边框合成单一的边框,此时在使用tr就可以达到目的。


方法2:
先在table标签里面设置cellspacing等于0,

<table cellspacing="0">

td{
border-bottom: 1px solid red;
}

解释,如何不设置cellspacing等于0,直接设置td的下边框,则下边框会不连续,因为cellspacing不为零,单元格于单元格之间存在间隙。

img 灰色默认外边框的去除

全局样式

1
2
3
4
img[src=''],
img:not([src]) {
opacity: 0;
}

img 灰色默认外边框的去除

授权接口

1
https://xxxxx.xx.com/activity/comm/shouQuan?callbackUrl=https%3A%2F%2Fm.zhuanzhuan.58.com%2FMzhuanzhuan%2Fwechat%2Fzzhelp%2Fguide.html

授权接口和临时票据不一样

location.origin 兼容

1
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''

返回扩展名

1
2
'emoji.pnn.png'.match(/\.[^.]*/g);
//  [".pnn", ".png"]

图片获取失败

1
2
3
4
5
function nofind() {
var img = event.srcElement;
img.src = 'images/logoError.png';
img.onerror = null; // 控制不要一直跳动
}
1
<img src="images/logo.png" onerror="nofind();" />

CSS 写自适应大小的正方形

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
<style type="text/css">
/*background 写法*/
.img {
width: 100%;
height: 0;
padding-bottom: 100%; /*关键所在*/
overflow: hidden;
background: url(../res/images/haha.png) center/100% 100% no-repeat;
}
.img img {
width: 100%;
}
/*img 写法*/
.img {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%; /*关键所在*/
overflow: hidden;
}
.img img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
<div class="img"></div>

不定期更新的 CSS 奇淫技巧

版本号比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//计算版本号大小,转化大小
function toNum(a) {
var a = a.toString();
//也可以这样写 var c=a.split(/\./);
var c = a.split('.');
var num_place = ['', '0', '00', '000', '0000'],
r = num_place.reverse();
for (var i = 0; i < c.length; i++) {
var len = c[i].length;
c[i] = r[len] + c[i];
}
var res = c.join('');
return res;
}
function cpr_version(a, b) {
var _a = toNum(a),
_b = toNum(b);
if (_a == _b) console.log('版本号相同!版本号为:' + a);
if (_a > _b) console.log('版本号' + a + '是新版本!');
if (_a < _b) console.log('版本号' + b + '是新版本!');
}
var a = '2.2.3';
b = '2.1.15';
cpr_version(a, b);

visibilitychange 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var hidden, visibilityChange;
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.mozHidden !== 'undefined') {
hidden = 'mozHidden';
visibilityChange = 'mozvisibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}

// 添加监听器
document.addEventListener(
visibilityChange,
function () {
console.log('当前页面是否被隐藏:' + document[hidden]);
},
false
);

捕捉错误

1
2
3
window.onerror = function (message, url, line, column, error) {
console.log('log---onerror::::', message, url, line, column, error);
};

box-shadow

1
2
3
.box-shadow-ps(@a,@b,@c,@d,@e) {
box-shadow: (@c*cos (180-@b)) (@c*sin (180-@b)) (@e - @d* @e) (@d* @e) @a;
}

正则切分 url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let url = location.href,
queryArr = [],
queryStr = '',
splitQuery = (str) => str.substring(1).split('&'),
reg = /^(https?\:\/\/[\w\.\/]+)(\?[^#]+)?(#[^\?&]+)?(.+)?$/,
res = reg.exec(url);

if (!res || !res[3]) return;

if (res[4]) queryArr = queryArr.concat(splitQuery(res[4]));
if (res[2]) queryArr = queryArr.concat(splitQuery(res[2]));
if (!queryArr.length) return;

queryArr = [...new Set(queryArr)]; // 去重
queryStr = '?' + queryArr.join('&');
location.hash = res[3] + queryStr; // 这个是将参数放在了hash后边

更好的正则匹配 url

1
2
3
/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/.exec(
location.href
);

宏任务微任务

先同步 → 微任务 → 宏任务

宏任务: setTimeout setInterVal
微任务: Promise 的 then 内的任务

1
2
3
4
5
6
7
8
9
10
11
setTimeout(function () {
console.log('4');
}, 10);
new Promise(function (resolve) {
console.log('5');
setTimeout(resolve, 3000);
}).then(function () {
console.log('6');
});

// 5 4 6

Node 下:
宏任务放在一起执行算一个队列

时间转换

1
2
3
new Date(2009, 1, 1); //正确

new Date('2009/1/1 10:00:00'); //正确

简单深拷贝

1
2
var a = {}; //属性自己填
var b = JSON.parse(JSON.stringify(a));

删除 node_modules

1
rimraf node_modules

animate 不动问题

1
2
3
4
5
6
7
8
9
10
11
12
.active {
-webkit-animation: spring 0.6s linear infinite;
}

@-webkit-keyframes spring {
from {
transform: rotateY(0);
}
to {
transform: rotateY(360deg);
}
}

注意:不能含有不带-webkit 的样式,否则也不生效

对象排序

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [
{ name: 'abc', age: 20 },
{ name: 'Cde', age: 19 },
{ name: 'dfc', age: 25 },
{ name: 'Bde', age: 21 },
];
arr.sort(function (a, b) {
var s = a.name.toLowerCase();
var t = b.name.toLowerCase();
if (s < t) return -1;
if (s > t) return 1;
});
console.log(arr);

终止 forEach 语句

1
2
3
try {
throw new Error('抛错来终止');
} catch (e) {}

通过 every 或 some 来替代

logN

如果 a 的 x 次方等于 N(a>0,且 a 不等于 1),那么数 x 叫做以 a 为底 N 的对数(logarithm),记作 x=logaN。

跳转到 ios 应用商店

1
2
3
"https://itunes.apple.com/us/app/apple-store/id_MY_APP_ID"

"itms-apps://itunes.apple.com/us/app/apple-store/id_MY_APP_ID"

本地城市按拼音排序

1
2
3
4
var list = ['王', '张', '李'];
list.sort(function (a, b) {
return a.localeCompare(b, 'zh');
});

获取路径区分

1
2
3
4
5
6
7
8
9
10
11
const path = require('path')
console.log('__dirname:', __dirname)
console.log('__filename:', __filename)
console.log('process.cwd():', process.cwd())
console.log('./:', path.resolve('./'))

// 结果
__dirname: /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs
__filename: /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs/2.path.js
process.cwd(): /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs
./: /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs

总结:
dirname: 获得当前执行文件所在目录的完整目录名 filename: 获得当前执行文件的带有完整绝对路径的文件名
process.cwd():获得当前执行 node 命令时候的文件夹目录名
./: 不使用 require 时候,./与 process.cwd()一样,使用 require 时候,与__dirname 一样

引入文件夹内文件

1
2
3
4
const requireAll = (requireContext) =>
requireContext.keys().map(requireContext);
const req = require.context('@/assets/img/icons/svg', false, /\.svg$/);
const iconMap = requireAll(req);

webpack 中 require.context 的使用

去掉 whistle 的 https 安全问题

window:
chrome 的快捷方式-属性-快捷方式-目标 后面加上

1
(空格)--disable-infobars --ignore-certificate-errors

访问 https 的资源就不会出现安全拦截了

Mac:
https://www.jianshu.com/p/a8cc2c04ee7c

浮层禁止底部滑动

PC 端:

1
overscroll-behavior: contain;

复制内容到剪贴板

复制内容到剪贴板

clipboard.js 兼容 ios

1
2
3
4
5
6
7
8
9
10
11
12
13
<span id="foo" data-clipboard-target="#foo" data-clipboard-action="copy"
>哈哈,我被复制了</span
>
改为
<span
style="cursor: pointer"
onclick=""
class="btn"
id="foo"
data-clipboard-target="#foo"
data-clipboard-action="copy"
>哈哈,这下ios可以复制我了</span
>

给 span 加了一个 css 属性:cuosor:pointer;以及 onclick=””,这个空事件,原因是 ios 默认非点击标签不具有点击效果,所以给这些标签添加相关属性,这样系统可以识别出来!

参考文献

页面内按需加载

1
2
const privacyAgreement = () =>
require.ensure([], () => require('./privacyAgreement'));
1
2
3
// 组件按需加载
require(['./my-async-component'], resolve)
'text-list': () => import('../../../components/common/text-list/index')

穿透 scope

stylus 的样式穿透 使用>>>

1
2
3
4
5
外层 >>> 第三方组件
样式

.wrapper >>> .swiper-pagination-bullet-active
background: #fff

sass 和 less 的样式穿透 使用/deep/

1
2
3
4
5
6
外层 /deep/ 第三方组件 {
样式
}
.wrapper /deep/ .swiper-pagination-bullet-active{
background: #fff;
}

同路由跳转改变参数视图不刷新

https://blog.csdn.net/cmyh100/article/details/79388186
https://my.oschina.net/u/3970421/blog/2995258

1
<router-view v-if="$route.meta.noAlive" :key="$route.fullPath"></router-view>

兼容打开和关闭浏览器页面

1
2
3
4
5
6
Vue.prototype.$close = () => { window.opener = null window.open('', '_self')
window.close() } Vue.prototype.$open = (obj) => { const { path = '', query = {}
} = obj let queryString = '' for (var item in query) { if
(query.hasOwnProperty(item)) { queryString = `${queryString}${queryString ? '&'
: ''}${item}=${query[item]}` } }
window.open(`//gm.zhuanzhuan.com/qc${path}?${queryString}`) }

解决取不到值的插件-安全值

@babel/plugin-proposal-optional-chaining

函数解决:

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
function safeNestValue(origin, keyStr, defaultValue) {
var result;
try {
result = eval('origin.' + keyStr);
} catch (e) {}
return result === void 0 ? defaultValue : result;
}

var a = {
b: {
c: {
d: {
e: [
1,
2,
{
f: 'g',
},
],
},
},
},
};

safeNestValue(a, 'b.c.d.e.h', 999);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var safeNestValue = (origin, keyStr, defaultValue) => {
let keyArr = keyStr.split('.').reduce((t, c) => {
return t.concat(
c.split(/(\[|\])/).filter((item) => item && item !== '[' && item !== ']')
);
}, []);
const fn2 = (o, arr) => {
if (arr.length === 0) return o;
const key = arr.splice(0, 1);
const result = o[key];
if (result === void 0) {
return defaultValue;
}
return fn2(result, arr);
};
return fn2(origin, keyArr);
};

无报错链式取值的几种方法

XXS

1
login?redirect=javascript%3Aalert%28document.cookie%29
1
javascript: alert(document.cookie);

js 去除数组中的空值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方法一:
var u=[undefined,undefined,1,'','false',false,true,null,'null'];
u.filter(d=>d);

方法二:
Array.prototype.clean = function(deleteValue) {
for (var i = 0; i < this.length; i++) {
if (this[i] == deleteValue) {
this.splice(i, 1);//返回指定的元素
i--;
}
}
return this;
};
方法三:
https://babeljs.io/docs/en/next/babel-plugin-proposal-optional-chaining.html

多维数组扁平化

多维数组扁平化
5 种方法实现数组扁平化

1
2
3
4
5
function flatten(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}

json-server 详细说明

json-server 详解
https://www.jianshu.com/p/5bb86a770e23
json-server 常用自定义路由和简单配置

静态方法和私有属性

ES6 class 静态属性和私有方法
Es6 类 class 的关键 super、static、constructor、new.target

判断一个对象是否为空

1
2
3
4
5
6
7
8
// 方法一
function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
}
// 方法二
function isEmptyObject(obj) {
return JSON.stringify(data) === '{}';
}

js 判断一个 object 对象是否为空

vue watch 的 deep 应用

Vue.js 中 watch 的高级用法

任意文件转 base64

https://www.zhangxinxu.com/sp/base64.html

字体压缩裁剪

字体子集裁剪
字体压缩
fontmin

字符串转变量名

在 Javascript 中将字符串转换为变量名称
Javascript 中字符串转为变量名的 4 种解决方案

window.onload

关于 JS 的–window.onload()方法

forEach async await

一边用 for…of 来遍历
当 async/await 遇上 forEach

禁止蒙层滚动

禁止蒙层底部页面跟随滚动

sticky 不生效

1、sticky 不会触发 BFC。如果不知道 BFC 可以看这里。
2、样式表 z-index 无效。行内 style 写有效。
3、sticky 是容器相关的,也就说 sticky 的特性只会在他所处的容器里生效。
4、元素最近的祖先元素 overflow 设置为非默认值 visible 时,则元素相对于该祖先元素进行 sticky 定位。若最近的祖先元素设置为 overflow:hidden,则元素不会 sticky 定位
position: sticky 详解(防坑指南)
position:sticky 的兼容性尝试

禁用橡皮筋

方法一:
css overscroll-behavior: none

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
const delBungee = () => {
var startY, endY;
//记录手指触摸的起点坐标
document.querySelector('body').addEventListener(
'touchstart',
function (e) {
startY = e.touches[0].pageY;
},
{ passive: false }
);
document.querySelector('body').addEventListener(
'touchmove',
function (e) {
endY = e.touches[0].pageY; //记录手指触摸的移动中的坐标
// console.log('startY', startY)
// console.log('endY', endY)
// console.log('window.scrollY', window.scrollY)
//手指下滑,页面到达顶端不能继续下滑
if (
((endY > startY || startY === 'undefined') && window.scrollY <= 0) ||
window.scrollY === 0
) {
e.preventDefault();
}
//手指上滑,页面到达底部能继续上滑
if (
endY < startY &&
window.scrollY + document.body.clientHeight >=
document.querySelector('body').scrollHeight
) {
e.preventDefault();
}
},
{ passive: false }
);
};

移动端 iOS 阻止橡皮筋效果
iOS safari 如何阻止“橡皮筋效果”?

拦截监听事件

1
2
3
4
5
6
7
8
9
10
(function () {
Element.prototype._addEventListener = Element.prototype.addEventListener;
Element.prototype.addEventListener = function (a, b, c) {
this._addEventListener(a, b, c);
if (!this.eventListenerList) this.eventListenerList = {};
if (!this.eventListenerList[a]) this.eventListenerList[a] = [];
this.eventListenerList[a].push(b);
};
})();
// 记得Element.removeEventListener

判断当前节点和另外一个节点位置

compareDocumentPosition
获取索引值
高效的获取当前元素是父元素的第几个子元素

1
2
3
4
5
6
7
8
getNodeIndex = (ele) => {
var i = 0;
while (ele.previousSibling) {
ele = ele.previousSibling;
i++;
}
return i;
};

自定义事件+监听 replaceState 和 pushState 行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var _wr = function (type) {
var orig = history[type];
return function () {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');

// 监听
window.addEventListener('replaceState', function (e) {
console.log('THEY DID IT AGAIN! replaceState 111111');
});
window.addEventListener('pushState', function (e) {
console.log('THEY DID IT AGAIN! pushState 2222222');
});

// 如果不区分前进还是后退 可以直接监听popstate事件

如何监听 URL 的变化?

优化滚动事件

passive 优化滚动事件

判断两个 DOM 是否相同

1
2
3
4
// 方法一:
Node.isEqualNode();
// 方法二:
// 直接用===

怎么判断某个 dom 节点是否包含某个 dom 节点?

两个新监听 API

IntersectionObserver
更优雅的实现元素是否在 viewport 监听(新 api)
MutationObserver
深入 MutationObserver
补充:
getBoundingClientRect
可以检测距离视窗距离,来实现懒加载,但是每次出现到视窗都会触发,所以需要再兼容一次

MessageChannel 和 web worker

MessageChannel 是什么,怎么使用?

postMessage

其中分为
window.postMessage() – 可用于 iframe 中通信,
还有 MessageChannel 中的
MC 实例.postMessage()
还有 web worker 实例中的
worker 实例.postMessage()

0.5px

1
2
3
4
5
6
7
8
9
10
meta.setAttribute(
'content',
'initial-scale=' +
1 / dpr +
', maximum-scale=' +
1 / dpr +
', minimum-scale=' +
1 / dpr +
', user-scalable=no'
);

iframe 嵌入 html

iframe 展示特定 html 代码
iframe 动态添加 html

利用 history 来监听返回键

vue 实现微信浏览器左上角返回按钮拦截功能
vue 的 router.back 用的是 history.go 实现的

node-sass 安装不上

1
2
3
4
5
6
7
// linux、mac 下
SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ npm install node-sass

// window 下
set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ && npm install node-sass
set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ ; npm install node-sass
npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

[node 安装 node-sass 失败,配置淘宝源]https://blog.csdn.net/a295277302/article/details/70992404
为什么 node-sass 总是安装失败?

###用户调整手机字体大小

移动端 rem 布局,用户调整手机字体大小或浏览器字体大小后导致页面布局出错问题

微前端

qiankun(乾坤)

debugx5.qq.com

前端 input file 图片压缩

前端 input file 图片压缩

canvas 转 blob

base64 转 blob

https://blog.csdn.net/weixin_44186072/article/details/95941972

https://www.cnblogs.com/ihuangjianxin/p/9405107.html

https://www.cnblogs.com/teemor/p/12580510.html

base64 转 blob

Observers

JS 中的观察者们 —— 四种 Observers

自动化测试

cypress

RxJS

https://github.com/tc39/proposal-observable#observable
https://rxmarbles.com/
https://thinkrx.io/

SameSite=None and Secure

解决方案 1.地址栏:chrome://flags 2.搜索:SameSite by default cookies 3.选择:disabled
https://blog.csdn.net/u014636209/article/details/105198731

修改原型

defineGetterdefineSetter

回车搜索

input[type=search] onsearch 事件

HTML5 去除 input [type=search] 的默认边框和删除按钮

vue 动画

vue-transition 动画

清理定时器

1
2
3
4
5
6
7
8
const timer = setInterval(() => {
//具体执行代码
console.log('1');
}, 1000);
this.$once('hook:beforeDestory', () => {
clearInterval(timer);
timer = null;
});

清理定时器

await 错误处理

方法一:
try catch
方法二:
封装函数

1
2
3
const awaitWrap = (promise) => {
return promise.then((data) => [null, data]).catch((err) => [err, null]);
};

async/await 的错误处理方法总结与优化

content-visibility

content-visibility: 一个可以提高渲染性能的 css 属性

无固定高度没有动画

用 max-height 来代替
初始值设置
max-height: 36px;
js 控制
max-height: 1000px;

v-else 闪屏

1
2
3
4
5
<div v-else v-cloak>Not A/B/C</div>

[v-cloak] {
display:none;
}

获取xpath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var getPath = function(elem){
if(elem.id != ''){
return '//*[@id=\"'+elem.id+'\"]';
}
if(elem == document.body){
return '/html/'+elem.tagName.toLowerCase();
}
var index = 1,siblings = elem.parentNode.childNodes;
for(var i = 0,len = siblings.length;i<len;i++){
var sibling = siblings[i];
if(sibling == elem){
return arguments.callee(elem.parentNode)+'/'+elem.tagName.toLowerCase()+'['+(index)+']';
}else if(sibling.nodeType==1&&sibling.tagName == elem.tagName){
index++;
}
}
}

方法二:
前端全(无)埋点之页面操作路径可视化

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
function ellocate (el) {
var locator = { xpath: '', css: ''};
var eloc = {
getClass: function(el) {
var formatClass = '';
var elementClass = el.className;
if(typeof elementClass != 'undefined' && elementClass != ''){
formatClass = '.' + elementClass.split(/[\s\n]+/).join('.');
}
return formatClass;
},
index: function(el) {
var elements = el.parentNode.children;
for (var i=0;i<elements.length;i++) {
if( elements[i] == el){
return i;
}
}
}
};
for (; el && el.nodeType == 1; el = el.parentNode) {
var idx = eloc.index(el);
if(el.tagName.substring(0,1) != "/" && el.tagName.toLowerCase() !== 'body' && el.tagName.toLowerCase() !== 'html') { //IE oddity: some tagNames can begin with backslash.
if(el.id != 'undefined' && el.id !='') {
var idPath="[@id=" + "'" + el.id + "'" + "]";
locator.xpath = '/' + el.tagName.toLowerCase() + idPath + locator.xpath;
locator.css = el.tagName.toLowerCase() + '#' + el.id + ' > ' + locator.css;
}
else {
idx='[' + idx + ']';
locator.xpath = '/' + el.tagName.toLowerCase() + idx + locator.xpath;
locator.css = el.tagName.toLowerCase() + eloc.getClass(el) + ' > ' + locator.css;
}
}
}
locator.xpath = '//html[1]/body[1]/' + locator.xpath;
locator.css = locator.css.substr(0, locator.css.length-3);
return locator;
};

其他方法

字符串转函数

方法一: eval
方法二:setTimeout(..)setInterval(..) 的第一个参数和eval类似
方法三: do {} es7的语法
方法四: new Function

参考文献:
https://blog.csdn.net/daihaoxin/article/details/106213871
https://mp.weixin.qq.com/s/zpg-s0mVWfya6gXx73AVjQ

手写 lodash/get、lodash/set 方法

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
function get(object, path, defaultValue) {
// 判断 object 是否是数组或者对象,否则直接返回默认值 defaultValue
if (typeof object !== 'object') return defaultValue
// 沿着路径寻找到对应的值,未找到则返回默认值 defaultValue
return _basePath(path).reduce((o, k) => (o || {})[k], object) || defaultValue
}

// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用
function _basePath(path) {
// 若是数组,则直接返回
if (Array.isArray(path)) return path
// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']'
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
}

function set(object, path, value) {
if (typeof object !== 'object') return object;
_basePath(path).reduce((o, k, i, _) => {
if (i === _.length - 1) { // 若遍历结束直接赋值
o[k] = value
return null
} else if (k in o) { // 若存在对应路径,则返回找到的对象,进行下一次遍历
return o[k]
} else { // 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象
o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
return o[k]
}
}, object)
// 返回object
return object;
}

参考文献

antd 取/设置表单

1
2
3
4
5
6
// 获取
const currSkuId = _form.getFieldValue([rowKey || '', 'skuId'])
//设置
_form.setFields([{name:[rowKey || '', 'goodsName'], value: '123'}]) // 其中rowKey是上一级的属性
//或者
form.setFieldsValue({ content: { pkgList: pkgList } })

监听校验

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
<ProFormText
label={`APPID-${index}`}
name="appId"
placeholder={'签约商户号绑定的APPID'}
rules={[
{
validator: (_, value) => {
if (!value || value.trim() === '') {
return Promise.reject(new Error('请输入APPID'))
}
const { channelAppConfigsList } = formRef.current.getFieldsValue()
let errMsg = ''
channelAppConfigsList.some((item: any, i: any) => {
if (i != index && item.appId == value) {
errMsg = item
return true
}
return false
})
if (errMsg) return Promise.reject(new Error('有重复APPID'))
return Promise.resolve()
},
required: true
}
]}
/>

Form.list嵌套Form.list

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<Form form={form} name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
<Form.Item name="area" label="Area" rules={[{ required: true, message: 'Missing area' }]}>
<Select options={areas} onChange={handleChange} />
</Form.Item>
<Form.List
name="sights"
initialValue={[
{
test: [
{
price: '',
name: '',
},
{
price: '',
name: '',
},
],
},
]}
>
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Space key={field.key} align="baseline">
<Form.List
{...field}
label="父"
name={[field.name, 'test']}
fieldKey={[field.fieldKey, 'test']}
initialValue={[
{
price: '',
},
{
name: '',
},
]}
>
{(testList, { add: addtest, remove: removetest }) => (
<>
{testList.map((testItem) => {
console.log(testItem);
return (
<Space key={testItem.key} align="baseline">
<Form.Item
{...testItem}
label="Price"
name={[testItem.name, 'price']}
fieldKey={[testItem.fieldKey, 'price']}
rules={[{ required: true, message: 'Missing price' }]}
>
<Input />
</Form.Item>
<Form.Item
{...testItem}
label="Name"
name={[testItem.name, 'name']}
fieldKey={[testItem.fieldKey, 'name']}
rules={[{ required: true, message: 'Missing name' }]}
>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => removetest(testItem.name)} />
</Space>
);
})}

<Form.Item>
<Button
type="dashed"
onClick={() => addtest()}
block
icon={<PlusOutlined />}
>
填加子
</Button>
</Form.Item>
</>
)}
</Form.List>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Space>
))}

<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加外层
</Button>
</Form.Item>
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>

Form.list嵌套Form.list

← Prev Next →