vue实现input强制输入Number类型

需求

如果想要实现input输入Number类型,又不想用难看的type=’number’怎么处理

1
<el-input style="width:200px" :maxlength="4" @keyup.native="isNumber(form,'recruitsCount')" v-model.number="form.recruitsCount" placeholder="请输入数字,不能为空"></el-input>
1
2
3
4
5
6
7
8
isNumber(obj,key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
},

测试代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
</head>

<body>
<div id="app">
<input style="width:200px" :maxlength="4" @keyup="isNumber(form,'count')" v-model.number="form.count" placeholder="请输入数字,不能为空"></input>
</div>
</body>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
form: {
count: null
}

},
methods: {
isNumber(obj, key) {
let isnumber = new RegExp('^[0-9]\\d*$')
if (!isnumber.test(obj[key])) {
obj[key] = null
} else {
return true
}
}
}
})
</script>

</html>

说明

  • 配合.number修饰符实现纯数字输入
  • .nmber的特点是将字符串转为num型,如果转换结果是NaN,则原样输出。
  • 因此如果第一个字符输入非数字,那么就会变成字符串,根据强制类型转换规则,首字母是数字才能转换成功,如1f这种就会转换为1,因此就出现预想中的输入非数字转换为数字的效果。
  • 因此关键是第一个字符不能为非数字,配合keyUp事件,检测首字母是否是数字即可,检查全部也无所谓,因为keyup事件发生时,input事件未发生,input又经过类型转换,因此能完美运行

编程日志(08)—vue实现plupload分片上传
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<!-- plupload封装 -->
<template>
<div class="supload">
<div class="clearfix list-picture" v-if="fileList.length>0&&listType=='picture'">
<div v-for="ff,index in fileList" class="pic-bx small">
<img :src="ff.showUrl">
<div class="cl-bx">
<i class="el-icon-close" @click="deleteFile(ff,index)"></i>
</div>
</div>
</div>
<div class="plupload clearfix" v-show="fileList.length<maxFile">
<template v-if="listType=='text'">
<div class="text_trigger">
<div :id="trigger_id" >
<i class="el-icon-document" ></i>
<span>{{triggerText}}(小于{{maxSize}}Mb)</span>
</div>
</div>
</template>
<template v-if="listType=='picture'">
<div class="fm-up" :id="trigger_id">
<div class="cc">
<i class="el-icon-circle-plus-outline"></i>
<p>点击选择</p>
<span>支持jpg/png格式</span>
<span>560px×400px</span>

</div>
</div>
</template>
</div>
<div class="pgs" v-if="progress>0&&listType=='text'">
<el-progress :text-inside="true" :stroke-width="20" :percentage="progress" status="success"></el-progress>
</div>

<div class="list-text" v-if="listType=='text'">
<template v-for="ff,index in fileList">
<div class="clearfix item-text">
<!-- <div class="progress" :style="{'width':ff.progress+'%'}"></div> -->
<div class="title">{{ff.name}}</div>
<div class="close" @click="deleteFile(ff,index)">删除</div>
</div>

</template>
</div>

</div>
</template>

<script>
/**
* 强制multi_selection为false使其只能逐个上传
*
*/
/**
* tip:
* 为了操作方便fileList以数组形式传入props,直接操作fileList可以实现数据双向绑定
* 并不建议这样去做,父子组件之间尽量保持单向数据流
* 因此可以暂不向外提供文件删除上传的回调入口
*/
/*
xlsx等文档可以在拓展过滤为zip的情况下可选的bug是因为xlsx本质是个压缩包,将xlsx拓展名改成zip再看看
*/
import plupload from 'plupload'
import {guid,getExtName} from '../../lib/util.js'

import config from '../../config/config.js'
export default {
data() {
return {
trigger_id:null, //触发按钮的ID属性
plup:null, // plupload实例对象
progress: 0, // 单文件上传进度
option: null
};
},
props: {
accept: {
type: String,
default: 'image/png,image/jpeg,audio/mp3'
},
// 支持的拓展名以逗号隔开jpg,gif,png,bmp,mp4
extensions:{
type: String,
default: 'mp4'
},
maxFile: {
type: Number,
default: 1
},
maxSize: {
type: Number,
default: 512 //mb
},
// 文件列表
fileList: {
default(){
return []
}
},
// text,picture 以picture形式呈现时,如果超过可上传数量,上传界面将消失
listType:{
default: 'text'
},
params:{
type:Object,
required:true
},
triggerText:{
default:'选择文件'
}
},
computed:{

},
components: {

},

methods: {
transGetUrl(url) {
return config.imgHost + url
},


deleteFile(ff,index){
this.fileList.splice(index,1)
},

},
watch:{
extensions(ne,olg){
this.option.filters.mime_types=[{
title: 'files', extensions: ne
}]

this.plup.destroy()
this.plup = new plupload.Uploader(this.option);
this.plup.init()

}

},
mounted(){
const $this = this;
this.option = {
runtimes: 'html5,flash,silverlight,html4',
url: config.downloadHost+'upload/pluploadUpload',
//url: '/preschool/upload/pluploadUpload',
browse_button: this.trigger_id,
chunk_size: '2048kb',
multi_selection:false,
filters: {
mime_types: [{ title: 'files', extensions: this.extensions }],
max_file_size: this.maxSize+'mb',
prevent_duplicates: true
},
multipart:true,
multipart_params:this.params,
init: {
FilesAdded: function(up, files) {
up.start();
},
UploadProgress: function(up, file) {
$this.progress = file.percent;
},
BeforeUpload: function(up,file) {},
FileUploaded: function(uploader,file,res){
// response,responseHeaders,status
if (res.status==200){
let result = JSON.parse(res.response);
if (result.code==0){
setTimeout(function(){
$this.progress = 0;
$this.fileList.push({
name:file.name,
// url: result.data.replace(/(.*)(pluploadDir)/g,config.imgHost),
url: result.data,
showUrl: $this.transGetUrl(result.data),
type: getExtName(result.data)
});
},500);
}
} else {
}
},
Error:function(up,err){},
Refresh: function(up){},
Destroy(up){},
OptionChanged:function(up,option_name,new_value,old_value){}
// Error: this.error
}
}
this.plup = new plupload.Uploader(this.option);
this.plup.init();
},
beforeMount(){
this.trigger_id = guid();
}
};
/**
* 额外参数传递:
* 1.设置multipart为true
* 为true时将以multipart/form-data的形式来上传文件,为false时则以二进制的格式来上传文件。
* 2. 设置multipart_params:{userId:1024}
* 方式二
* 设置init的BeforeUpload(up,file){up.setOption("multipart_params",{userId:1024})}
* 也可以up.setOption({'multipart_params':{userId:1030}});这样可以方便设置多个参数
* 像 up.setOption({'multipart':true,'multipart_params':{userId:1032}})
* 在某些情况下可能更方便
*/
</script>

调用方式

1
2
<plup-vue :file-list="fileListForAttach" list-type="text" :params="extendParamsForUploadComponent" :extensions="format" :maxSize="1024">
</plup-vue>

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
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
/*上传组件*/
.supload{
line-height: 30px;
.pgs{
line-height:20px;width:600px;
}
.list-text{
line-height:20px;width:600px;
.item-text{
line-height: 30px;
height: 30px;
background: #ededed;
position: relative;
margin: 5px auto;
.progress{
position: absolute;
height: 100%;
width: 1%;
background: green;
top: 0;left: 0;
opacity: 0.2;
transition: width 0.5s;
-moz-transition: width 0.5s;
-webkit-transition: width 0.5s;
-o-transition: width 0.5s;

}
.title{
float: left;height: inherit;line-height: inherit;padding: 0 10px;
width: 500px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.close{
float: right;height: inherit;
line-height: inherit;padding: 0 10px;
cursor: pointer;
color:$colorRed
}
}
}
.list-picture {

.pic-bx{
float: left;
margin: 10px 10px 10px 0;
border:1px dashed #ededed;
img{width: 100%; }
position: relative;
}
.cl-bx{
width: 100%;height: 25px;background: rgba(1,1,1,0.5);
position: absolute;right: 0;top: 0;
text-align: right;
line-height: 25px;
i{
font-size: 15px;
color:#fff;
margin-right: 3px;
cursor: pointer;
}
}
.small{
width: 240px;height: 180px;overflow: hidden;
border-radius: 4px
}
}
.plupload{
.text_trigger{
display: inline-block;
position: relative;
color:$colorBlue;
cursor: pointer;
}

}

}
.fm-up{
width: 240px;height: 180px;
text-align: center;
cursor: pointer;
// border: 1px solid #03A9F4;
.cc{
height: 100px;width: 200px;
line-height: 20px;
margin: auto;
margin-top: 40px;
}
color:#dcdcdc;
border: 1px solid #dcdcdc;
i{font-size: 25px;}
border-radius: 4px;
p{line-height: 20px;}
span{font-size: 12px;line-height: 20px;}
}

编程日志(03)—圣杯布局、双飞翼布局

双飞翼布局

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>
<style>
* {
margin: 0;
padding: 0
}

body {
min-width: 550px;
font-weight: bold;
font-size: 20px;
}

#header,
#footer {
background: rgba(29, 27, 27, 0.726);
text-align: center;
height: 60px;
line-height: 60px;
}

#container {
overflow: hidden;
}

.column {
text-align: center;
}

#left,
#right,
#center {
float: left;
/*三列高度撑开对齐,关键步*/
padding-bottom: 10000px;
margin-bottom: -10000px;
}

#center {
width: 100%;
background: rgb(206, 201, 201);
}

#left {
width: 200px;
margin-left: -100%;
background: rgba(95, 179, 235, 0.972);
}

#right {
width: 150px;
margin-left: -150px;
background: rgb(231, 105, 2);
}

.content {
margin: 0 150px 0 200px;
height: 400px;
}
</style>

<body>
<div id="header">#header</div>
<div id="container">
<div id="center" class="column">
<div class="content">
#center
</div>
</div>
<div id="left" class="column">#left</div>
<div id="right" class="column">#right</div>
</div>
<div id="footer">#footer</div>
</body>

</html>

image

说明

  • 三列布局,中间宽度自适应,两边定宽
  • 中间栏要在浏览器中优先展示渲染
  • 允许任意列的高度最高

查看源码及更多


pkg-node打包可执行文件工具

前言

最近在研发基于electron的桌面应用,其中涉及到外设交互部分需要使用dll实现,在经历了艰难的环境配置之后,最后由于electron内存管理问题导致应用崩溃使得electron+ffi实现动态链接库调用这条路无法走通。当时还剩下两种方案可以尝试:

  1. 自己创建dll封装原生dll调用,感觉成功几率很低。
  2. 写node addons编译成node插件,需要较强c++功底,难度较大,且最后成功概率不高。

为什么坚持了两个星期还不放弃是因为node+ffi这条路已经走通了,证明第三方的dll是没有问题的。所以试着能不能解决electron中出现的问题。

当electron不能实现的事实逐渐明朗,我开始转移方向。首先想到的是nwjs+ffi的方案,随后迅速想到为什么不使用node自己实现呢?将此部分功能单独分割成为第三方应用,通过某种通信方式实现(写缓存、数据库、http等)。最不济的情况是我为每台终端部署node服务(此应用是B2B,需要实施,不需要自己安装,这一点帮了大忙)

最好的方式是不需要安装node等一堆东西,直接是可执行文件。因此我开始寻找node打包成可执行文件的方法。

为什么要将node应用打包成可执行文件

  1. 部署方便,给客户演示和生产环境部署都很方便
  2. 保护源代码(很有用)
  3. 宿主机器其他应用对node版本可能有不同的要求
  4. 测试应用在不同node版本下兼容性

pkg打包的使用方法

将Node.js打包为可执行文件的工具有pkg、nexe、node-packer、enclose等,从打包速度、使用便捷程度、功能完整性来说,pkg是最优秀的。

安装

推荐采用局部安装

1
npm install pkg -D

用法

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
$ pkg -h

pkg [options] <input>

Options:

-h, --help output usage information
-v, --version output pkg version
-t, --targets comma-separated list of targets (see examples)
-c, --config package.json or any json file with top-level config
--options bake v8 options into executable to run with them on
-o, --output output file name or template for several files
--out-path path to save output one or more executables
-d, --debug show more information during packaging process [off]
-b, --build don't download prebuilt base binaries, build them
--public speed up and disclose the sources of top-level project

Examples:

– Makes executables for Linux, macOS and Windows
$ pkg index.js
– Takes package.json from cwd and follows 'bin' entry
$ pkg .
– Makes executable for particular target machine
$ pkg -t node6-alpine-x64 index.js
– Makes executables for target machines of your choice
$ pkg -t node4-linux,node6-linux,node6-win index.js
– Bakes '--expose-gc' into executable
$ pkg --options expose-gc index.js

项目的入口点是强制CLI参数。它可能是:

  • 输入文件的路径
  • package.json ,pkg会去寻找bin属性作为入口文件
  • 路径是一个目录地址,pkg会在目录下寻找package.json文件,然后如上

targets

pkg能同时为多个平台打包。使用-t 选项,定义由三部分组成,如node6-macos-x64:

  • nodeRange node${n} or latest
  • platform freebsd, linux, alpine, macos, win
  • arch x64, x86, armv6, armv7

native addons

如果说pkg还有哪儿还可以改进的地方,那就是无法自动打包二进制模块.node文件。如果你的项目中引用了二进制模块,如sqlite3,那么你需要手动地将.node文件复制到可执行文件同一目录,我通常使用命令cp node_modules/*/.node .一键完成。但是,如果你要跨平台打包,例如在windows上打包linux版本,相应的二进制模块也要换成linux版本,通常需要你手动的下载或者编译。

后台运行与开机自启动

开机自启动原理就是将exe文件,通过命令注册到开机服务项。

SC Create 创建一个Windows系统服务
描述SC 是用于与服务控制管理器和服务进行通信的命令行程序。

命令行,有一点需要注意,等号和值之间要有空格,否则会报错

1
sc create test binPath="E:\node\app.exe" start=auto

可能遇到问题:

第一次打包的时候,会遇到下包很慢很可能超时的问题。到https://github.com/zeit/pkg-fetch/releases
下载对应的包,然后~/.pkg-cache/2.5/目录下,改名为fetched-v8.11.3-macos-x64(参考运行时下的包名字改)即可。
参考https://github.com/zeit/pkg/issues/419

windows 路径一般是用户/username/.pkg-cache

注册为系统服务

当我使用 sc create xx binPath= xx start= auto创建完系统服务后,发现启动报错,大致找了一下。

exe注册成服务了!但是启动服务的时候会报[错误1053:服务没有及时响应启动或控制请求.] ,我想应该是我的exe被服务启动后没有能回复一个服务的消息,不知道要在exe里面做什么样的操作才能不报这样的错误,不是所有的程序都能添加到服务里,要是专问的程序才行,程序里要相应控件,才会有返回值,返回告诉操作系统服务开启情况,QQ程序开发时就不是用来为作为服而设计的,所以不能当服务用

因此参考issues137找到一个nssm的工具,用法很简单。大致罗列一下

第一步:先将程序地址设置到环境变量,就把nssm.exe放到目录下好了。
第二步:nssm install servicename
第三部:按要求配置,注意设置日志输出文件和时间戳
第四步:进入服务列表进行设置,在恢复里面设置第一次失败重启(因为要做开机启动嘛),第二次也重启服务,第三次无操作,千万别设置重启电脑,之前试了一次把硬盘引导分区都破坏了,导致连BIOS都进不去,只好拆下硬盘,用其他电脑PE重做分区,还好是一个新的测试电脑。

参考


编程日志(02)—同一浏览器多用户登录session冲突问题

账号登录冲突

在后端程序中,往往将session和成功登录的用户id进行绑定来保持登录状态,这样前后端交互时就不用显示传递用户ID来判断是哪个用户,并通过设置session过期时间来保障账户的安全。

session的值会被后端设置在cookie,前端无需做什么特别操作,正常请求接口即可。由于session就相当于用户id,当同一个浏览器存在多个标签页,A页是A用户,B页是B用户,那么session绑定的其实是后登陆的那一个用户的id,因为这种绑定是在登陆接口处理的。这样的话假如请求的数据(比如查看个人信息)是基于session拿用户id的,就会导致A用户拿到B用户的数据。

一个临时思路

登陆成功后将用户id加密后存储到本地,在每个需要验证的页面加上这个参数,当用户刷新页面时与本地存储的值进行比较,不符合就跳转登陆页。(后登陆的用户会覆盖上一个用户的本地值,而url里的参数不会变所以会导致URL中获取的值和本地不一致)

由于项目使用的是vue,可以将在路由里设置钩子函数,在跳转路由前检查对应数据,路由是hash模式,获取url参数和本地存储的方法可以自己想办法实现

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

router.beforeEach((to, from, next) => {
// 工具方法,获取url地址中traceid的值
let urltraceid = getPop('traceid');
// 获取localStorege中traceid的值
let localtraceid = getLocalStorage('traceid').traceid
console.log('url=%s,local=%s ', urltraceid, localtraceid)
// 先判断两个参数是否存在
if (urltraceid && localtraceid) {
if (urltraceid == localtraceid) {
next()
} else {
let url = window.location.href.replace(/[\?,\#]\S*/g, '');
window.location.href = url;
next({
path: "/"
});
}
}

})

在登陆的时候设置参数,跳转地址类似于下面

1
index.html?traceid=xx#/main

编程日志(01)--cocosCreator实现类似jQuery的ajax

为什么在cocosCreator中不使用axios等工具

cocosCreator版本:2.0.7

在游戏场景中少不了要使用网络交互,在以往的编程中,用jQuery实现或者axios实现并不是很难,从论坛的反馈来看,cocos对ES6的支持尤其是promise,经过长时间的更新似乎依然没有很好地完善,由于开发时间限制,这里就没有去体验这些说法直接使用回调函数避免兼容性问题原始实现ajax请求。

构造ajax方法

使用XMLHttpRequest实现ajax请求并不困难,这里需要注意的是针对IE10以下版本ActiveXObject的兼容性问题,目前个人没有去验证这一点,且项目本生要求支持基于vue的webview。下面是官方文档给的一个示例

1
2
3
4
5
6
7
8
9
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status < 400)) {
var response = xhr.responseText;
console.log(response);
}
};
xhr.open("GET", url, true);
xhr.send();

我的目的是实现像JQuery一样使用$.ajax(),只需要定义好传参结构就可以了,且由于目前是自己使用,可以只给出核心功能,参数的检验可以之后完善。下面是完整代码,定义cc.global方便全局使用。

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

cc.global = {
ajax: function(obj) {

let err = '';
var xhr = cc.loader.getXMLHttpRequest();
xhr.open(obj.type, obj.url);
if (obj.type.toUpperCase() == 'GET') {
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");

} else if (obj.type.toUpperCase() == 'POST') {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && (xhr.status >= 200 && xhr.status <= 207)) {
err = false;
} else {
err = true;
}
if (err) {
//错误处理
if (obj.error) {
obj.error()
}
} else {
if (obj.success) {
obj.success(JSON.parse(xhr.responseText))
}
}
};

xhr.send(obj.data ? cc.global.qsPrimitive(obj.data) : null);
}
}
1
2
3
4
5
6
7
8
9
10
11
cc.global.ajax({
type: 'post',
url: '',
data: {
userCode:'ac',
userPwd:'pwd'
},
success: function(res) {
console.log(res)
}
})

非常简单,但是还有问题需要说明一下

qs的实现

根据post请求协议规定,post数据需要放在Request body中传输,四种常见的 POST 提交数据方式(application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml),这些可以再研究一下。

在项目中主要用到的还是get请求和post的请求,他们直观的区别就在于GET参数是放在Query Params上,通过在地址后面拼接如下qs序列化的参数来传参,而POST不是说不可以将参数拼在地址里,关于这一点可以参考99%的人都理解错了HTTP中GET与POST的区别,只是有参数的长度限制以及规范使用。目前的项目中需要传递比较大的数据,因此需要将其放在在Request body中

1
?key1=value1&key2=value2

(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url

不管是GET还是POST的x-www-form-urlencoded方式,都要求将参数qs序列化。

下面是从node querrystring模块源码扒出的简化的方法,核心只是一个循环语句只需注意一下中文编码的问题

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
// 简单查询序列化
qsPrimitive: function(obj) {
var sep = sep || '&';
var eq = eq || '=';

// 假设value都是字符串
if (obj !== null && typeof obj === 'object') {
var keys = Object.keys(obj);
var len = keys.length;
var flast = len - 1;
var fields = '';
for (var i = 0; i < len; ++i) {
var k = keys[i];
var v = obj[k];
var ks = encodeURIComponent(k) + eq;
fields += ks + encodeURIComponent(v);
if (i < flast)
fields += sep;

}
return fields;
} else {
return '';

}

},


Zepto源码学习-核心篇

0.前言

Zepto源码1.2.0未压缩带注释约有1835行,之前是当做设计模式来阅读,并没有深入。且在当前前端环境下,JQuery的重要性大大降低了,从事开发工作大多用的是Angular、Vue等,并没有将jQuery用到精通。以训练为目的,尝试将Zepto源码讲的清楚一点

1.构建Zepto

下载源码并install构建

1
2
3
4
git clone https://github.com/madrobby/zepto.git
cd zepto
npm install
npm run dist

这部分内容可以通过阅读官方仓库指南获得,通过进一步分析package.json文件,可以看出是用coffee-script的cake方法做的构建文件make,但是源码并没有用coffee-script来写,所以不用担心。关于coffee-script,个人觉得如果闲的厉害可以去学学,否则不如去学点别的,或者多看一些源码。

2.模块分析

首先要明白一点,如果第一次看,逐行分析是没有意义的,必须先了解整体结构。先整体再局部,然后细化。
通过阅读手册或者make文件可以知道最终代码会包含zepto event ajax form ie几个模块。实际上,手册排版也大致是按照这些模块划分的。

中文手册

3.整体结构分析

如果对原型链有一定的了解和开发经验,将很容易理解zepto的源码结构,如果是新手,那么先了解原型链,再阅读源码,将会加深对原型链的理解。下面给出打包出的zepto.js整体结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function(global, factory) {
if (typeof define === 'function' && define.amd)
define(function() {
return factory(global)
})
else
factory(global)
}(window, function(window) {
// Zepto核心
var Zepto = (function() {})()
window.Zepto = Zepto
// 一种短路运算,如果成立则执行后面的,否则不执行
window.$ === undefined && (window.$ = Zepto)
// 相应的,也可以这么写
// '$' in window || (window.$ = Zepto);
// 以下是其他Zepto模块
;
(function($) {})(Zepto)
return Zepto
}))

这段代码首先关注最外层的IIFE(立即执行函数表达式),针对AMD模块做了处理。这里可以拓展了解JS的模块化规范CommonJS、AMD等。其他要点已经在代码里做了注释

4.核心模块

上面已经介绍了整个代码的组织结构,接下来分析一下最重要的Zepto核心是怎么实现的。这里可以利用chrome调试工具逐行运行,观察代码的调用栈来了解运行过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="./dist/zepto.js">

</script>
</head>
<body>
<div class="z"></div>
</body>
<script type="text/javascript">
debugger
$('.z').html('x')
</script>
</html>

写一个简单的页面,从第一行设置断点,然后逐步运行。

4.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
var Zepto = (function() {
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}
// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. This method can be overridden in plugins.
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}
zepto.init = function(selector, context) {
// ...
return zepto.Z(dom, selector)
}
// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context) {
return zepto.init(selector, context)
}
zepto.Z.prototype = Z.prototype = $.fn
$.zepto = zepto
return $
})()

4.2 zepto.Z和Z对象

这里有一个关键的对象zepto.Z,返回Z对象实例

1
2
3
4
5
6
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

可以打断点各种试一试,其实是将选择器选中的dom数组转为对象

1
console.log('%o',$(document).length)

之所以能访问length是因为Z对象实例本身有这个属性

4.3 zepto.init

为了减轻阅读负担,先简化zepto.init函数。简而言之,zepto.init对selector参数,也就是$(selector,context)传入的第一个参数可能出现的情况编写了逻辑分支。看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
zepto.init = function(selector,context){
var dom
if(!selector){
return zepto.Z()
} else if(typeof selector == 'string'){
// ...
} else if(isFunction(selector)){
return $(document).ready(selector)
} else if(zepto.isZ(selector)){
return selector
} else {
// ...
}
return zepto.Z(dom, selector)
}

首先主要逻辑有五条

  1. selector不存在则返回Z空对象
  2. selector是个string则进一步处理
  3. selector是个function则执行ready
  4. selector是Z实例则原样返回,可多层嵌套$($(selector))验证
  5. else进一步处理

按照顺序本来应该分别阐述各个逻辑的实现细节,不过可以先放一放。目前我们依然不能够去深追细节,而应该继续从结构层面分析后续的内容。目前,我们只需有一个模糊概念:一切似乎都与Z对象有某种联系。

4.4 运行原理

到目前为止,我们分析了$(selector)执行的逻辑从而接触了Z对象。接下去从$(selector).html()执行过程进一步了解zepto核心的解构。

首先我们得思考$(selector)拿到的是一个Z对象实例,那么它为什么能够执行html()方法。

首先我们先看一下Z构造方法

1
2
3
4
5
6
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

实例化后应当只有length,selector这两个属性,以及类似数组下标的dom节点,马上想到其他方法应该是委托给了原型。

1
zepto.Z.prototype = Z.prototype = $.fn

通过查看$.fn源码,果然发现了html()方法的定义,还有许多attr等也同样在这里定义,这样我们至少弄清楚了运行的流程,剩下的按部就班的阅读各个部分的实现细节就可以了。

1
console.log($(document).__proto__===$.fn)//true

最后看看html的实现

1
2
3
4
5
6
7
8
html: function(html){
return 0 in arguments ?
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
}) :
(0 in this ? this[0].innerHTML : null)
},

这里面另外又涉及了empty方法,append方法,说明各个方法之间并不是孤立的。建议用debug的方式追溯,这将覆盖绝大部分的源码,所以我们来看看$.fn以外的代码,将$.fn放到最后来看。

4.5 $.extend()方法

查阅手册,$.extend()有两个用法

  • $.extend(target, [source, [source2, …]]) ⇒ target
  • $.extend(true, target, [source, …]) ⇒ target v1.0+

  • 通过源对象扩展目标对象的属性,源对象属性将覆盖目标对象属性。

  • 默认情况下为,复制为浅拷贝(浅复制)。如果第一个参数为true表示深度拷贝(深度复制)。

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
// 观察递归实现拷贝
function extend(target, source, deep) {
for (key in source) {
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key])){
target[key] = {}
}
if (isArray(source[key]) && !isArray(target[key])){
target[key] = []
}
extend(target[key], source[key], deep)
} else if (source[key] !== undefined) {
target[key] = source[key]
}
}
}

// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target) {

// target 如果是布尔值则第二个参数是目标对象
var deep, args = slice.call(arguments, 1)
if (typeof target == 'boolean') {
deep = target
target = args.shift()
}
// 目标对象只有一个,源对象可以有多个
args.forEach(function(arg) {
extend(target, arg, deep)
})
// 即使传入时是布尔值,运行后已经被替换目标对象
return target
}

代码应当是很好理解的,顺便可以看看设计的两个工具方法

1
2
3
4
5
6
7
isArray = Array.isArray ||function(object){ return object instanceof Array }
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

$.isArray = isArray
$.isPlainObject = isPlainObject

我们已经可以参考手册给出的方法顺序,逐个去研究一些工具方法,比如再看一个

  • $.parseJSON
  • $.trim
  • $.noop
    1
    2
    3
    4
    5
    6
    7
    if (window.JSON) $.parseJSON = JSON.parse

    $.trim = function(str) {
    return str == null ? "" : String.prototype.trim.call(str)
    }

    $.noop = function() {}

4.6 其他工具方法

到目前为止,我们已经大致了解Zepto是怎么运行的,随之代码覆盖面的扩大,出现了许多工具库方法,比如isObject,isWindow这些。这个时候可以按照源码顺序,尝试逐行理解代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 驼峰化
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
$.camelCase = camelize

$.contains = document.documentElement.contains ?
function(parent, node) {
return parent !== node && parent.contains(node)
} :
function(parent, node) {
while (node && (node = node.parentNode))
if (node === parent) return true
return false
}
1
2
3
4
5
6
7
8
9
10
11
12
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}

return elements
}
1
2
3
4
// filter是数组原生filter
$.grep = function(elements, callback){
return filter.call(elements, callback)
}

差不多覆盖文档中所有的方法,只剩下–$.fn

4.6 $.fn

参考阅读


Linux部署(一)NodeJs安装

Linux安装NodeJs

一. 源码安装

官方文档

1. 下载

在官网下载对应的Linux二进制文件然后扔到服务器上!或者

1
wget https://nodejs.org/dist/v10.14.2/node-v10.14.2-linux-x64.tar.xz

2. 解压

目前官网下载的是.xz文件,解压到当前文件目录(取决于想放哪,建议放到/usr/local/下,好找一点)

1
tar -xJvf node-v10.14.2-linux-x64.tar.xz

3. 添加软链接到bin目录

为了能直接使用node和npm命令,为/usr/local/bin目录下添加软链接

1
2
ln -s /opt/nodejs/node-v9.8.0-linux-x64/bin/node /usr/local/bin/node
ln -s /opt/nodejs/node-v9.8.0-linux-x64/bin/npm /usr/local/bin/npm

4. 测试

1
2
3
node -v
npm -v
npm list -g --depth 0 // 查看自己全局安装过的包

5. npm源

1
2
3
4
5
6
// 1.设成淘宝的

npm config set registry http://registry.npm.taobao.org/
// 2.换成原来的

npm config set registry https://registry.npmjs.org/

深入了解解构
1
2
3
let o = {a:{b:1,c:2}};
let {a:{b}}=o;
b // 1

a在这里是作为模式,并不会被赋值

对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

1
2
3
4
5
6
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于

1
2
3


如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: ‘aaa’, bar: ‘bbb’ };
baz // “aaa”

let obj = { first: ‘hello’, last: ‘world’ };
let { first: f, last: l } = obj;
f // ‘hello’
l // ‘world’

1
这实际上说明,对象的解构赋值是下面形式的简写(参见《对象的扩展》一章)。

let { foo: foo, bar: bar } = { foo: “aaa”, bar: “bbb” };

1
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: “aaa”, bar: “bbb” };
baz // “aaa”
foo // error: foo is not defined

1
2
3
4
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。


看一个复杂的例子

const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc // Object {start: Object}
start // Object {line: 1, column: 5}
`
上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。


JSsnippet

1. JS生成某个范围的随机数

Math.random() 这个函数可以生成 [0,1) 的一个随机数
而涉及范围的话,就有个边界值的问题。这样就包含四种情况:

  • 1) min ≤ r ≤ max

  • 2) min ≤ r < max

  • 3) min < r ≤ max

  • 4) min < r < max

参考

Read more