react16中生命周期hooks

概述

react hooks 推出有段时间了,函数式组件越来越多,当函数式组件中需要使用声明周期钩子时就需要自己封装出一些生命周期函数

目的

在函数式组件中如类组件中一样使用生命周期钩子

实现

  • Effect Hook 在组件挂载、更新、卸载时都会执行的生命周期钩子
  • useRef 返回一个在组件的整个生命周期内保持不变的 ref 对象

didMount

利用 Effect Hook 依赖不变不会再次执行的特性,依赖传入空数组即可实现挂载时执行一次的特性

1
2
3
const useMount = (cb) => {
useEffect(cb, [])
}

update

利用 useRef 对象在组件的整个生命周期内保持不变的特性,在组件初始化时初始化 ref 对象,判断 ref 对象的状态决定是否执行回调

1
2
3
4
5
6
7
8
9
const useUpdate = (cb, deps) => {
const ref = useRef(0)
useEffect(() => {
if (ref.current) {
cb()
}
ref.current = ref.current + 1
}, deps)
}

Unmout

利用 Effect Hook 返回值在组件卸载时执行的特性,在组件初始化时把回调作为其返回值,即可实现卸载执行的特性

1
2
3
4
5
const useUnmout = (cb) => {
useEffect(() => {
return cb
}, [])
}

参考

排列

排列,一般地,从 n 个不同元素中取出 m(m≤n)个元素,按照一定的顺序排成一列,叫做从 n 个元素中取出 m 个元素的一个排列(permutation)。特别地,当 m=n 时,这个排列被称作全排列(all permutation)。

算法实现思路

此问题可以简化为每次从数组中提取出一个元素,再从剩余元素提取所需的 n-1 个元素

参考代码

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
/**
* 排列问题可以简化为每次从数组中提取出一个元素
* 边界处理
* 1. 当只需要一个元素时返回这个元素的二维数组
* 2. 其它每次提取一个元素放到数组中即可
*/

function getData(arr, restLen) {
if (arr.length < restLen) {
return []
}
let result = []
for (let i = 0; i < arr.length; i++) {
const tempArr = arr.filter((ele, index) => i !== index)
let list = []
if (restLen <= 1) {
list = [[arr[i]]]
} else {
list = getData(tempArr, restLen - 1).map((item) => [arr[i], ...item])
}

result = [...result, ...list]
}
return result
}

const arr = [1, 2, 3, 4]
let list = []
for (let i = 1; i <= arr.length; i++) {
list = [...list, ...getData(arr, i)]
}

console.log('list', list, list.length)

参考

项目脚手架

构建流程

  • 检查目录文件
  • 获取初始化参数(包括项目名称、版本等信息)
  • 获取项目模板(包括本地模板和 github 模板)
  • 项目中填充参数生成项目

检查目录

递归获取对应文件夹下的文件列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function traverseDirectory(pathname) {
const realPath = resolveRootPath(pathname)
if (isDirectory(realPath)) {
const result = []
const list = fs.readdirSync(realPath)
const len = list.length
for (let i = 0; i < len; i++) {
const tempPath = path.resolve(pathname, list[i])
if (isDirectory(list[i])) {
const tempArr = traverseDirectory(tempPath)
result.push(...tempArr)
} else {
result.push(tempPath)
}
}
return result
} else {
return []
}
}

初始化参数

使用 inquirer 通过提问的方式获取对应的参数

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
async function getProjectConfig(pathname) {
const defaultProjectName = (pathname || process.cwd())
.split(/(\/|\\)/)
.reverse()
.filter(Boolean)[0]

const answers = await inquirer.prompt([
{
name: 'name',
message: '请输入项目名称',
default: defaultProjectName,
},
{
name: 'version',
message: '请输入版本',
default: '1.0.0',
},
{
name: 'author',
message: '请输入创建人',
},
{
name: 'template',
message: '请选择模板',
type: 'list',
choices: Object.keys(templateMap).map((key) => ({
key,
value: key,
name: templateMap[key].label,
})),
},
])
return answers
}

获取项目模板

通过初始化参数判断使用的模板类型,若为本地模板则返回文件路径,若为 git 仓库模板则先下载保存在临时文件夹下,返回文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function getTemplate(key) {
const temp = templateMap[key]
if (temp && temp.isGit) {
try {
rm(downloadTempRootPath)
const target = await downloadTemplate(temp.path, downloadTempRootPath, {
clone: true,
})
return target
} catch (e) {
error('\ngit 拉取模板失败,失败原因:', e)
return ''
}
}
return temp.path
}

项目中填充参数生成项目

读取模板文件填充模板

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
async function generateProject(source, destination, config) {
return new Promise((resolve, reject) => {
metalsmith(process.cwd())
.metadata(config)
.clean(false)
.source(source)
.destination(destination)
.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
Object.keys(files).forEach((fileName) => {
const t = files[fileName].contents.toString()
files[fileName].contents = Buffer.from(Handlebars.compile(t)(meta))
})
done()
})
.build((err) => {
rm(process.cwd())
if (err) {
reject(err)
} else {
resolve()
}
})
})
}

相关库

todo

  • 更多模板
  • 优化模板功能
  • package.json 字段详解

滚动加载更多

当页面数据过多为了优化页面加载速度和避免页面的卡顿时使用滚动加载的方式实现数据的分页加载功能

加载更多方案

利用 scroll 事件实现

通过监听对应元素的滚动,不断计算元素相对窗口的位置判断元素是否滚动到底部,若滚动到底部就加载更多

缺点
  • scroll 事件不断触发需要做节流操作实现
  • 每次触发事件时都要计算元素的位置性能不好
优点
  • 兼容性好
  • 易于实现

利用 IntersectionObserver 实现

IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。

优点
  • IntersectionObserver 是浏览器内部实现的方法,性能较高
  • 可以自定义视窗重叠的阈值
缺点
  • 兼容性较差

使用 IntersectionObserver 的一般流程

第一步

初始化 IntersectionObserver,构建 IntersectionObserver 实例,确定视窗窗口和触发阈值及回调函数

1
const ob = new IntersectionObserver(callback, options)
说明
callback

当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数

  • entries: 一个 IntersectionObserverEntry 对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
  • observer: 被调用的 IntersectionObserver 实例。
options(可选)

一个可以用来配置 observer 实例的对象。如果 options 未指定,observer 实例默认使用文档视口作为 root,并且没有 margin,阈值为 0%(意味着即使一像素的改变都会触发回调函数)。你可以指定以下配置:

  • root: 监听元素的祖先元素 Element 对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见。
  • rootMargin: 一个在计算交叉值时添加至根的边界盒(bounding_box)中的一组偏移量,类型为字符串(string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的 margin 属性等同; 可以参考 The root element and root margin in Intersection Observer API 来深入了解 margin 的工作原理及其语法。默认值是”0px 0px 0px 0px”。
  • threshold: 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见. 若指定值为 1.0,则意味着整个元素都是可见的。阈值的默认值为 0.0。

第二步

调用 observe 观测元素,targetElement 即为观测的元素

1
ob.observe(targetElement)

第三步

调用 unobserve 取消对元素的观测,target 即为取消观测的对象

1
ob.unobserve(target)

第四步

调用 disconnect 终止对所有目标元素可见性变化的观察

1
ob.disconnect()

React 中的实现

  • 初始化 IntersectionObserver,确定视窗窗口和触发阈值及回调函数
  • 通过 children 属性获取子组件中的最后一个元素
  • 监听列表中的最后一个组件,当最后的组件触发事件时加载更多
  • 引入 intersection-observer 组件做兼容性处理

完整代码参考

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import React from 'react'
import 'intersection-observer'

interface IProps extends React.DOMAttributes<HTMLDivElement> {
children: any
getRootElement?: (parent: Element) => Element
rootMargin?: string
observerCallback?: IntersectionObserverCallback
}

class LoadMore extends React.PureComponent<IProps> {
private ele: Element | null = null
private observer: IntersectionObserver | undefined
private observerElement: Element | null = null
private lastThreshold: number = 0

public componentDidMount() {
const { rootMargin } = this.props
this.observer = new IntersectionObserver(this.observerCallback, {
root: this.getRootElement(),
rootMargin,
threshold: [0, 0.01],
})
this.listenLastDom()
}

public componentDidUpdate(prevProps: IProps) {
const { children: oldChildren } = prevProps
const { children } = this.props
const len = React.Children.toArray(children).length
if (len !== React.Children.toArray(oldChildren).length && len) {
this.listenLastDom()
}
}

public componentWillUnmount() {
this.unobserve()
}

public render() {
const {
children,
getRootElement,
observerCallback,
rootMargin,
...otherProps
} = this.props
return (
<div ref={this.getElement} {...otherProps}>
{children}
</div>
)
}

private getRootElement = () => {
const { getRootElement } = this.props
const ele = this.ele as Element
let parent = null
if (getRootElement) {
parent = getRootElement(
ele && ele.parentElement
? ele.parentElement
: (document.body as Element),
)
}
if (!parent && ele && ele.parentElement) {
parent = ele.parentElement
}
return parent
}

private getElement = (ele: Element | null) => {
this.ele = ele
}

// 监听回调
private observerCallback = (
entries: IntersectionObserverEntry[],
observer: IntersectionObserver,
) => {
const [{ intersectionRatio = 0 }] = entries

const { observerCallback } = this.props
if (intersectionRatio > this.lastThreshold) {
if (observerCallback) {
observerCallback(entries, observer)
}
}
this.lastThreshold = intersectionRatio
}

// 停止监听元素
private unobserve = () => {
if (this.observerElement && this.observer) {
this.observer.unobserve(this.observerElement)
}
}

// 监听元素
private listenLastDom = () => {
if (this.ele && this.ele.lastChild) {
const dom: Element = this.ele.lastChild as Element
this.lastThreshold = 0
const observer = this.observer as IntersectionObserver
if (dom && observer) {
this.unobserve()
this.observerElement = dom
observer.observe(this.observerElement)
}
}
}
}

export default LoadMore

参考

mac 下查询端口占用情况并杀掉对应 node 进程

准备工作

命令行下查询端口占用情况

1
lsof -i tcp:8080

返回结果如下

1
2
COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node 45971 xxxx 21u IPv6 0xxxxxxxxxxxxxxxxx 0t0 TCP *:http-alt (LISTEN)

mac 下杀掉进程的命令

1
kill pid

node 下执行命令

  • 异步执行命令
1
require("child_process").exec("cmd");
  • promise 包裹后通过 async await 的方式来执行
1
2
3
const { stdout, stderr } = require("util").promisify(
require("child_process").exec
)("cmd");

开发

提取对应占用对应端口的 node pid

  • 通过命令行获取对应的端口占用情况
  • 获取到的字符串统一转为小写
  • 通过正则表达式匹配出想要筛选的 node 进程
  • 通过正则提取对应进程的 pid
1
2
3
4
5
6
7
8
9
10
11
12
13
const { stdout, stderr } = await require("util").promisify(
require("child_process").exec
)(`lsof -i tcp:8080`);

const list = stdout
.toLowerCase()
.split("\n")
.filter(Boolean)
.filter((str) => /^node/.test(str))
.map((str) => {
const reg = /^node\s+(\w+)\s+/.exec(str);
return Number(reg[1]);
});

执行 kill 命令杀掉对应的 node 进程

  • 遍历进程数组执行命令杀掉对应进程
1
2
3
const { stdout, stderr } = await require("util").promisify(
require("child_process").exec
)(`kill 45971`);

扩展

  • 可以通过构建 node cli 实现端口查杀对应占用进程的功能

完整代码

注:utils 文件主要是通过 console 实现的打印功能

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
const { exec } = require("child_process");
const util = require("util");

const { log, error, info } = require("./utils");

const pExec = util.promisify(exec);

async function getPid(port) {
try {
const { stdout, stderr } = await pExec(`lsof -i tcp:${port}`);
if (stderr) {
throw new Error(stderr);
} else {
const list = stdout
.toLowerCase()
.split("\n")
.filter(Boolean)
.filter((str) => /^node/.test(str))
.map((str) => {
log(str);
const reg = /^node\s+(\w+)\s+/.exec(str);
return Number(reg[1]);
});
return list;
}
} catch (e) {
error(e);
return [];
}
}

async function kill(pid) {
try {
const { stdout, stderr } = await pExec(`kill ${pid}`);
if (stderr) {
throw new Error(stderr);
} else {
if (stdout) {
log(stdout);
}
info(`kill pid ${pid} success`);
}
} catch (e) {
error(e);
}
}

async function init(port) {
const pids = await getPid(port);
try {
await Promise.all(pids.map((pid) => kill(pid)));
process.exit(0);
} catch (e) {
error(e);
process.exit(1);
}
}

init(8080);

用ts搭建express项目

初始化项目

进入项目文件夹,执行 npm init 命令,一路回车,最后输入 yes 即可创建项目,生成项目的 package.json 文件,文件内容如下

1
2
3
4
5
6
7
8
9
10
11
{
"name": "express-ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

安装相关依赖

  • npm install express 安装 express
  • npm install typescript @types/node @types/express -D 安装相关 ts 相关依赖
  • npm install @babel/cli @babel/core babel-plugin-module-resolver hjson -D 安装相关 babel 相关依赖
  • npm install ts-node nodemon tsconfig-paths -D 安装开发环境相关依赖
  • npm install hjson rimraf -D 安装其它依赖

配置项目

  • 在根目录下创建 tsconfig.json,配置 ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"compilerOptions": {
"module": "commonjs", //指定生成哪个模块系统代码
"allowJs": true, // 允许编译javascript文件
"target": "es5", //目标代码类型
"noImplicitAny": false, //在表达式和声明上有隐含的'any'类型时报错。
"sourceMap": false, //用于debug
"baseUrl": "./", // 解析非相对模块名的基准目录
"rootDir": "src", //仅用来控制输出的目录结构--outDir。
"listFiles": false, // 编译过程中打印文件名
"locale": "zh", // 显示错误信息时使用的语言
"outDir": "dist", //重定向输出目录。
"skipDefaultLibCheck": true, // 忽略库的默认声明文件的类型检查
"types": ["node", "express"],
"paths": {
// 模块名到基于 baseUrl的路径映射的列表
"@utils/*": ["src/utils/*"]
}
},
"include": ["./src/"],
"exclude": ["node_modules"]
}
  • 在根目录下创建 babel.config.js 文件配置 babel,本项目中只用到了 babel 编译路径别名的功能
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
const hjson = require("hjson");
const fs = require("fs");
const path = require("path");

const text = fs.readFileSync("./tsconfig.json", { encoding: "utf8" });

const tsConfig = hjson.parse(text);

const pathMap = tsConfig.compilerOptions.paths || {};
const pathAlias = Object.keys(pathMap).reduce((prev, curr) => {
const realKey = `${curr}`.replace(/\/\*$/, "");
const realPath = `/${(pathMap[curr] || [])[0]}`
.replace(/\/\*$/, "")
.replace(/^\S*src/, "./dist");
prev[realKey] = realPath;
return prev;
}, {});

module.exports = function (api) {
api.cache(true);

const plugins = [
[
"module-resolver",
{
root: ["dist/"],
alias: pathAlias,
},
],
];

return {
plugins,
};
};
  • 在根目录下创建 nodemon.json,配置开发环境
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"restartable": "rs",
"ignore": ["node_modules/**/node_modules"],
"verbose": true,
"execMap": {
"ts": "ts-node -r tsconfig-paths/register src/index.ts --files"
},
"watch": ["./src"],
"env": {
"NODE_ENV": "development"
},
"ext": "ts"
}

编写测试代码

  • 创建 scr/index.ts 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
import * as express from "express";
import { log } from "@utils/log";

const app = express();
const port = 8000;

app.get("/", (req, res) => {
res.send("hello world");
});

app.listen(port, () => {
log(`Server is listening on http://localhost:${port}`);
});
  • 创建 scr/utils/log.ts 文件
1
2
3
4
5
6
7
import { isDev } from "./env";

export const log = (...args) => {
if (isDev) {
console.log(...args);
}
};
  • 创建 scr/utils/env.ts 文件
1
export const isDev = process.env.NODE_ENV !== "production";

配置相关脚本

  • nodemon 开发环境编译命令:"dev:server": "nodemon"
  • ts-node 开发环境编译命令: "dev:ts:server": "ts-node -r tsconfig-paths/register src/index.ts --files"
  • 生产环境编译命令: "build:server": "rimraf ./dist && tsc --build ./tsconfig.json && babel ./dist/index.js --out-dir ./dist"
  • 生产环境启动服务命令: node ./dist/index.js

最终 package.json

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
{
"name": "express-ts",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start:server": "node ./dist/index.js",
"dev:server": "nodemon",
"dev:ts:server": "ts-node -r tsconfig-paths/register src/index.ts --files",
"build:server": "rimraf ./dist && tsc --build ./tsconfig.json && babel ./dist/index.js --out-dir ./dist"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"@babel/cli": "^7.10.4",
"@babel/core": "^7.10.4",
"@types/express": "^4.17.6",
"@types/node": "^14.0.14",
"babel-plugin-module-resolver": "^4.0.0",
"hjson": "^3.2.1",
"nodemon": "^2.0.4",
"rimraf": "^3.0.2",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
"typescript": "^3.9.6"
}
}

开发环境

在根目录执行 npm run dev:server 或者执行 npm run dev:ts:server 就可以启动服务,访问 http://localhost:8000,即可在浏览器中看到 hello world,注意 npm run dev:ts:server 文件有更改后不会自动重启服务

生产环境

在根目录执行 npm run build:server,然后再执行 npm run start:server就可以启动服务,访问 http://localhost:8000,即可在浏览器中看到 hello world

目录结构

1
2
3
4
5
6
7
8
9
src // 源码目录
|--- utils // 工具文件目录
| |--- log.ts // 输出日志文件
| |--- env.ts // 环境参数相关
|--- index.ts // 项目入口文件
|--- babel.config.js // babel 相关配置
|--- nodemon.json // nodemon 相关配置
|--- package.json // 项目配置
|--- tsconfig.json // ts 相关配置

完整项目参考链接

相关知识

js基础知识系列(九)

事件

事件流

页面接收事件的顺序

事件冒泡

事件开始时由具体的元素接收,然后逐级上传到较为不具体的节点

事件捕获

不太具体的节点较先捕获到事件,具体的节点最后捕获到事件

DOM 事件流

  • 事件捕获阶段
  • 目标阶段
  • 事件冒泡阶段

事件处理程序

响应某个事件的程序

DOM0 级事件处理程序

将事件赋值给一个事件处理程序属性

DOM2 级事件处理程序

  • addEventListener 添加事件处理程序
  • removeEventListener 删除事件处理程序

IE 事件处理程序(IE8 之前版本)

  • attachEvent 添加事件处理程序
  • detachEvent 删除事件处理程序

事件对象

DOM 中的事件对象

属性 / 方法 类型 读 / 写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消默认事件
currentTarget Element 只读 表明是否可以取消事件的默认行为
defaultPrevented Boolean 只读 为 true 表明已经调用了 preventDefault
detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读 调用事件处理程序的阶段:1 表示捕获阶段,2 表示处于目标阶段,3 表示冒泡阶段
preventDefault Function 只读 取消事件的默认行为,如果 cancelable 为 true 可以调用这个方法
stopImmediatePropagation Function 只读 取消事件的进一步捕获或冒泡,同时组织任何事件处理程序调用(DOM3 级事件中新增)
stopPropagation Function 只读 取消事件的进一步捕获或冒泡,,如果 bubbles 为 true 可以调用这个方法
target Element 只读 事件目标
trusted Boolean 只读 为 true 表示事件是浏览器生成的,为 false 表示事件是由开发人员通过 js 创建的(DOM1 中新增)
type String 只读 被触发的事件类型
view AbstratView 只读 与事件关联的抽象视图,等同于发生事件的 window 对象

IE 中的事件对象

通过 window.event 对象访问事件对象,包含如下属性

属性 / 方法 类型 读 / 写 说明
cancelable Boolean 读 / 写 默认为 false ,但将其设置为 true 就可以取消事件冒泡(与 stopPropagation 方法作用相同)
returnValue Boolean 读 / 写 默认为 true ,但将其设置为 false 就可以取消事件默认行为(与 preventDefault 方法作用相同)
target Element 只读 事件目标(与 target 作用相同)
type String 只读 被触发的事件类型

事件类型

  • UI 事件,当用户与页面上元素交互时触发
  • 焦点事件,当元素失去或获得焦点时触发
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发
  • 滚轮事件,当使用鼠标滚轮时触发
  • 文本事件,当在文档中输入文本时触发
  • 键盘事件,当用户通过键盘在页面上执行操作时触发
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器) 输入字符时触发
  • 变动事件,当底层 DOM 结构发生变化时触发

HTML5 事件

  • contextmenu 事件,上下文菜单
  • beforeunload 事件
  • DOMContentLoaded 事件
  • readstatechange 事件
  • pageshow 和 pagehide 事件
  • hashchange 事件

设备事件

  • orientationchange 事件,通过 window.orientation 访问旋转角度
  • deviceorientation 事件,设备静止状态下的设备变化信息
  • devicemotion 事件,设备移动信息

触摸与手势事件

  • 触摸事件
  • 手势事件

内存和性能

事件委托

解决事件处理程序过多的问题

移除事件处理程序

每个事件处理程序都会建立一个浏览器代码与页面交互的 JavaScript 代码链接

模拟事件

DOM 中的模拟事件

可以通过 document 对象上的 createEvent 方法创建 event 对象

  • 模拟鼠标事件
  • 模拟键盘事件
  • 自定义 DOM 事件

js基础知识系列(八)

BOM(浏览器对象模型)

window 对象

浏览器的一个实例。javaScript 对象访问浏览器的接口,ECMAScript 中的 Global 对象

全局作用域

窗口关系

  • top : 始终指向最外层的框架
  • parent:当前框架的直接上层框架
  • self:当前框架的 window 对象
注意:不同框架的 Object 对象不同,不能用 instanceof 检测跨框架的对象

窗口位置

  • screenLeft 和 screenTop 表示窗口距离屏幕左上角的距离信息(IE、Safari、Chrome)
  • screenX 和 screenY 表示窗口距离屏幕左上角的距离信息(火狐)
  • moveTo(x,y) 窗口移动到屏幕的(x,y)坐标位置
  • moveBy(x,y) 窗口在水平和垂直方向分别移动 x 和 y 的距离
1
2
const x = window.screenLeft || window.screenX;
const y = window.screenTop || window.screenY;

窗口大小

  • innerHeight 和 innerWidth 页面视图区大小(IE9+、火狐、opera、chrome、safari)
  • document.documentElement.clientWidth 和 document.documentElement.clientHeight 在 CSS1Compat 模式下表示视图大小
  • document.body.clientWidth 和 document.body.clientHeight 在非 CSS1Compat 模式下表示视图大小
  • resizeTo(width, height) 页面重置到 width 和 height 大小
  • resizeBy(width, height) 页面分别缩小 width 和 height

弹出窗口

window.open(url, 窗口名称, 参数) 返回打开窗口的句柄,可以对打开的窗口进行关闭操作,返回的句柄中 opener 默认指向当前的页面对象

间歇调用或超时调用

  • setTimeout 和 clearTimeout 调用一次
  • setInterval 和 clearInterval 多次调用
第 3 个参数及后面的参数都是第一个函数参数调用时的参数

系统对话框

  • alert 提示框
  • confirm 确认框
  • prompt 输入框
同步代码,在代码未执行就无法继续执行后面的代码

location 对象

获取当前窗口加载的文档信息

属性名称 说明
hash url 中的 hash 值,即 url 中 # 后的字符串,若不存在就为空子符串
host 域名加端口的字符串
hsotname 域名
href 完整的 url
pathname url 中的路径,即包含在以 / 开始 ? 结尾,之间的内容
port 端口
protcol 协议
search 查询字符串,即 ? 后 # 前的内容
  • location.assign 打开新的 url ,并生成一个历史记录
  • location.replace 当前页面打开 url,并替换当前页面的历史纪录
  • location.reload 重新加载页面,值为 true 时,强制从服务器刷新页面

检测插件

navigator.plugins 可以在非 IE 浏览器中获取安装的插件数组,数组的每个元素包含如下属性

  • name:插件名称
  • description:插件描述
  • filename:插件的文件名称
  • length:插件处理的 MIME 类型数量

注册处理程序

  • navigator.registerProtocolHandler

screen 对象

浏览器窗口外部显示器信息

history 对象

  • history.go 跳转到指定的页面
  • history.back 返回页面
  • history.forward 前进
  • history.length 历史记录的数量

js基础知识系列(七)

函数

定义函数的方式

火狐、谷歌、safari、opera 存在属性 name 可以访问当前函数的 name

函数声明

函数声明提升,即执行代码前会先读取函数声明

1
2
3
4
5
// 函数声明
func(); // 函数声明提升,输出结果 func
function func(...args) {
console.log(func.name); // func
}

函数表达式

又称匿名函数或拉姆达函数

1
2
3
4
5
// 函数表达式
func(); // 无提升,抛出异常 TypeError: func is not a function
var func = function (...args) {
console.log(func.name); // func
};

递归调用

arguments.callee 指向正在执行的函数指针

1
2
3
function func(...args) {
console.log(arguments.callee.name); // func
}

闭包

有权访问另一个函数作用域中变量的函数

执行环境和作用域

  • 执行环境:定义变量或函数有权访问的其它数据
  • 变量对象:与执行环境相关联的对象,环境中定义的所有变量或函数都保存在这个对象中
  • 函数环境:将活动对象作为变量对象,活动对象最开始只包含一个对象,即 arguments 对象

闭包与变量

闭包中保存的是整个变量对象,闭包中引用的变量都是指向这个环境变量对象的对应属性地址,而不是具体的值,在这个环境变量中定义的任何变量若不手动销毁都会一直存在一个引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var arr = [];
function test() {
for (var i = 0; i < 4; i++) {
arr[i] = function () {
console.log(i);
};
}
}
test();
arr[2](); // 4

// 立即执行函数强制进行值传递
var arr = [];
function test() {
for (var i = 0; i < 4; i++) {
arr[i] = (function (j) {
return function () {
console.log(j);
};
})(i);
}
}
test();
arr[2](); // 2

this 对象

匿名函数执行环境具有全局性。函数在调用时会自动获取 this 和 arguments 这两个对象,内部函数在搜索这两个变量时,只会搜索到活动对象为止

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = "window";
var test = {
name: "test",
getName: function () {
// 存在活动对象,搜索到这里为止
return function () {
// 匿名函数执行环境具有全局性
console.log(this.name); // window
};
},
};

test.getName()();

模仿块级作用域

通过立即执行函数的方法模仿块级作用域

私有变量

通过闭包的方式在外层创建局部变量的方式实现私有变量,在内部函数中定义的可以操作外部私有属性的方法叫做特权方法

静态私有变量

通过在原型中利用闭包的方式定义一个局部变量,然后在原型上定义一些特权方法实现对该变量的操作,实现所有实例共享同一个变量的方法

1
2
3
4
5
6
7
8
9
10
11
12
(function () {
let test = name; // 静态私有变量
return function Sup(name) {
this.prototype.getName = function () {
return test;
};

this.prototype.setName = function (temp) {
test = temp;
};
};
})();

模块模式

  • 单例:只有一个实例的对象
  • 模块模式:通过对单例添加私有变量和特权方法增强单例
1
2
3
4
5
6
7
8
9
10
11
const app = (function () {
const arr = [];
return {
getCount: function () {
return arr.length;
},
addComp: function (comp) {
arr.push(comp);
},
};
})();

增强的模块模式

通过实例对象,注入更多的属性

1
2
3
4
5
6
7
8
const app = (function () {
const obj = new Object(); // 实例对象
let len = 10;
obj.getCount = function () {
return len;
};
return obj;
})();