heguichuan

Go Hiking


  • 首页

  • 归档

  • 标签

记一次写npm命令行包的记录

发表于 2021-06-08

记一次写 npm 命令行包的记录

步骤

  1. 项目目录结构随意,并没有特殊要求,执行 npm publish 即可推送
  2. 要实现命令行运行需要在 package.json 中填写 bin 字段,值为指向要执行的 js 文件的地址,**且该文件一定要在顶部加如下代码:#!/usr/bin/env node**,bin 的作用为 install 的时候在node_modules/.bin目录下添加链接,而该代码的作用为在.bin目录中链接文件中指定文件的执行环境,参考如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../test-mock/index.js" "$@"
ret=$?
else
node "$basedir/../test-mock/index.js" "$@"
ret=$?
fi
exit $ret

cmd 文件

1
2
3
4
5
6
7
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\test-mock\index.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\test-mock\index.js" %*
)

踩坑

  1. bin文件头部一定要加环境变量#!/usr/bin/env node说明!
  2. 用做传承的文件路径一定要用path.resolve转换成绝对路径!
  3. 加 –cwd 参数是为了指定 nodemon 的工作目录,默认监听启动入口文件的目录

Web Worker 使用教程

发表于 2021-06-08

Web Worker 使用教程

一、参考链接

软一峰 Web Worker 使用教程

二、注意事项

  1. 同源限制 , 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
  2. DOM 限制 , Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
  3. 通信联系 , Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
  4. 脚本限制 , Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest对象发出AJAX请求。
  5. 文件限制 , Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

三、基本用法

主线程

采用new命令,调用Worker()构造函数,新建一个 Worker 线程。 构造函数的参数是一个脚本文件 。如果下载没有成功(比如 404 错误),Worker 就会默默地失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var worker = new Worker("work.js");

worker.onerror(function(event) {
doSomething();
});
worker.postMessage("Hello World");
worker.postMessage({ method: "echo", args: ["Work"] });

worker.onmessage = function(event) {
console.log("Received message " + event.data);
doSomething();
};

worker.terminate();

Worker 线程

Worker 线程内部需要有一个监听函数,监听message事件。 self代表子线程自身,即子线程的全局对象

1
2
3
4
5
6
7
8
9
self.addEventListener(
"message",
function(e) {
self.postMessage("You said: " + e.data);
},
false
);
// 也可以使用self.onmessage
// self.close() 用于在 Worker 内部关闭自身。

Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()。

1
2
importScripts("script1.js");
importScripts("script1.js", "script2.js");

通信

主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。

二进制数据

拷贝方式发送二进制数据,会造成性能问题 。 JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了 。这种转移数据的方法,叫做Transferable Objects。

1
2
3
4
5
6
// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);

// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

API

1
var worker = new Worker(jsUrl, options);
  • worker.onerror:指定 error 事件的监听函数。
  • worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
  • worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • worker.postMessage():向 Worker 线程发送消息。
  • worker.terminate():立即终止 Worker 线程。
  • self.name: Worker 的名字。该属性只读,由构造函数指定。
  • self.onmessage:指定message事件的监听函数。
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • self.close():关闭 Worker 线程。
  • self.postMessage():向产生这个 Worker 线程发送消息。
  • self.importScripts():加载 JS 脚本。

四、worker-farm

在项目中使用过一次worker-farm,感觉还是比较简单。

自己使用体验,放到 callback 里面的代码是不会多线程执行的,child.js 里面定义的代码才是真的多线程异步执行。

child.js:

1
2
3
module.exports = function(inp, callback) {
callback(null, inp + " BAR (" + process.pid + ")");
};

main file:

1
2
3
4
5
6
7
8
9
var workerFarm = require("worker-farm"),
workers = workerFarm(require.resolve("./child")),
ret = 0;
for (var i = 0; i < 10; i++) {
workers("#" + i + " FOO", function(err, outp) {
console.log(outp);
if (++ret == 10) workerFarm.end(workers);
});
}

output something like the following:

1
2
3
4
5
6
7
8
9
10
#1 FOO BAR (8546)
#0 FOO BAR (8545)
#8 FOO BAR (8545)
#9 FOO BAR (8546)
#2 FOO BAR (8548)
#4 FOO BAR (8551)
#3 FOO BAR (8549)
#6 FOO BAR (8555)
#5 FOO BAR (8553)
#7 FOO BAR (8557)

同一服务器使用不同nodejs版本分别运行对应程序

发表于 2021-06-08

同一服务器使用不同nodejs版本分别运行对应程序

node8以后npm自带的功能

科普文:运维不给升级 Node 版本怎么办

1
2
3
4
5
6
npm i -S node@lts #安装node包
npx node@10 index.js #指定node版本运行

# 如果低版本的npm不支持npx,则可以手动安装npx包到本地
# npm i -S npx
# 再在package.json中加入如下命令:"start": "./node_modules/.bin/npx node@10 index.js"

使用nvm自带功能

1
nvm run 10.16.3 index.js #nvm指定node版本运行脚本,nvm -h 查看帮助文档

使用pm2指定执行程序路径

pm2能指定node版本运行吗?

pm2文档

1
pm2 start index.js --interpreter /root/.nvm/versions/node/v10.16.3/bin/node -n appName #interpreter参数指定node路径

阿里云防火墙开放端口

发表于 2021-06-08

阿里云防火墙开放端口

1、配置安全组,开放对应端口

这一步配置后,从来没直接成功过,可能是因为Centos7默认安装了firewalld

2、配置firewalld防火墙

Centos防火墙设置与端口开放的方法

1
2
3
4
5
6
firewall-cmd --zone=public --add-port=80/tcp(永久生效再加上 --permanent)
# 说明:
# –zone 作用域
# –add-port=8080/tcp 添加端口,格式为:端口/通讯协议
# –permanent #永久生效,没有此参数重启后失效
firewall-cmd --reload #重启

Babel 学习笔记

发表于 2020-04-17

Babel 学习笔记

参考文档

  • 官方文档
  • Babel 手册

babel是什么

Babel与polyfill的关系:Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。所以当使用 Array.prototype.find、Object.assign等静态方法和实例方法时,需要引入polyfill。

Babel 是一个编译器(输入源码 => 输出编译后的代码)。可以让你提前使用新的语法,而不用管浏览器是否已经支持。

实现的方式为:1、转换源代码为目标浏览器支持的语法;2、添加新功能的polyfill;

编译过程分为三个阶段:解析、转换和打印输出。

babel工作模式

输入文本 =》输出文本;

支持plugin模式,本质就是对源文本内容转换后输出新的源文本给下一级。

preset,用于预定义一组plugin集合,这样不用再手动一个个引入plugin。

@babel/preset-env,babel官方提供的一个preset。

Plugin插件

分转换插件和语法插件,

注意:转换插件会自动启用语法插件。因此,如果你已经使用了相应的转换插件,则不需要指定语法插件。

转换插件

官网

这些插件用于转换你的代码。

转换插件会自动启用语法插件。因此,如果你已经使用了相应的转换插件,则不需要指定语法插件。

语法插件

这些插件只允许 Babel 解析(parse) 特定类型的语法(而不是转换)。

parserOpts ??

插件名称

  1. npm可以直接指定名称,如果插件名称的前缀为 babel-plugin-,你还可以使用它的短名称。 babel-plugin-myPlugin -> myPlugin。带scope的插件也一样。
  2. 还可以使用相对/绝对路径。
1
2
3
4
5
6
7
8
9
{
"plugins": [
"myPlugin",
"babel-plugin-myPlugin", // 两个插件实际是同一个
"./node_modules/asdf/plugin",
"@org/babel-plugin-name",
"@org/name" // 两个插件实际是同一个
]
}

插件顺序

将根据转换插件或 preset 的排列顺序依次执行。

  • 插件在 Presets 前运行。
  • 插件顺序从前往后排列。
  • Preset 顺序是颠倒的(从后往前)。

插件参数

插件和 preset 都可以接受参数,参数由插件名和参数对象组成一个数组。

如果不指定参数,下面这几种形式都是一样的:

1
{ "plugins": ["pluginA", ["pluginA"], ["pluginA", {}]] }

要指定参数,则传递一个以参数名作为键(key)的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{ 
"plugins": [
[
"transform-async-to-module-method",
{
"module": "bluebird",
"other": {
"a": "aaa",
"b": "bbb"
}
}
]
]
}

preset 的设置参数的工作原理完全相同。

插件开发

参考手册

本质上就是一个函数。

预设(Presets)

官方 Preset

我们已经针对常用环境编写了一些 preset:

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

Stage-X (实验性质的 Presets)

这些提案可能会有变化,因此,特别是处于 stage-3 之前的任何提案,请务必谨慎使用。

TC39 将提案分为以下几个阶段:

  • Stage 0 - 设想(Strawman):只是一个想法,可能有 Babel插件。
  • Stage 1 - 建议(Proposal):这是值得跟进的。
  • Stage 2 - 草案(Draft):初始规范。
  • Stage 3 - 候选(Candidate):完成规范并在浏览器上初步实现。
  • Stage 4 - 完成(Finished):将添加到下一个年度版本发布中

创建 Preset

如需创建一个自己的 preset,只需导出一份配置即可。preset 可以包含其他的 preset,以及带有参数的插件。

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = function() {
return {
presets: [
require("@babel/preset-env")
],
plugins: [
"pluginA",
require("@babel/plugin-proposal-object-rest-spread"),
[require("@babel/plugin-proposal-class-properties"), { loose: true }],
]
};
}

preset-env 与 polyfill

官方文档
知乎 - Babel7 中 @babel/preset-env 的使用

babel使用 preset-env能将最新的语法转换为ecmascript5的写法,当我们需要使用新增的全局函数(比如promise, Array.from)和实例方法(比如 Array.prototype.includes )时就需要引入 polyfill, 一般用法在 index.js文件的最上层引入,或是在打包文件的entry 入口处引入,这样做的缺点是会全局引入整个polyfill包,比如promise会全局引入,污染全局环境。

引入方式:

1
2
3
4
import "@babel/polyfill";
// babel7开始推荐使用下面的方式引入,使用require也可以。
import "core-js/stable";
import "regenerator-runtime/runtime";

与 useBuiltIns 结合使用

结合useBuiltIns选项可以定义@babel/polyfill的引入方式。

  • entry:需要在项目入口文件最顶部引入import '@babel/polyfilll',会结合targets转换为一系列引入语句,去掉目标浏览器已支持的polyfilll模块,不管代码里有没有用到,只要目标浏览器不支持都会引入对应的polyfilll模块。
  • usage:不需要手动在代码里写import '@babel/polyfilll',打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到的部分polyfilll模块,但任然需要被install。
  • false:需要手动引入,babel对 import '@babel/polyfilll'不作任何处理,也不会自动引入 polyfilll 模块。官方建议在webpack配置中作为入口(entry)引入,而不是在入口 js文件中使用import/require语句。
1
2
3
4
// useBuiltIns选项为false 或 干脆就没用preset-env 的情况下,建议使用该方式引入polyfill
module.exports = {
entry: ["@babel/polyfill", "./app/js"],
};

@babel/plugin-transform-runtime

babel的polyfill和runtime的区别

babel-polyfill 使用场景

Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。

babel-runtime 使用场景

Babel 转译后的代码要实现源代码同样的功能需要借助一些帮助函数,而这些帮助函数可能会重复出现在一些模块里,导致编译后的代码体积变大。Babel 为了解决这个问题,提供了单独的包 babel-runtime 供编译模块复用工具函数。

githook-分支与提交信息校验

发表于 2020-01-14

Branch校验

validate-branch-name

安装

1
npm i husky validate-branch-name --save-dev

Husky requires Node >= 8.6.0 and Git >= 2.13.2
Husky version >= 1.0.0
笔记时validate-branch-name版本为1.0.4

配置

1
2
3
4
5
6
7
8
9
10
11
12
// package.json
{
"husky": {
"hooks": {
"pre-push": "validate-branch-name"
}
},
"validate-branch-name": {
"pattern": "^(master|develop){1}$|^(feature|fix|hotfix|release)\/.+$",
"errorMsg": "branch format error."
}
}

Message校验

优雅的提交你的 Git Commit Message

安装

1
2
npm install --save-dev @commitlint/{cli,config-conventional}
npm install --save-dev husky

配置

支持以下格式的配置文件:.commitlintrc.js, .commitlintrc.json, or .commitlintrc.yml file or a commitlint field in package.json.

比如我们在项目目录下创建配置文件.commitlintrc.js, 写入:

1
2
3
4
5
6
7
module.exports = {
extends: [
'@commitlint/config-conventional'
],
rules: {
}
};

**结合husky**,package.json中添加如下:

1
2
3
4
5
6
7
8
// package.json
{
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}

Passing husky’s HUSKY_GIT_PARAMS to commitlint via the -E|--env flag directs it to the relevant edit file. -e would default to .git/COMMIT_EDITMSG.

Message辅助填写

优雅的提交你的 Git Commit Message

Commitizen: 替代你的 git commit

它提供的 git cz 命令替代我们的 git commit 命令, 帮助我们生成符合规范的 commit message.

cz-conventional-changelog (一个符合 Angular团队规范的 preset). 使得 commitizen 按照我们指定的规范帮助我们生成 commit message.

全局安装

1
2
npm install -g commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc #意思是home目录下新建.czrc文件,内容为{ "path": "cz-conventional-changelog" }

使用

用git cz替代git commit命令即可。

推荐规范说明

目前规范使用较多的是 Angular 团队的规范, 继而衍生了Conventional Commits specification. 很多工具也是基于此规范, 它的message格式如下:

1
2
3
4
5
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

通过 git commit 命令带出的 vim 界面填写的最终结果应该类似如上这个结构, 大致分为三个部分(使用空行分割):

标题行: 必填, 描述主要修改类型和内容

主题内容: 描述为什么修改, 做了什么样的修改, 以及开发的思路等等

页脚注释: 放 Breaking Changes 或 Closed Issues

分别由如下部分构成:

type: commit 的类型

feat: 新特性

fix: 修改问题

refactor: 代码重构

docs: 文档修改

style: 代码格式修改, 注意不是 css 修改

test: 测试用例修改

chore: 其他修改, 比如构建流程, 依赖管理.

scope: commit 影响的范围, 比如: route, component, utils, build…

subject: commit 的概述, 建议符合 50/72 formatting

body: commit 具体修改内容, 可以分为多行, 建议符合 50/72 formatting

footer: 一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接.

这样一个符合规范的 commit message, 就好像是一份邮件。

拓展阅读1:自定义校验规则、辅助规则

也许 Angular 的那套规范我们不习惯, 那么可以通过指定 Adapter cz-customizable 指定一套符合自己团队的规范.

安装

1
2
3
npm i -g cz-customizable
#or
npm i -D cz-customizable

修改 .czrc 或 package.json

1
2
3
4
5
6
7
8
9
10
11
// .czrc
{ "path": "cz-customizable" }
// 或者
// package.json
{
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}
}

自定义规则

在~/ 或项目根目录下创建 .cz-config.js 文件, 维护你想要的格式,比如: leohxj/.cz-config

对自定义规则的校验

如果你使用的是自定义的 commitizen adapter, 那么你需要修改commitlint的校验配置为commitlint-config-cz

1
npm i -D commitlint-config-cz

.commitlintrc.js修改为:

1
2
3
4
5
6
7
module.exports = {
extends: [
'cz'
],
rules: {
}
};

拓展阅读2:standard-version 自动生成 CHANGELOG

通过以上工具的帮助, 我们的工程 commit message 应该是符合 Angular团队那套, 这样也便于我们借助 standard-version 这样的工具, 自动生成 CHANGELOG, 甚至是 语义化的版本号(Semantic Version).

Centos联网设置

发表于 2019-12-03

背景:每次安装完centos后,无论是虚拟机或者实体机默认都无法访问网络;多次搜索解决方案后,于此总结一下。

以太网

解决实例

  1. /etc/sysconfig/network-scripts/ifcfg-enp4s0中ONBOOT修改为yes
  2. 重启网络服务service network restart,如果提示找不命令,则用service NetworkManager restart试试

配置项一览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ls /etc/sysconfig/network-scripts/
# 里面ifcfg开头的ifcfg-ens33就是
vim /etc/sysconfig/network-scripts/ifcfg-ens33
# 大约配置是:
TYPE=Ethernet #网络类型为:Ethernet以太网
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none #设置为none禁止DHCP,设置为static启用静态IP地址,设置为dhcp开启DHCP服务
DEFROUTE=yes #默认网卡
IPV4_FAILURE_FATAL=no
##IPV6INIT=yes
##IPV6_AUTOCONF=yes
##IPV6_DEFROUTE=yes
##IPV6_FAILURE_FATAL=no
##IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33 #定义网络设备名称
UUID=45fe5552-7117-4c84-9742-c87adfa222b9
DEVICE=ens33 #指出设备名称
ONBOOT=yes #设置为yes,开机自动启用网络连接ZONE=publicI
PADDR=192.168.31.10
NETMASK=255.255.255.0
GATEWAY=192.168.31.2
DNS1=192.168.31.2

wifi

安装NetworkManager-wifi

1
yum -y install NetworkManager-wifi #安装完后,reboot,wifi网卡应该已经启动,如果未启动执行 nmcli r wifi on 开启无线网

配置wifi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#系统内置网络配置界面,可以在这个图形界面链接、禁用wifi
nmtui

#扫描可用于连接wifi
nmcli dev wifi
#添加一个wifi的连接
nmcli dev wifi con “无线网络名称” password “无线网络密码” name “任意连接名称(删除,修改时用)”
# !!!特别注意一下,如果先链接的以太网再连的wifi,一定要禁用以太网链接,不只是把ONBOOT改为no,而且要断开链接,这个可以在nmtui界面把以太网设置为deactive

#添加成功后查看已创建的wifi连接
nmcli conn

#如果wifi没有连接上
nmcli con up wifi连接名(刚才nmtui创建的连接)

#修改该连接为开机自动连接
nmcli con mod wifi连接名 connection.autoconnect yes

注意事项

  1. 如果只启用wifi,那么需要关闭以太网,把ONBOOT=no,否则没插网线的情况下,路由可能还是走的以太网。如果需要配置wifi的静态IP,关键部分基本和以太网的一样。
  2. 其他情况:如果无线网卡安装不正常,可以lspci命令查看网卡型号,使用lspci命令需要先安装yum -y install pciutils*,查看设备后下载相应的驱动程序进行安装。查询内核日志,查看是否需要安装无线网卡的固件:dmesg | grep firmware

参考链接

CentOS 配置无线网络,开启wifi

浏览器本地JS计算文件MD5

发表于 2019-02-14

浏览器本地JS计算文件MD5

js-spark-md5

全量计算

示例

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
let MD5IsRunning = false;
getFileMD5(file) {
const errTipText = "MD5计算失败,请重新选择文件!";

return new Promise((resolve, reject) => {
if (MD5IsRunning) {
reject("操作繁忙!");
}

if (!file) {
reject(errTipText);
}

let fileReader = new FileReader();

fileReader.onload = e => {
MD5IsRunning = false;
if (file.size != e.target.result.byteLength) {
reject(errTipText);
} else {
resolve(SparkMD5.ArrayBuffer.hash(e.target.result));
}
};

fileReader.onerror = function() {
MD5IsRunning = false;
reject(errTipText);
};

MD5IsRunning = true;

fileReader.readAsArrayBuffer(file);
});
}

增量计算

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 doIncrementalTest(file) {
if (running) {
return;
}

var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
chunkSize = 2097152, // read in chunks of 2MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
fileReader = new FileReader();

fileReader.onload = function (e) {
spark.append(e.target.result); // append array buffer
currentChunk += 1;

if (currentChunk < chunks) {
loadNext();
} else {
running = false;
console.log('success:', spark.end()); // compute hash
}
};

fileReader.onerror = function () {
running = false;
console.log('error');
};

function loadNext() {
var start = currentChunk * chunkSize,
end = start + chunkSize >= file.size ? file.size : start + chunkSize;

fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}

running = true;
loadNext();
}

xlsx,js处理、生成excel文件

发表于 2018-07-18

xlsx,js处理、生成excel文件

参考

导出为excel的例子
文档
js-xlsx/github

json生成xlsx文件关键代码

1
<div onclick="doit('xlsx')">abc</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function doit(type) {
var jsonData = [{
a:1,
b:2,
c:3
},
{
a:11,
b:22,
c:33
},
{
a:21,
b:22,
c:23
}]
var wb = XLSX.utils.book_new(); //生成新的workbook实例
var ws = XLSX.utils.json_to_sheet(jsonData); //将json转化为worksheet对象
XLSX.utils.book_append_sheet(wb, ws, '表单11'); //向workbook实例中添加表单(worksheet)
XLSX.writeFile(wb, 'test.xlsx'); //保存文件
}

JS表单序列号与反序列化

发表于 2018-06-22

JS表单序列号与反序列化

jQuery有这两个方法 .serializeArray() 与.serialize().

.serializeArray()返回: Array

描述: 将用作提交的表单元素的值编译成拥有name和value对象组成的数组。例如[ { name: a value: 1 }, { name: b value: 2 },…]

一般用于Ajax POST

.serialize()返回: String

描述: 将用作提交的表单元素的值编译成字符串。如:single=Single&multiple=Multiple&multiple=Multiple3&check=check2&radio=radio1

一般用于GET

主要介绍.serializeArray()方法。

这个方法很方便,但是在多选的情况先会出现一些问题:

比如select,checkbox。此时返回的是[{multiple:Multiple},{multiple:Multiple3},{check:check1}},{check:check2}]

而我们期望的是[{multiple:[Multiple,Multiple3]},{check:[check1,check2]}]

而且我们通过ajax提交的请求数据根对象期望是json对象而不是直接的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
/**
* 序列化表单元素为JSON对象
* @param form Form表单id或表单jquery DOM对象
* @returns {}
*/
var serialize = function(form){
var $form = (typeof(form)=="string" ? $("#"+form) : form);
var dataArray = $form.serializeArray();
var result={};
$(dataArray).each(function(){
//如果在结果对象中存在这个值,那么就说明是多选的情形。
if(result[this.name]){
//多选的情形,值是数组,直接push
result[this.name].push(this.value);
}else{
//获取当前表单控件元素
var element = $form.find("[name='"+ this.name +"']")[0];
//获取当前控件类型
var type = ( element.type || element.nodeName ).toLowerCase();
//如果控件类型为多选那么值就是数组形式,否则就是单值形式。
result[this.name] = (/^(select-multiple|checkbox)$/i).test(type) ? [this.value] : this.value;
}
});
return result;
};
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
/**
* 通过js设置表单值
* @param form Form表单id或表单jquery DOM对象
* @param data json对象,多选时为数组
* 代码实现参考此开源项目https://github.com/kflorence/jquery-deserialize/
*/
var deserialize = function(form,data){
var rcheck = /^(?:radio|checkbox)$/i,
rselect = /^(?:option|select-one|select-multiple)$/i,
rvalue = /^(?:button|color|date|datetime|datetime-local|email|hidden|month|number|password|range|reset|search|submit|tel|text|textarea|time|url|week)$/i;

var $form = (typeof(form)=="string" ? $("#"+form) : form);

//得到所有表单元素
function getElements( elements ) {
//此处elements为jquery对象。这个map函数使用来便利elements数组的.如存在多个form表单,则便利多个form表单
return elements.map(function(index, domElemen) {
//this代表form表单,this.elements获取表单中的DOM数组. jQuery.makeArray 转换一个类似数组的对象成为真正的JavaScript数组。
return this.elements ? jQuery.makeArray( this.elements ) : this;
//过滤不启用的选项
}).filter( ":input:not(:disabled)" ).get();
}
//把表单元素转为json对象
function elementsToJson( elements ) {
var current,elementsByName = {};
//elementsByName对象为{控件名:控件元素或控件元素数组}
jQuery.each( elements, function( i, element ) {
current = elementsByName[ element.name ];
elementsByName[ element.name ] = current === undefined ? element :
//如果已经是一个数组了,那么就添加,否则构造一个数组
( jQuery.isArray( current ) ? current.concat( element ) : [ current, element ] );
});
return elementsByName;
}

var elementsJson = elementsToJson(getElements($form));
//data是一个对象
for(var key in data){
var val = data[key];
var dataArr = [];//更具数据直接构造一个jQUery序列化后的数组形式。
//判断值是否为数组
if( $.isArray(val)){
for(var i= 0,v;v=val[i++];){
//是数组那么就变成多个对象形式
dataArr.push({name:key,value:v});
}
} else{
//不是数组直接构造
dataArr.push({name:key,value:val});
}

//根据数据构造的这个数组进行操作
for(var m= 0,vObj;vObj=dataArr[m++];){
var element;
//如果表单中无元素则跳过
if ( !( element = elementsJson[vObj.name] ) ) {
continue;
}
//判断元素是否为数组,暂时获取第一个元素,后面会有迭代赋值。
var type = element.length?element[0]:element;
//元素类型
type = ( type.type || type.nodeName ).toLowerCase();

var property = null;
//是单值类型
if ( rvalue.test( type ) ) {
element.value = (typeof(vObj.value)=="undefined" || vObj.value==null)?"":vObj.value;
//checkbox
} else if ( rcheck.test( type ) ) {
property = "checked";
//select
} else if ( rselect.test( type ) ) {
property = "selected";
}
//判断类型是否为多选
if(property) {
//如果是,则迭代多选的元素赋值
for(var n= 0,e;e=element[n++];){
if(e.value==vObj.value){
//设置选中
e[property] = true;
}
}
}
}
}
};
12<i class="fa fa-angle-right"></i>

16 日志
9 标签
© 2021 heguichuan
由 Hexo 强力驱动
主题 - NexT.Muse