generator

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

generator跟函数很像,定义如下:

function* foo(x) {
    yield x + 1;
    yield x + 2;
    yield x + 3;
}

除了return语句,还可以用yield返回多次。yield返回{value:xx,done:false},每次的结果存在value中。

直接调用试试:

1
foo(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

直接调用一个generator和调用函数不一样,fib(5)仅仅是创建了一个generator对象,还没有去执行它。

generator方法调用

不断地调用generator对象的next()方法:
var f = foo(1)
f.next()
Object {value: 2, done: false}
f.next()
Object {value: 3, done: false}
f.next()
Object {value: 4, done: true} //已经返回true,不用再返回了
f.next()
Object {value: undefined, done: true}

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。

直接用for … of循环迭代generator对象,这种方式不需要我们自己判断done:
for (var x of foo(1)) {
    console.log(x); // 依次输出2,3,4
}
直接将对象中的value依次返回出来

来封装一个持久输出1~100的函数

function* increase(){
    for(var i=1;i<101;i++){
        yield i
    }
}

var f = increase()
for(var i = 1;i<101;i++){
    console.log( i == f.next().value ) //100个true
}

yield*

在一个generator中调用其他的Generator方法,需用用

function* foo() {
    yield 0;
    yield 1;
}
function* bar() {
    yield 'x';
    yield* foo();
    yield 'y';
}
for (let v of bar()){
    console.log(v);
};
//依次输出 x,0,1,y

next()方法的参数

如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值,因为在执行异步代码以后, 有时候需要上一个异步的结果,作为下次异步的参数,如此循环:

function* foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z); //或者 yield (x + y + z)
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

注意

  • 申明方式 function * A(){}
  • 直接调用generate函数只是创建一个generate对象,而没有执行内部代码
  • 调动f.next(),只是循环在generate对象中执行操作,遇到yield则返回,下次调用next()紧接着执行,直到遇到下一个yield或者return 返回
  • 如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值
  • 如果执行return()方法, 那么这个迭代器的返回会被强制设置为迭代完毕, 返回{value:xx,done:true}

IScroll

IScroll官网

IScroll还是很不错的,尤其是应用在移动端,移动端body自带滚动,那么内部的列表滚动就不会那么自然,起码没有缓冲效果

记录基本使用demo

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

<script type="text/javascript" src='iscroll-probe.js'></script>
<style>
html,body{
margin:0;
}
ul,li{
margin:0;padding:0;list-style: none;
}
.warp{
width: 100%;
height: 50vh;
overflow: hidden;
position: relative; /*用了定位后才能将滚动条相对于当前窗口而不是文档*/
}
ul{
/*width: 2000px;*/
}
li{
height: 74px;
background: green;
border: 5px solid;
box-sizing: border-box;
/*float: left;
width:200px;*/
}
</style>
<body id='body'>


<div class="warp">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
<li>12</li>
<li>13</li>
<li>14</li>
<li>15</li>
<li>16</li>
<li>17</li>
<li>18</li>
<li>19</li>
<li>20</li>
<li>21</li>
<li>22</li>
<li id="abc">abc</li>
<li>23</li>
<li>24</li>
<li>25</li>
<li>26</li>
<li>27</li>
<li>28</li>
<li>29</li>
<li>30</li>
</ul>
</div>

<script>
window.onload=function(){
var myScroll = new IScroll('.warp', {
mouseWheel: true,
scrollbars: true,
fadeScrollbars:true,
interactiveScrollbars:true, //滚动条可拖动
probeType: 2, //监听位置必须要搭配这个
// snap:true //开启分页,自动计算内容占滚动容器的多少页
snap:'li', //以每个li作为分页,不会停留在li的一半的位置
// scrollX: true,scrollY: false, //水平滚动
// eventPassthrough:true,
// freeScroll:true
});

//scrollStart scrollEnd...
myScroll.on('scroll', function () {
console.log(this.y, this.maxScrollY)
});
// myScroll.scrollTo(0, -74, 1000); //滚动到位置 三参是时间
// myScroll.goToPage(0, 1, 1000); // 滚动到水平第0页 竖直的第1页 配合snap使用

myScroll.scrollToElement('#abc',500,0,-74) //滚动到元素,时间,水平偏移,竖直偏移量
document.addEventListener('touchmove',function(e){
e.preventDefault()
},false)
}
</script>
</body>
</html>

注意事项

  • 1,页面在移动端滚动条移动变得非常卡,最后加上了document.addEventListener(‘touchmove’, function (e) { e.preventDefault(); }, false);滚动条变得很流畅,并且阻止body滚动

  • 2,关于页面在不刷新的情况下如果添加了新的模块或者高度发生了变化会导致无法下拉的情况,使用iscroll5可以解决这个问题,就是在你触发了变化的事件之后,加上myscroll.refresh(); 去调用它自带的刷新方法。

  • 3,用了Iscroll会阻止默认行为,那么点击事件就不能使用,需要配置中加上 click:true(移动端可以借助faskClick,还解决了300ms延迟问题)加了 click:true的话 注意 表单将无效

  • 4,a标签等不能跳转的问题,可以修改源码

    • preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|A)$/ }
  • 3,indicators是个好东西,参考官网demo:minimap 和parallax

    minimap

    parallax

canvas水波纹

轻戳预览

现在的水波纹效果,很多场景还是可以看到,这里用canvas实现一个类似的效果
思路:其实就是2个贝塞尔曲线填充2种不同颜色,然后动态改变贝塞尔的2个控制点,制造一个波纹的效果

代码比较简单,废话不多少,直接贴Demo吧
HTML:

<style>
    html,body{
        height: 100%;
        margin:0;
        background: grey;
    }
    canvas{
        width: 100%;
        height: 100%;
    }
</style>
<body>
    <canvas id="canvas"></canvas>
</body>

JS:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var _width = canvas.width = canvas.offsetWidth;
var _height = canvas.height = canvas.offsetHeight;
var leftDot = rightDot = _height * 0.7  //起始点和终点高度
var leftDotSpeed = 0.2;
var rightDotSpeed = 0.16;
var n = 0;
window.requestAnimationFrame =  window.requestAnimationFrame ||
                                window.mozRequestAnimationFrame ||
                                window.webkitRequestAnimationFrame ||
                                window.msRequestAnimationFrame;

run();
function drawSun(){
    var color = ctx.createRadialGradient(_width/2,_height*0.6,1,_width/2,_height*0.8,600);
    color.addColorStop(0,'red');
    color.addColorStop(.2,'#fff');
    color.addColorStop(0.5,'lightgrey');
    color.addColorStop(1,'grey');
    ctx.beginPath();
    ctx.arc(_width/2, _height*0.8, 600, 0, 2*Math.PI);
    ctx.fillStyle = color;
    ctx.fill();
}
function drawWater(n,color, leftDot, rightDot){
    var angle = n * Math.PI / 180;
    var deltaHeight = Math.sin(angle) * _height / 8;
    var deltaHeightRight = Math.cos(angle) * _height / 7;
    ctx.beginPath();
    ctx.moveTo(0, leftDot);
    ctx.bezierCurveTo(_width / 4, leftDot + deltaHeight, _width * 3 / 4, rightDot + deltaHeightRight, _width, rightDot);
    ctx.lineTo(_width, _height);
    ctx.lineTo(0, _height); //使得下半个矩形闭合
    ctx.fillStyle = color;
    ctx.fill();
}
function run() {
    ctx.clearRect(0, 0, _width, _height);
    n+=1;
    leftDot += leftDotSpeed
    rightDot += rightDotSpeed
    if(leftDot>_height*0.8 || leftDot<_height*0.6){
        leftDotSpeed *= -1
    }
    if(rightDot>_height*0.8 || rightDot<_height*0.6){
        rightDotSpeed *= -1
    }
    drawSun()
    drawWater(n+50,'lightcyan', leftDot, rightDot)
    drawWater(n,'cyan', leftDot, rightDot)
    requestAnimationFrame(run);
}

webpack之hash、chunkhash和contenthash

文件的hash值通常作为前端静态资源实现增量更新的方案之一,因为通过网络获取资源可能会很慢。 这也就是为什么浏览器需要缓存资源的原因。

如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。告诉浏览器下载较新版本的一种简单方法就是更改资源的文件名。

1.hash与chunkhash

首先我们先看一下官方文档对于两者的定义:

[hash] is replaced by the hash of the compilation.(hash代表的是compilation的hash值。)

[chunkhash] is replaced by the hash of the chunk.(chunkhash代表的是chunk(模块)的hash值。)

什么是 compilation?compilation对象代表某个版本的资源对应的编译进程。compilation在项目中任何一个文件改动后就会被重新创建,然后webpack计算新的compilation的hash值,这个hash值便是hashhash是compilation对象compiler①计算所得,而不是具体的项目文件计算所得。所以所有的文件名都会使用相同的hash指纹。

①compiler对象代表的是配置完备的Webpack环境。 compiler对象只在Webpack启动时构建一次,由Webpack组合所有的配置项构建生成。

例如使用hash配置产生了2个相同hash文件名的js文件:

1
2
3
4
5
6
output: {
filename: '[name].[hash:8].js',
path: __dirname + '/built'
}
//a.778asdef.js
//b.778asdef.js

这样带来的问题是,这2个js文件任何一个改动都会影响另外两个文件的最终文件名。上线后,另外两个文件的浏览器缓存也全部失效。这肯定不是我们想要的结果。

这时候就用到了chunkhash,chunkhash是根据具体模块文件的内容计算所得的hash值,所以某个文件的改动只会影响它本身的hash指纹,不会影响其他文件。配置webpack的output如下:

1
2
3
4
5
6
output: {
filename: '[name].[chunkhash:8].js',
path: __dirname + '/built'
}
//a.778asdfg.js
//b.778afrds.js

这样每个文件的hash都不相同,上线后无改动的文件不会失去缓存。

2. js与css共用相同chunkhash的解决方案

webpack的理念是把所有类型的文件都以js为汇聚点,不支持js文件以外的文件为编译入口。所以如果我们要编译style文件,唯一的办法是在js文件中引入style文件。如下:

1
import 'style/style.scss';

webpack默认将js/style文件统统编译到一个js文件中,可以借助extract-text-webpack-plugin将style文件单独编译输出。但是在计算chunkhash时,会把所有的js代码和style代码混合在一起计算。比如main.js引用了main.scss:

这时候,如果我们只是修改了css,那么不但单独编译出来的css文件hash值有改变,对应的JS文件hash也会改变。

这时候就用到了contenthash

extract-text-webpack-plugin提供了另外一种hash值:contenthash。顾名思义,contenthash代表的是文本文件内容的hash值,也就是只有style文件的hash值。这个hash值就是解决上述问题的银弹。修改配置如下:

1
new ExtractTextPlugin('[name].[contenthash].css');

这样编译输出的js和css文件将会有其独立的hash值。

你需知道的Vue全家桶

vuex详细教程请转Vuex官网

一、知识点小结:

1.复合辅助函数
1
2
3
4
5
6
7
8
9
10
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// 映射 this.count 为 store.state.count
count: state => state.count, ① == ②
//计算属性的名称与 state 的子节点名称相同时
'count' ② == ①
})
}
2.Getters 接受 state 作为其第一个参数,也可以接受其他 getters 作为第二个参数
1
2
3
4
5
6
getters= {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
3.更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,mutation 都是同步事务
1
2
3
4
5
6
7
8
9
// ...
mutations= {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
4.Actions相当于异步mutation,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象作为参数
1
2
3
4
5
6
7
8
9
10
11
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions= {
async actionA ({ commit }) {
//触发mutation
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
5.Store可以复合Modules将 store 分割成模块,每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
1
2
3
4
5
6
7
8
export default new Vuex.Store({
actions, //公用的actions
getters, //公用的getters
modules: {
cart, //拥有自己的 state、mutation、action、getter
products //拥有自己的 state、mutation、action、getter
},
})
  • 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
  • 对于模块内部的 action,局部状态通过 context.state 暴露出来, 根节点状态则为 context.rootState
1
2
3
4
5
6
7
8
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
//...
}
}
}
  • 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来
1
2
3
4
5
6
7
8
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
添加插件,例如同步 websocket 数据源到 store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default function createWebSocketPlugin (socket) {
return store => {
socket监听到后台触发的data改变时触发receiveData的mutation ①·
socket.on('data', data => {
store.commit('receiveData', data)
})
//store监听到mutation触发后发送socket到后台 ②
store.subscribe(mutation => {
if (mutation.type === 'UPDATE_DATA') {
socket.emit('update', mutation.payload)
}
})
}
}
const plugin = createWebSocketPlugin(socket)
const store = new Vuex.Store({
state,
mutations,
plugins: [plugin]
})
内置 Logger 插件

如果正在使用 vue-devtools,你可能不需要此插件

1
2
3
4
import createLogger from 'vuex/dist/logger'
const store = new Vuex.Store({
plugins: [createLogger()]
})
组件template中可以直接使用跟属性例如:$stroe
1
2
3
4
5
<template>
<div id="app">
Clicked: {{ $store.state.count }} times, count is {{ evenOrOdd }}.
</div>
</template>

二、项目实战

源码在github:vue-example,基于vue2+vue-router2+vuex2,不是我吹牛,vue看完我这个例子就够了,比官网的好懂

4个必须知道的数组方法

1.过滤器filter

filter()方法创建一个新数组,返回所有通过所提供函数实现的测试的元素集合。

1
2
3
4
5
6
7
8
9
10
var arr = [
{"name":"apple", "count": 2},
{"name":"orange", "count": 5},
{"name":"pear", "count": 3},
{"name":"orange", "count": 16},
];
var newArr = arr.filter(function(item){
return item.name === "orange";
});
console.log(newArr)//[{"name":"orange", "count": 5},{"name":"orange", "count": 16}]

2.forEach / map

相同点:都是遍历数组对数组的每一项进行操作,不改变原数组

不同点:forEach无返回值,map返回值是一个数组,数组的每一项是遍历时函数的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = [1,2,3,4,5,6,7,8];
//******forEach
var B = arr.forEach(function(item,index){
item+=1
return item
});
console.log(arr) //[1,2,3,4,5,6,7,8]
console.log(B) //undefined
//******Map
var B = arr.forEach(function(item,index){
item+=1
return item
});
console.log(arr) //[1,2,3,4,5,6,7,8]
console.log(B) //[2,3,4,5,6,7,8,9]

3.reduce

reduce(callback,initialValue)拥有两个参数,回调函数initialValue。回调函数本身共有4个参数,prevnextindexarray。基本上常用的就是pre和next。

  • prev:如果没有传初始值,prev的初始值是数组的第一项,next的初始值是数组的第二项,如果传了初始值initialValue,那么prev的初始值就是initialValue,next的初始值是数组的第一项
  • 每次迭代都应该返回一个值,作为下次迭代的prev,next则顺延到数组的下一个
1
2
3
4
5
6
7
8
9
var arr = ["apple","orange","apple","orange","pear","orange"];

function getCount(){
return arr.reduce(function(prev,next){
prev[next] = (prev[next] + 1) || 1;
return prev;
},{});
}
console.log(getCount()); // {apple: 2, orange: 3, pear: 1}

4.find

find方法的callback为数组的每个索引执行一次功能,直到找到一个callback返回真值的值。如果找到这样的元素,find立即返回该元素的值。否则find返回undefined。通常我们只会用到callback的2个参数,(item,index)

1
2
3
4
5
6
7
8
9
var inventory = [
{name: 'cherries', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5},
];
var res = inventory.find(function(item,index){
return item.name === 'cherries';
})
console.log(res) //{name: "cherries", quantity: 2} 和filter类似,但是只找到符合条件的第一个

说走就走的正则表达式

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、replace、search 和 split 方法。

正则表达式中的特殊字符

元字符 描述
\ 将下一个字符标记符、或一个向后引用、或一个八进制转义符。即相当于多种编程语言中都有的“转义字符”的概念。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
* 匹配前面的子表达式任意次。例如,zo能匹配“z”,也能匹配“zo”以及“zoo”。等价于o{0,}
+ 匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多的匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少的匹配“o”,得到结果 ['o', 'o', 'o', 'o']
.点 匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“(”或“)”。
(?:pattern) 非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(
(?=pattern) 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95
(?!pattern) 非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95
(?<=pattern) 非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95
(?<!pattern) 非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95
x y
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身.
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何可见字符。等价于[^ \f\n\r\t\v]。
\w 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的”单词”字符使用Unicode字符集。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符
( ) 将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到\9 的符号来引用。注意 \1、\2、\n 是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像 $1、$2、$n 这样的语法,例如,’bar foo’.replace( /(…) (…)/, ‘$2 $1’ )。

使用正则表达式的方法

正则表达式可以被用于RegExp的exec和test方法以及 String的match、replace、search和split方法。这些方法在JavaScript 手册中有详细的解释。

方法 描述
exec 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)。
test 一个在字符串中测试是否匹配的RegExp方法,它返回true或false。
match 一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。
search 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
split 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。

正则表达式标志

标志 描述
g 全局搜索。
i 不区分大小写搜索。
m 多行搜索。
y 执行“粘性”搜索,匹配从目标字符串的当前位置开始,可以使用y标志。

exec和match异同点:

  • 相同点
    • 两个方法都是查找符合条件的匹配项,并以数组形式返回。
      当没有找到匹配项时,都返回null。 当正则表达式不包含子表达式且不包含全局标志g时,二者返回相同的数组。
  • 不同点
    • exec会匹配子表达式/d(b+)d/中(b+)的结果作为数组的第二项的值
    • match会返回所有符合条件的匹配项,并以数组形式返回。数组第一项存放第一个匹配项,数组第二项存放第二个匹配项…依次类推。
    • exec则永远返回第一个匹配项。但是当连续调用exec时,则每次的返回值都是下一个匹配项

将用户输入转义为正则表达式中的一个字面字符串, 可以通过简单的替换来实现:

1
2
3
4
function escapeRegExp(string){
return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$&");
//$&表示整个被匹配的字符串
}

原生JS封装自己的Picker

移动端选择器随处可见,现在自己封装了一个ZnPicker,基于原生,体积小(压缩后仅4K),易调用

效果预览:轻戳

使用说明

<body>
  <div id="ZnPicker"></div>
  <script src='ZnPicker.js'></script>
  <script>
    var picker=new ZnPicker({
      dataArea:{min:1,max:199,unit:'岁'},
      // dataSource:[{name:'张三',id:1},{name:'李四',id:2},{name:'王五',id:3}],
      title:'请选择年龄',
      defaultSite: 122,
      lineHeight:36
    })
    picker.show()
    picker.onChange=function(data){
      alert(data)
    }
  </script>
</body>

参数说明

Options/callBack Description Required
title Picker标题 false
defaultSite 默认起始位置 false
lineHeight 单个行高 false
dataArea 区间数据源,和dataSource有且只有一个/callback返回区间数 true
dataSource 列表数据源,和dataArea有且只有一个/callback返回数据id true
show Picker显示 /
onChange 选中后的回调函数 /

请允许我记录一点小东西

  • onmouseenter 与onmouseover的区别是 onmouseenter不支持冒泡事件

  • Chrome模拟器不支持 on事件如ontouchstart 需要用addEventListener 添加

  • css3 transition对应有一个js结束事件 transitionend,只支持addEventListener 添加

    • // Safari 3.1 到 6.0 代码

      document.getElementById(“myDIV”).addEventListener(“webkitTransitionEnd”, myFunction);

    • // 标准语法

      document.getElementById(“myDIV”).addEventListener(“transitionend”, myFunction);

  • 移动端页面event.preventDefault()需要加一个判断,开始向下move 和 底部向上滑动则阻止默认行为,否则页面不能滚动

基于nodejs和socket.io的聊天室

socket.io详细介绍可以查看官网

这里说一说我自己搭建的聊天室哦,源码在github:Jasonellen
首先我们来安装一下2个依赖expresssocket.io
npm i express socket.io -S
让我们来开启一个服务器
//开启一个服务
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
server.listen(3000, function () {
  console.log('Server listening at http://localhost:3000');
});

// Routing
app.use(express.static(__dirname + '/src'));

//设置房间用户数量members
var members = 0;
前端HTML
<body>
  <!-- 首页供用户首次进入输入昵称 -->
  <div class="mymodal">
    <div class="content">
      <h1>请输入你的昵称</h3>
      <input type="text" maxlength="14" class='name'>
    </div>
  </div>
  <!-- 主页 聊天使用 -->
  <h2 class='text-center'>欢迎来到南南ChatRoom<br><small>xxx 正在输入...</small></h2>
  <ul></ul>
  <div class="input-group ipt">
    <input type="text" class="form-control" placeholder="请输入聊天内容" aria-describedby="send">
    <span class="input-group-addon" id="send">发送</span>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src='/lib/jquery.min.js'></script>
  <script src='./index.js'></script>
</body>
JS部分
  • 先定义一些变量

    var login = false; //还未登陆
    var nameIpt = $('.content input') //输入用户名
    var mesIpt = $('.ipt input') //消息输入框
    var myModal = $('.mymodal'); //首次进入的modal框
    var ul = $('ul') //详细列表box
    var typing = $('small'); //正在输入
    var username = ''; //用户名
    var sendBtn = $('#send') //发送按钮
    //定义socket
    var socket = io();
    
  • 定义几个常用的函数

    //输入框获得焦点
    function _focus(){
      //获取焦点
      if(!login && !(event.ctrlKey || event.metaKey || event.altKey)){
        //如果没有登录
        nameIpt.focus()
      }else if(!(event.ctrlKey || event.metaKey || event.altKey)){
        //登录成功
        mesIpt.focus()
      }
    }
    
    //添加一条聊天消息
    function addMessage(data){
      var li = $('<li>').html(`<strong style='color:${data.color}'>${data.username}:</strong><span>${data.message}</span>`)
      li.fadeIn(3000)
      ul.append(li)
      ul.scrollTop(ul[0].scrollHeight)
      mesIpt.val('')
    }
    
    //添加欢迎消息
    function welcome(data){
      var li = $('<li>').html(`<li><p>欢迎 ${data.username} 加入了聊天室 <br><span>目前聊天室共${data.members}人</span></p></li>`)
      ul.append(li)
      ul.scrollTop(ul[0].scrollHeight)
    }
    //输出日志
    function log(data){
      var li = $('<li>').html(`<li><p class='lost'>${data}</p></li>`)
      ul.append(li)
      ul.scrollTop(ul[0].scrollHeight)
    }
    
  • socket使用,监听/触发方法

    //登录成功
    socket.on('loginSuccess',function(data){
      login = true;
      username = data.username
      myModal.fadeOut()
      welcome(data)
    })
    //欢迎加入房间
    socket.on('welcome',function(data){
        welcome(data)
    })
    //xxx离开房间
    socket.on('leave',function(data){
      var li = $('<li>').html(`<li><p class='lost'>额.. ${data.username} 离开了聊天室 <br><span>目前聊天室共${data.members}人</span></p></li>`)
      ul.append(li)
      ul.scrollTop(ul[0].scrollHeight)
    })
    //消息发送成功
    socket.on('sendMessageSuccess',function(data){
        addMessage(data)
    })
    //显示正在输入
    socket.on('typingSuccess', function (username) {
        typing.text(`${username}正在输入...`).css('display','block').delay(2000).fadeOut();;
    });
    //本人失去连接
    socket.on('disconnect', function () {
        log('您已失去连接正在尝试重连...')
    });
    //本人重连失败
    socket.on('reconnect_error', function () {
        log('重连失败,再次尝试...')
    });
    //重连成功
    socket.on('reconnect', function () {
      log('连接成功!');
      if (username) {
        //如果还在房间里,重新加入房间
        socket.emit('addUserRequest', username);
      }
    });
    
  • 主入口用户触发函数

    //设置屏幕点击和按键按下 输入框自动获得焦点
    $(window).on('click',function(e){    
        _focus();
    })
    $(window).on('keydown',function(e){
     _focus();
     //输入名字后按回车进入聊天室
     if(e.which === 13){
       if(!login){
         //如果没有登录 发送登录请求
         var _name = nameIpt.val().trim()
         if(_name){
           socket.emit('addUserRequest', _name);
         }
       }else{
         //已经登录发送消息
         var message = mesIpt.val().trim()
         if(message){
           socket.emit('sendMessageRequest', message);
         }
       }
     }
    //按钮发送
    sendBtn.click(function(){
      var message = mesIpt.val().trim()
       if(message){
         addMessage({username,message})
         socket.emit('sendMessageRequest', message);
       }
    })
    //用户输入消息是触发typing
    mesIpt.on('input', function() {
        socket.emit('typingRequest', username);
    });  
    
本人还是喜欢贴出完整代码+简短叙事,逻辑看懂了什么都懂了..还有什么疑问的话可以上我的github给我留言o

OVER

canvas粒子运动

今天说一说Canvas的粒子运动:效果图戳这里

首先我们需要创建一个HTML

<style>
  body{
    margin: 0;
    height:1500px;
    background: #000;
  }
  canvas{
    width: 100%;
    height: 100%;
  }
</style>
<body>
  <canvas id='canvas'></canvas>
  <script src='index.js'></script>
</body>

最主要的就是JS啦☺

获取canvsa 设置画布和宽高

1
2
3
4
5
var canvas = document.getElementById('canvas')
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var _width = canvas.width = canvas.offsetWidth;
var _height = canvas.height = canvas.offsetHeight;

更新页面用requestAnimationFrame替代setTimeout

window.requestAnimationFrame =  window.requestAnimationFrame ||
                                window.mozRequestAnimationFrame ||
                                window.webkitRequestAnimationFrame ||
                                window.msRequestAnimationFrame;

创建粒子对象(具有位置、半径、颜色和速度属性,画圆、运动 连线 和 远离的方法)

function Particle(x, y, r, color){
  this.x = x;
  this.y = y;
  this.r = r;
  this.color = color;
  this.movex = Math.random()/3;
  this.movey = Math.random()/3;
}

//画圆
Particle.prototype.draw = function () {
    ctx.save();
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, 0 ,360);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.restore();
};

// 连线
Particle.prototype.line = function (other) {
  var dx = this.x - other.x;
  var dy = this.y - other.y;
  let gap = Math.sqrt(dxdx + dydy)
  if(gap < 80){
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(this.x, this.y);
    ctx.lineTo(other.x, other.y);
    ctx.strokeStyle = "rgba(255,255,255,.3)";
    ctx.lineWidth = .7;
    ctx.stroke();
    ctx.restore();
  }
};

//运动 (改变x,y的坐标)
Particle.prototype.move = function () {
  this.movex = (this.x <_width && this.x>0)?this.movex:-this.movex;
  this.movey = (this.x <_height && this.x>0)?this.movex:-this.movey;
  this.x += this.movex;
  this.y += this.movey;
};

//远离鼠标
Particle.prototype.faraway = function (other) {
  var fx = this.x - other.x;
  var fy = this.y - other.y;
  var fd = Math.sqrt(fxfx + fyfy);
  if(fd < 50){
    if(fx<0 && fy<0){
      this.x -= 50;
      this.y -= 50;
    }else if(fx<0 && fy>0){
      this.x -= 50;
      this.y += 50;
    }else if(fx>0 && fy<0){
      this.x += 50;
      this.y -= 50;
    }else{
      this.x += 50;
      this.y += 50;
    }
  }
};

创建远离点

1
2
3
4
5
6
7
8
9
10
11
12
13
function Away(x, y){
this.x = x;
this.y = y;
this.color = 'red';
}
Away.prototype.awaydraw = function () {
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, Math.random()*20, 0 , 360);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
};

远离点跟随鼠标移动

1
2
3
4
5
6
7
8
9
10
11
var _away = new Away(0, 0)
window.onmousemove = function(e){
var _scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var e = e || window.event;
_away.x = e.clientX;
_away.y = e.clientY + _scrollTop;
}
window.onmouseout = function(){
_away.x = null;
_away.y = null;
}

运动 起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function run(){
ctx.clearRect(0, 0, _width, _height)
//如果鼠标在窗口内,就画远离点
if(_away.x){
_away.awaydraw();
}
for(var i=0; i<length; i++){
P_array[i].move();
P_array[i].draw();
P_array[i].faraway(_away)
//逐个判断连线
for(var j=0; j<length; j++){
P_array[i].line(P_array[j])
}
}
//循环执行
requestAnimationFrame(run);
}

初始化页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var P_array =[];
var P_num = 360;
var length = 0;
//初始化页面
function init(){
//创建很多个 粒子
for(var i=0; i< P_num; i++){
var _color = `rgb(${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)})`;
console.log(_color)
P_array.push(new Particle(Math.random()*_width, Math.random()*_height, Math.random()*5, _color))
}
length = P_array.length;
//页面 动 起来
run();
}

总结一下

其实这个效果有个思路的话没那么难的,运动面向对象的思想一步一步很容易就可以做出来的,我脑子笨做了几个小时😢