性能优化与调试技巧:探讨如何通过优化JavaScript代码来提高性能,包括减少重绘和重排、使用节流和防抖技术、使用性能分析工具等
React 快速入门
使用 React 与 Babel
通过以下语句可直接使用 React 和 ReactDOM 库, 并使用 Babel 进行编译, 从而快速进入开发环节
为什么要编译?
对于 ES6 某些特性与 JSX 而言, 需要编译才可使用
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.8.3/babel.js"></script>
使用 JSX
React 开发中常用的 JSX 语法,是一种类 HTML 的语法扩展,能够使创建 UI 组件更加直观和简洁。然而,浏览器并不原生支持 JSX,需要将其编译成标准的 JavaScript 函数调用(如 React.createElement()
)来正常运行。
例如,以下 JSX 代码:
const element = <h1>Hello, world!</h1>;
会被编译为:
const element = React.createElement('h1', null, 'Hello, world!');
React state hooks 入门
使用 useState
声明可以直接更新的状态变量
const [name, setName] = useState(0);
并且可以使用 setName
来更新状态 name
setName("jack"); // 设置 name 为 jack
为什么要"多此一举"地使用 useState
声明? 而不直接用变量声明呢?
当状态/变量发生改变时, 会触发再渲染, 对于使用普通变量声明的 HTML 元素会整块整块地刷新
而对于 React hooks 声明的状态只会更新相应的状态
例如
`<div>
<input type="text" value=${name} />
<div>`
与
(<div>
<input type="text" value={name} />
<div>
后者 input 不会因为 name 改变而失去聚焦
useEffect 快速入门
useEffect(fn[, dep])
在每次渲染时会执行传入的函数 fn
,
第二个参数 dep
是依赖项, 即保证依赖项中的状态变化后才执行传入的函数, 而不是每次渲染都执行,
当依赖项为 []
时, 只有组件初始化(mount)和组件删除(unmount)时才会执行
什么是组件?
function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
</div>
);
}
这就是一个组件 MyApp
, 注意首字母大写, 我们可以是用 <MyApp />
指这个组件
用以上 React 知识写一个简单的 demo
实现效果:
在输入框中, 输入时下面的文字同步更新, 并存储在本地, 刷新页面后不变
实现代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Demo</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="root"></div>
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.8.3/babel.js"></script>
<script type="text/babel">
const root = document.querySelector(".root");
function Form() {
const [name, setName] = React.useState(() => window.localStorage.getItem("name") || "");
React.useEffect(() => {
window.localStorage.setItem("name", name);
}, [name]);
function handleNameInput(e) {
e.preventDefault();
setName(e.target.value);
}
return (
<>
<form className="name-form">
<label htmlFor="name">Enter your name: </label>
<br />
<input value={name} type="text" id="name" onChange={handleNameInput} />
</form>
<div className="display-container">
<div className="display-name">Here is your name: </div>
<div className="display-name">{name}</div>
</div>
</>
);
}
ReactDOM.render(<Form />, root);
</script>
</body>
</html>
-
HTML 和外部依赖:通过
<script>
标签引入了 React、ReactDOM 和 Babel。Babel 用于在浏览器中编译 JSX 代码。 -
React 组件结构:
Form()
是一个 React 组件,用于渲染输入框和展示输入的内容。- 使用
React.useState
钩子创建了一个name
状态,初始值通过window.localStorage.getItem("name")
获取,保证页面刷新后输入仍然存在。
-
状态和副作用:
React.useEffect
钩子用于在name
状态更新时,将其值保存到localStorage
,确保输入持久化handleNameInput
函数响应输入事件,通过setName
更新name
状态。
-
组件渲染:
- 初始渲染时,
name
会从localStorage
读取,如果没有值则为空。 - 输入框的值通过
value={name}
实现双向绑定,用户输入触发onChange
事件后,handleNameInput
更新name
状态。 - 组件重新渲染后,输入框和展示区域会同步显示
name
的最新值。
- 初始渲染时,
-
输出显示:
<form>
中包含输入框。- 输入的名字在
<div>
中实时显示,示例中使用了两行<div>
进行展示,其中第二个<div>
显示用户输入的内容。
加入防抖功能
什么是防抖?
特定时间内只执行一次, 不断触发则只会在特定时间后执行最后一次
function debounce(fn, dur) {
dur = dur || 100;
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
在dur
时间内不断调用 debounce
, 导致 setTimeout
不断刷新进而重置倒计时
代码实现
function debounce(fn, dur = 100) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
};
}
function handleNameInput(e) {
e.preventDefault();
const newName = e.target.value;
debounce(() => setName(newName))();
}
实现效果: 用户连续输入时只取特定时间的最后一个字符, 哈哈哈哈反人类输入过快设计
加入节流功能
什么是节流?
特定时间内只执行一次, 不断触发会间隔特定时间依次执行
function throttle(fn, time = 500) {
let timer;
return function(...args) {
if (timer == null) {
fn.apply(this, args);
timer = setTimeout(() =>{
timer = null;
}, time);
}
}
}
在dur
时间内, 不断调用只有当 timer
为 null
时, 才会调用 fn
而只有当第一次或setTimeout
结束时, timer
为 null
, 即达到节流效果
一图胜千言
代码实现
function debounce(fn, dur = 500) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, dur);
};
}
function throttle(fn, dur = 1000) {
let timer;
return function () {
if (timer == null) {
fn.apply(this, arguments);
timer = setTimeout(() => {
timer = null;
}, dur);
}
};
}
const throttledSetName = throttle((newName) => setName(newName), 500);
function handleNameInput(e) {
e.preventDefault();
const newName = e.target.value;
throttledSetName(newName);
}
不过实现效果好像有问题??? 🥲🥲🥲待会儿再来看看