js部分
从字符串中快速提取数字
const str = "12.6px";
const numInt = parseInt(str);
// numInt: 12
const numFloat = parseFloat(str);
// numInt: 12.6
当数组/对象中的某一项需要根据条件来决定是否存在时
对象
断言+对象展开运算符
let isSleep = true;
const obj = {
name: "logan",
...isSleep && {
age: 230
},
};
obj为{
name: "logan",
age: 230
}
数组
三目+数组展开运算符(断言表达式会存在一个undefined,数组默认不会过滤掉)
const hasSecond = true
const arr = [
{
num: 1
},
...hasSecond ? [{
num: 2
}] : []
]
用length属性删除数组
const arr = [0, 1, 2, 3, 4, 5];
arr.length = 0; // arr变为[]
在vue2中,数据劫持对于数组的操作,是监听了数组原型中的所有能改变数组的方法(push,pop,shift,unshift,splice,sort,reverse),所以直接改变length虽然改变了数组的值,但是不会触发watcher更新视图。
所幸在vue3中,vue团队使用Proxy替代Object.defineProperty解决了该问题,静静等待vue3的正式版发布。
逗号操作符
逗号操作符 对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
const history = [];
// 将数据处理后放入列表,同时返回数据本身
const push = val => (history.push(memory(val)), val);
解构表达式常用到的地方
for of表达式行内解构
const cacheList = [
{
id: 1,
date: "2020-05-18",
timeout: 2569
},
{
id: 3,
date: "2020-04-16",
timeout: 7863
},
{
id: 2,
date: "2020-03-21",
timeout: 6387
}
]
for(let {id, date, timeout} of cacheList) {
console.log(id);
console.log(date);
console.log(timeout);
}
取出对象剩余属性【扩展运算符】
vue组件向下传递props时有语法糖$attrs
和$listener
,
但是react并没有提供这个方案,所以当props需要跨好几层传递时,为了避免让我们的组件成为props的搬运者,可以利用解构的方式来做。
代码如下
// 父组件要把数据传递给孙组件
function Parent() {
const [list, setList] = useState([]);
const produce = [
{
id: 0,
name: "x23"
}
];
return (
<child list={list} produce={produce} change={setList}/>
)
}
// 子组件利用对象的解构操作向下传递
function Child(props) {
const {list, ...rest} = props;
return (
<Grandson {...rest} />
)
}
// 孙组件成功接收
function GrandSon(props) {
return (
<>
<ul>
{produce.map(({id, name}) => <Cell key={id} title={name}>)}
</ul>
<button onClick={change}></button>
</>
)
}
同样,这种思路的书写方式在vue中也可以派上用场,比如你写了一个table组件,要加入很多位置的自定义字段,这里可以用v-bind去做。
(这里的启发来源于Layui的table源码部分)
<th
v-for="({title, ...rest}) in col"
:key="title"
v-bind="rest"
>
{{title}}
</th>
参数直接解构,减少不必要的临时变量声明
window.addEventListener("wheel", function({clientX, clientY}) {
console.log({clientX, clientY});
});
动态属性名和属性名表达式
const type = "email";
const params = {
username: "James Rhodes",
password: "war machine Rox with an X",
[type]: "Lodi@stark.com",
}
const key = "password";
console.log(params[key]); // 读取params的password属性,结果为war machine Rox with an X
比如你要在vue中动态取值(vue(2.6.0+)的动态attribute)
<component :[key]="value"></component>
取值时使用默认值
||
function Jarvis(options) {
this.name = options.sir || "Tony Stark";
}
解构表达式
function Jarvis(options) {
const {sir = "Tony Stark"} = options;
this.name = sir;
// ps: 注意区分,取出的属性加别名是这样写
// const {sir:name} = options;
// this.name = name;
}
(可忽略)ES11提供的??运算
function Jarvis(options) {
this.name = options.sir ?? "Tony Stark";
}
cssText替代dom.style.xxx
const dom = document.querySelector(".wrapper");
dom.style.cssText = "width:20px;height:20px;border:solid 1px gray;";
使用Array的工厂函数创建特定长度的数组
Array.from(arrayLike[, mapFn[, thisArg]])
- arrayLike 想要转换成数组的伪数组对象或可迭代对象。
- mapFn 可选 如果指定了该参数,新数组中的每个元素会执行该回调函数。
- thisArg 可选 可选参数,执行回调函数 mapFn 时 this 对象。
Array.from({ length: 10 }, (v, i) => i + 1);
// 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
目前笔者常用该特性来mock数据
app.get('/getProduces', function (req, res) {
res.send({
code: 200,
data: Array.from({length: 6}, (v, i) => ({
name: `produce - ${i}`,
url: `/view/produce/${i}.html`,
type: Math.round(Math.random())
? "new"
: "default",
num: Math.round(Math.random() * 10000),
}))
});
});
向数组头部添加数据
有时候或许会用数组做一个栈结构
const arr = ["Ferrie", "Natasha"];
arr.unshift("Tony");
// arr变成["Tony", "Ferrie", "Natasha"]
减少作用域查找
例如以下运行在全局作用域的代码:
<script>
var header = document.querySelector("#header");
header.style.cssText = `position: sticky;top: ${offsetTop}px;`;
</script>
一个 script
标签中,代码的上下文都在全局作用域,由于全局作用域比较复杂,编译器在处理时会比较慢。
就像上面的 header
变量,第二行在使用的时候,需要在全局作用域查找一下这个变量,
假设 header
是在一个循环里面使用,那可能就会有效率问题了。
所以尽量避免全局作用域,选择局部作用域:
举个🌰,闭包作用域
<script>
!function(){
var header = document.querySelector("#header");
header.style.cssText = `position: sticky;top: ${offsetTop}px;`;
}()
</script>
避免滥用闭包
纵使闭包很好用也别滥用,闭包中变量的作用域是独立的。 编译器在查找变量时,会从当前作用域向父级作用域依次查找。 所以闭包嵌套的深度会影响变量的查找时间。
再举个🌰,我们有一个flow函数,函数内部定义了一个animate
函数,
animate
的作用域内,存在一个常量location
,而函数内还需要用到target
和rate
,这两个变量的作用域在其父级作用域内,查找时间比location
的时间要久,
function flow(target, rate = 0) {
rate += 0.1;
function animate() {
const location = getLocation(target.location, 0.5);
return Tween.get(target).to({rate});
}
return animate();
}
这里可以通过参数的形式,把target
和rate
传给animate。
这样一来,他们的作用域变成同级,读取时间也只是编译器处理常量与变量的差异了。
function flow(target, rate = 0) {
rate += 0.1;
function animate(target, rate) {
return Tween.get(target).to({rate});
}
return animate(target, rate);
}
这个就启示我们如果某个全局变量需要频繁地被使用的时候,可以用一个局部变量缓存一下,(🌰:我自己来了)
function call() {
if(window.location.origin !== "xxxxxx") return;
const hash = getHash(window.location.href);
const url = `/api/radio/${window.location.search}/${hash}`;
}
多处使用window.location
,我们可以在函数作用域内缓存一下,
function call() {
const {location} = window;
if(location.origin !== "xxxxxx") return;
const hash = getHash(location.origin, location.hash);
const url = `/api/radio/${location.search}/${hash}`;
}
=== 替代 ==
两个原因
- 1.js是一个弱类型语言,
==
会忽略数据类型,为了更好的可读性选择更精确的===
运算 - 2.更好的编译速度
编译器在处理
==
时会先将两个变量转换成同类型,然后再比较值是否相等; 而处理===
时会先判断类型是否相同,然后再决定要不要比较值相等。
字符串模板
为了更好的可读性,放弃字符串拼接或数组.join(""),选择字符串模板
const str = `原价:${price},优惠减:${discount},最终价:${(price - discount).toFixed(2)}`;
变量修饰符的优先级:const > let > var
why?
var
- 可以重新声明
var starLord = "fool";
starLord = "very fool";
var starLord = "foolish";
- 可以变量提升
function () {
console.log(name); // 输出undefined,不会报错
var name = "Tony"
}
- 跨越块作用域
- 块作用域:{ },条件、循环语句的{ }
{
var Tony = "Iron Man";
}
console.log(Tony); // 输出Iron Man
if(true) {
var Steve = "caption";
}
console.log(Steve ); // caption
let i = 0;
while(i++ < 2) {
if(i === 1) {
var thor = "Fatty";
}
}
console.log(thor); // Fatty
let
只能在当前块级作用域使用
const
- const更语义化,提醒开发者该变量不应该被改变;
- 编译器会对const进行优化,读取常量的速度要快于变量,其本质区别在于编译器内部处理的方式不同。
正确姿势使用Promise.all
在ES11的Promise.allSettled出现之前,Promise.all收集promise时,任意一个出现错误都无法得到处理结果,这时候可以对all收集的promise对象进行catch操作手动处理err
const p1 = new Promise(...)
const p2 = new Promise(...)
const p3 = new Promise(...)
Promise.all([p1,p2,p3]).then(...).catch(err => err)
多个 loading 的冲突问题
引入一个用于计数的变量loadingCount
,
loading 时先判断数量是否为 0,满足条件则+1,然后执行,
close 时先-1,若数量为 0,则执行
let loadingCount = 0
function open() {
if(loadingCount > 0) return
loadingCount++;
loading.open();
}
function close() {
loadingCount--
if(loadingCount === 0) {
loading.close()
}
}
vue中 触发父组件的事件后,重新拿到props
props: {
msgList: ...
}
this.$emit("sendMsg", id);
// 在下一个更新队列完成后判断父组件传递过来的数据是否有值
this.$nextTick(() => {
this.showDetail = this.msgList && this.msgList.length > 0;
});
canvas画线和清除功能的冲突问题
清除时记得调用context.beginPath(),否则之前的线路还存在
css部分
调试UI小技巧
试着给调试区域(#app)加入这个样式
#app {
outline: 1px #000 solid;
}
可自动适应宽/高的背景图片拼接方案
当dom
的背景是一张图片,其宽高还要由内容决定,
这时怎么做到图片自适应高度呢?
(闪烁是因为我们改变宽高引起了浏览器的重绘与重排)
<style>
.box {
width: 100px;
height: auto; // 高度随意更改
background: url(/static/img/box-bg-top.png) top no-repeat,
url(/static/img/box-bg-bottom.png) bottom no-repeat;
}
.box:before {
content: '';
position: absolute;
z-index: -1;
top: 80px;
right: 0;
bottom: 80px;
left: 0;
background: url(/static/img/box-bg-center.png) repeat;
}
</style>
<body>
<div class="box">
</div>
</body>
实现思路:dom本身通过background-position和background-repeat实现图片的头和尾,然后用伪元素实现中间的自适应部分
合理运用pointer-events
在一个超级大的dom中,只有一个小小的子dom需要鼠标事件,我们可以这样做
.parent {
width: 600px;
height: 500px;
pointer-events: none;
}
.close {
float: right;
width: 10px;
height: 10px;
pointer-events: visible;
}
css选择器
第x个往后(不包含第x个)
li:nth-of-type(n+x) {
...
}
第x个及之前(包含第x个)
li:nth-of-type(-n+x) {
...
}
not选择器(排除某一个)
li:not(:last-child) {
background: #08627f;
}
字体间距及居中
大多数人只设置letter-spacing,这样会导致文字右边距变大从而不居中,这时候需要首行缩进(text-indent)同样的值来保证居中效果
.text {
letter-spacing: 12px;
text-indent: 12px;
}
transform
替代left/top,可有效避免重绘
重绘重排会引起闪屏,效果在上面案例可以看到
.dot {
transform: translate3d(10px, 16px, 0);
}
使用translateZ强制开启GPU渲染
.dot {
transform: translateZ(0);
}
但开启GPU加速可能会出现浏览器频繁闪烁或抖动的情况,
可这样解决:
.dot {
-webkit-backface-visibility:hidden;
-webkit-perspective:1000;
}
绝对定位,宽高不确定情况下的 居中问题
.box {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
给thead加border
table {
border-collapse:collapse;
}
thead {
border: 1px #0af solid;
}
图片不失真
img {
object-fit: cover;
}
精灵图适应屏幕
@screen_width: 1920;
img {
background-image: url("...");
background-position: 15px 30px;
background-size: calc(100vw / @screen_width * 160);
}
input去边框
border: none;
outline: none;
用户调整dom大小
兼容性略差
.resize {
resize:both;
}
css平滑滚动
scroll-container {
scroll-behavior: smooth;
}
<nav>
<a href="#page-1">1</a>
<a href="#page-2">2</a>
<a href="#page-3">3</a>
</nav>
<scroll-container>
<scroll-page id="page-1">1</scroll-page>
<scroll-page id="page-2">2</scroll-page>
<scroll-page id="page-3">3</scroll-page>
</scroll-container>
高度是百分比时的文字垂直居中问题
借助table-cell和vertical-align
<style>
.parent {
display: table;
width: 600px;
height: 50%
}
.parent a {
display: table-cell;
vertical-align: middle;
}
</style>
<div class="parent">
<a href="#page-1">1</a>
<a href="#page-2">2</a>
<a href="#page-3">3</a>
</nav>
var取元素属性值
class,id以及keyframes都可以直接获取元素上的属性
<template>
<div v-for="(item, index) in list"
:style="{'--y': `${index*60}deg`}"
class="around-item u_3d">
<div class="content">{{item}}</div>
</div>
</template>
<style>
@keyframes around-self {
0% {
transform: rotateY(calc(var(--y) * -1));
}
100% {
transform: rotateY(calc(var(--y) * -1 - 360deg));
}
}
.content {
animation: around-self 10s linear 0s infinite;
}
</style>
其他
在webpack配置loader时,通过test、exclude、include缩小搜索范围
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
include: [resolve('src/components'), resolve('src/modules')],
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('node_modules/webpack-dev-server/client')],
exclude: /node_modules/
},
]
}
script、style标签的修饰符
preload
资源要在dom渲染之前加载 通常是一些负责首屏渲染的逻辑
<link rel="preload" href="index.css">
prefetch、dns-prefetch
页面加载完成,此时没有任务需要做,这时候加载 通常是一些与用户交互有关的脚本,比如第三方的弹出层
<link rel="prefetch" href="next.css">
<link rel="dns-prefetch" href="//example.com">
无修饰符(异步无阻塞)
暂停解析document. 请求脚本 执行脚本 继续解析document
<script src="index.js" ></script>
defer
在英文中是“延迟”的意思, 不影响document解析的同时,并行下载home.js和user.js 按照页面中的顺序,在其他的同步脚本执行后,DOMContentLoaded事件执行前,依次去执行home.js和user.js。
<script src="home.js" defer></script>
<script src="user.js" defer></script>
async
不影响document解析的同时, 并行下载home.js和user.js 当脚本下载完后立即执行,脚本的执行顺序不确定,执行阶段不确定,可能再DOMContentLoaded执行前,也可能在执行后
<script src="home.js" async></script>
<script src="user.js" async></script>
图片格式选择webp
体积小,请求速度快,但是存在兼容问题,需要手动处理