190504-182600

js基础语法

变量提升( hoisting

使用 var 关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)

注释

1
2
3
4
5
HTML的注释标签: <!--  -->

CSS的注释标签: /* */

JS的注释标签: /* */(注释代码块)、//(注释单行)

DOM操作

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
//获取输入框的内容
theText = document.getElementById('mytext').value
//设置输入框的内容为aaaa
document.getElementById('mytext').value="aaaa"
//判断元素中是否包含class
document.querySelector("#hdtb-tls").classList.contains("hdtb-tl-sel") // true

//input赋值
$("#search").attr("value","值");
document.getElementById("search").value="值";

//获取元素中的文本
document.getElementsByClassName('ng-binding')[0].firstChild.nodeValue
document.querySelectorAll('.form-control')[7].firstChild.nodeValue
document.getElementsByClassName('ng-binding')[0].innerText //可能包含标签
Array.apply(null, document.querySelectorAll('.ctx_detail')).pop().textContent //获取将节点下所有文本的合并

//class编辑
document.querySelector('.el-menu-item').classList.remove('is-active')
document.querySelector('.el-menu-item').classList.add('is-active

//得到节点list中最后一项
Array.apply(null, document.querySelectorAll('.ctx_detail')).pop() //nodelist为伪数组,没有pop()方法,需要先转成数组;pop()方法会删除数组的最后一项并返回该项。

//插入元素
element.insertAdjacentHTML('afterend', '<div id="two">two</div>')
//'beforebegin':元素自身的前面。
//'afterbegin':插入元素内部的第一个子节点之前。
//'beforeend':插入元素内部的最后一个子节点之后。
//'afterend':元素自身的后面。

//替换节点内部dom
element.innerHTML = '<a href="" target="_blank" ></a>'
//替换节点本身
element.replaceWith(node)
//祖级替换为子级
node.parentNode.parentNode.replaceWith(node)

//添加属性
video.setAttribute('controls','') //必须含两个参数,表示变量和值,没有值的用''

console.log()

console.log打印出来的内容并不一定可信。一般对于基本类型number、string、boolean、null、undefined的输出是可信的。但对于Object等引用类型来说,可能会异步执行,导致结果异常,这是由平台控制台I/O 延迟导致的,根据平台不同,可能同步也可能异步执行,是无法确定的。

所以对于一般基本类型的调试,调试时使用console.log来输出内容时,不会存在坑。但调试对象时,最好还是使用打断点(debugger)这样的方式来调试更好。

次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过console.log(JSON.stringify(a)),因为JS中对象是引用类型,每次使用对象时,都只是使用了对象在内存的堆中的引用。

字符串处理

截取片段

1
'abcdef'.slice(0, 4) // 得到前4个字符

替换字符

1
2
3
4
5
6
7
8
9
10
11
12
13
mdText = mdText.replace(new RegExp(".jpg",'ig'),".jpg-A")
mdText = mdText.replace(/.jpg/ig,jpg-A")
//替换所有, 存在问题:有特殊符号需要转义,如'(',需写成'\(' ;'ig'中i表示不分大小写,g是全部替换;

mdText = mdText.split(".jpg").join(".jpg-A") //替换所有 缺点:无法不分大小写
mdText = mdText.replace(".jpg",".jpg-A") //只能替换第一个

//定义函数替换所有
String.prototype.replaceAll = function (FindText, RepText) {
let regExp = new RegExp(FindText,'g'); //创建正则RegExp对象,g标识全文匹配
return this.replace(regExp, RepText);
};
mdText = mdText.replaceAll(".jpg",".jpg-A")

数字和字符串转换

数字转字符串

“” + value ,加法运算符配合一个空字符串可以把任意值转换为字符串

1
2
3
4
5
6
7
8
9
let a = '' + 23  // ’23‘

// 用于数字补0
let countStr = ('00000' + count).slice(-6) // 取字符串的倒数第6位到最后一位

//等同于:
function PrefixInteger(num, length) {
return (Array(length).join('0') + num).slice(-length);
}

value.toString()

1
2
n = 100
x = n.toString() // 不能把null和undefined转换为字符串

String(value)

要让数字更加显式地转换为字符串,可以使用String()函数

字符串转数字
1
2
3
4
5
6
7
8
9
10
11
12
parseInt() 函数从string的开始解析,返回一个整数
parseInt('123') : 返回 123;
parseInt('1234xxx') : 返回 1234;
parseInt('123.456') : 返回 123;
parseInt('1 2 3') : 返回 1;字符串的情况,自会返回第一个数
parseInt('bb cc 12') : 返回 NaN;字符串第一个不是数,返回nan
parseInt('123' 321) : 返回 321;
parseInt("AF", 16); 返回 175;会自动把二进制十六进制八进制的转化成数字

如果解析不到数字,则将返回一个NaN的值,可以用isNaN()函数来检测;

parseFloat()只会返回小数

正则表达式

single char quantifiers(数量) position(位置)
\d 匹配数字 * 0个或者更多 ^一行的开头
\w 匹配word(数字、字母) + 1个或更多,至少1个 $一行的结尾
\W 匹配word(数字、字母) ? 0个或1个,一个Optional \b 单词”结界”(word bounds)
\s 匹配white space(包括空格、tab等) {min,max}出现次数在一个范围内
\S 匹配white space(包括空格、tab等) {n}匹配出现n次的
. 匹配任何,任何的字符

[] 表示或的逻辑;

/[-.(]/ -放在第一位表示连字符本身,放在中间,表示”从..到..”,比如[a-z]表示a-z

[.)] 括号中的特殊符号不需要转义,就表示其本身

1
[^ab]` 括号中的`^`表示非,anythings except `a` and `b

(a|b)也可表示选择

例子
1
2
3
4
5
let time = '2018-2-8 21:1:32' 
let a = time.split(/[-:]/) //匹配'-'或':'

replace(/(^\d{2}|\d{3}$)/, '') //替换开头的2位数字
replace(/:\d{1,2}$/, '') //替换从结尾开始,:后面的1到2位数字

数组

删除数组中的指定值的元素

1
2
3
4
5
6
7
8
9
10
let arr = ['ab', 'cd', 'ef']
// splice方法 改变原始数组
arr.splice(arr.findIndex(item => item === 'cd'), 1)

// lodash方法
import _ from 'lodash'
_.pull(arr, 'cd')

//delete方法, 不会改变数组长度,只是把值变为undefined
delete arr[arr.findIndex(item => item === 'cd')]

数组合并和去重

1
2
3
let arr1 = [1,2,3], arr2 = [3,5,6]
let arr = Array.from(new Set([...arr1, ...arr2])) //结果为[1,2,5,6]
//Set是ES6新增的数据结构,具有数据不重复的特点,因此将给定的数组去重后生成Set集合; ...为ES6新增的扩展运算符,用于取出数组元素

判断数组类型

1
Array.isArray([]) //true

对象

增加属性

1
obj.name = '一个名称'

删除属性

1
delete obj.name

对象key存在判断

1
obj.hasOwnProperty(key)

获取对象key数量

1
Object.keys(obj).length

日期时间

时间标准

UTC为国际标准时间,是根据原子钟来计算时间,更精确

GMT(格林威治时间)是根据地球的自转和公转来计算时间,自转在缓慢变慢

GMT和UTC的时间可看成一样,虽然有很小的误差;

时间格式

ISO-8601 标准格式:2019-04-20T06:20:54.513Z

GMT格式:Sat Apr 20 2019 14:20:54 GMT+0800 (CST)

UNIX时间戳(POSIX时间):从国际标准时间 1970年1月1日0时0分0秒起至现在的总秒数,如:1555741254000

Date对象以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)

时区

CST:北京时间是东八区,即GMT+8或者UTC+8,北京时间比UTC时间快8个小时。

有格式显示出来的时间,都与本地相关,即与本地系统设置(或系统默认)的时区相关。

设置时间时,没有指定 time zone,js 将会使用浏览器的 time zone

获取时间时,没有指定 time zone,结果将会转换成浏览器的 time zone

时间转换

JavaScript Result
new Date( ) Mon Feb 13 2017 00:00:00 GMT+0800 (CST)
Date.now() 1555741254000 1970-1-1的 0点(UTC)至今的毫秒数,类型为number
Date.parse(time:string) 1555741254000 //unix时间戳
toLocaleDateString( ) 2/13/2017
toLocaleTimeString( ) 12:00:00 AM
toLocaleString( ) 2/13/2017, 12:00:00 AM
toISOString( ) 2017-02-12T16:00:00.000Z //返回UTC 0时区时间,
toJSON( ) 2017-02-12T16:00:00.000Z //同toISOString( )
toDateString( ) Mon Feb 13 2017
toGMTString( ) Sun, 12 Feb 2017 16:00:00 GMT
toTimeString( ) 00:00:00 GMT+0800 (CST)
toUTCString( ) Sun, 12 Feb 2017 16:00:00 GMT

常用方法

字符转unix时间戳
1
2
3
Date.parse('2019/01') //1546272000000
Date.parse('2019/') //1546272000000 //只有年时,需在后面加/,否则会转成本地时区,导致错误,多8小时
+new Date('2019/') //1546272000000
toLocaleString

将时间转换为系统时区,并按指定语言显示时间格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 2019/11/15 14:57:20
new Date().toLocaleString('zh', { hour12: false })

//191115 15:14
new Date()
.toLocaleString('zh', {
year: '2-digit',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
// second: '2-digit',
hour12: false,
})
.split(/[/]/)
.join('')
//增加其他参数时显示:
weekday: 'short', // 191115周五 15:23


//当前时间
new Date().toLocaleTimeString('zh', { hour12: false }) //09:35:12
1
2
3
4
5
6
7
8
9
10
//当前时间转为格式: 20190420 06:20,改方法不能适配系统时区
let startTime = new Date(Date.now() + 28800000)
.toJSON() //转为:2019-04-20T06:20:54.513Z
.replace('T', ' ')
.substring(0, 16)
.split(/[-]/) //时间格式: 20190420 06:20
.join('')

//date: 2019-02-30T12:21:31.584Z,转为格式: 190203 12:21
return date.replace('T', ' ').substring(2, 16).split(/[-]/).join('')
年月加1
1
2
3
let startTime = new Date('2019/12')  //1575129600000
+startTime.setMonth(startTime.getMonth() + 1) //加1月 2020/01/01 结果:1577808000000
+startTime.setFullYear(startTime.getFullYear() + 1) //加1年 2020/12/01 结果:1606752000000

老方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getTime () { // 获取当前日期和时间
let now = new Date() // Sat Apr 13 2019 05:27:09 GMT+0800 (CST)
let y = now.getFullYear()
let m = now.getMonth() + 1
let d = now.getDate()
let hh = now.getHours()
let mm = now.getMinutes()
let ss = now.getSeconds()
if (m < 10) m = '0' + m
if (d < 10) d = '0' + d
if (hh < 10) hh = '0' + hh
if (mm < 10) mm = '0' + mm
if (ss < 10) ss = '0' + ss

return `${y}.${m}.${d} ${hh}:${mm}:${ss}`
}

####

常用函数

中文字数计算

1
2
3
4
5
6
7
8
9
10
11
//获取字符串长度(汉字算两个字符,字母数字算一个)
function getByteLen(str) {
let realLength = 0,
charCode = -1
str.split('').forEach((item, index) => {
charCode = str.charCodeAt(index)
if (charCode >= 0 && charCode <= 128) realLength += 1
else realLength += 2
})
return realLength
}

video视频处理

1
2
3
4
5
6
let video = document.querySelector('.video-wrapper video')
let source = document.querySelector('.video-wrapper video source')
video.play() //播放视频
video.pause() //停止播放
source.src //视频链接
video.setAttribute('autoplay','') //自动播放

画中画

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
video.requestPictureInPicture() //进入画中画
document.exitPictureInPicture() //退出画中画
// 进入画中画模式时候执行
video.addEventListener('enterpictureinpicture', function() {
// 已进入画中画模式
})
// 退出画中画模式时候执行
video.addEventListener('leavepictureinpicture', function() {
// 已退出画中画模式
})
//PictureInPictureWindow窗口对象
{
height: 192,
width: 341
onresize: null, //事件监听小窗口尺寸的改变
}
//resize时获取宽高
video.addEventListener('enterpictureinpicture', function(event) {
var pipWindow = event.pictureInPictureWindow //PictureInPictureWindow窗口对象
// 绑定resize事件
pipWindow.addEventListener('resize', function () {
// pipWindow.width就是小视频窗口的宽度
// pipWindow.height就是小视频窗口的高度
})
})

流程控制

单线程、同步、异步

javascript是单线程,所有任务需要排队,任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务是不进入主线程、而进入”任务队列”(task queue)的任务,只有等主线程任务执行完毕,”任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行。

异步运行机制如下:

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

“任务队列”是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。

所谓”回调函数”(callback),就是那些会被主线程挂起来的代码,异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

——js中的同步和异步的个人理解

异步循环

异步one by one

es5实现(回调嵌套地狱)

1
2
3
4
5
6
7
8
9
10
11
12
13
function clickButton(){
console.log('end',new Date());
}
//每隔1秒执行一次 clickButton()
setTimeout(function(){
clickButton()
setTimeout(function(){
clickButton()
setTimeout(function(){
clickButton()
},1000)
},1000)
},1000)

同步循环中的异步

同步循环执行异步操作,同步的部分会先执行,然后异步的在一起返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let arr = [1, 2, 3]
while (arr.length) {
((arr) => {
setTimeout(() => {
console.log('异步',arr,new Date())
}, 1000);
})(arr.concat() )
console.log('同步' + arr,new Date())
arr.shift()
}

//结果:
同步 1,2,3 2019-05-24T08:25:42.926Z
同步 2,3 2019-05-24T08:25:42.929Z
同步 3 2019-05-24T08:25:42.929Z
异步 [ 1, 2, 3 ] 2019-05-24T08:25:43.927Z
异步 [ 2, 3 ] 2019-05-24T08:25:43.930Z
异步 [ 3 ] 2019-05-24T08:25:43.930Z

条件

判断对象为空

1
2
3
JSON.stringify(obj) === '{}' // 采用
Object.keys(obj).length === 0 // 可用
obj.toString() // 无效,obj为{}时,结果为"[object Object]"

类型判断

1
2
3
4
5
6
7
8
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(''); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(function f(){}); // "[object Function]"
Object.prototype.toString.call(); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"

小数位保留

四舍五入

1
2
var num =2.446242342;
num = num.toFixed(2); // 输出结果为 2.45

不四舍五入

第一种,先把小数变整数:

1
Math.floor(15.7784514000 * 100) / 100   // 输出结果为 15.77

第二种,当作字符串,使用正则匹配:

1
Number(15.7784514000.toString().match(/^\d+(?:\.\d{0,2})?/))   // 输出结果为 15.77,不能用于整数如 10 必须写为10.0000

注意:如果是负数,请先转换为正数再计算,最后转回负数

事件

添加和移出事件listener

1
2
3
4
5
6
7
8
9
10
11
12
13
function moveWidth() {
event.preventDefault() // 移动时禁用默认事件
el.style.width = `${event.clientX - disX}px` //改变对象宽度
}
//添加listener,函数引用时不要带(),否则添加listener时会执行函数,事件触发时就不会执行
document.addEventListener('mousemove', moveWidth,false)

document.onmouseup = function(e) {
//移除listener,需与添加时相同
document.removeEventListener('mousemove', moveWidth,false)
document.onmouseup = null
body.style.cursor = 'unset'
}

事件冒泡与捕获

事件冒泡与捕获是 DOM 中事件传播的两种方式,比如两个一里一外的div注册了相同事件,当点击里层 div 的时候,这两个事件谁先执行。

冒泡事件,由里向外,最里层的元素先执行,然后冒泡到外层。

捕获事件,由外向里,最外层的元素先执行,然后传递到内部。

通过修改 addEventListener的第三个参数,true 为捕获,false 为冒泡(默认为冒泡)

同时出现冒泡和捕获会出现什么结果?

浏览器处理时间分为两个阶段,先执行捕获阶段,然后是冒泡阶段,例如3个方块:【1【2【3】】】,1、3为冒泡,2为捕获事件,点3依次执行2、3、1。

默认事件取消与停止冒泡

有时我们只想执行最内层或最外层的事件,根据内外层关系来把范围更广的事件取消掉(对于新手来说,不取消冒泡,很容易中招的出现 bug)。

取消事件冒泡:event.stopPropagation() (IE 中window.event.cancelBubble = true)

取消浏览器的默认事件: event.preventDefault() (IE 中window.event.returnValue = false)

取消默认事件和停止冒泡的区别:浏览器的默认事件取消掉就不会执行啦;冒泡取消的是由外向里(捕获)、由里向外(冒泡),stop 之后,就不会继续遍历了

event对象

target和currentTarget

  • e.target 指向触发事件监听的对象。
  • e.currentTarget 指向添加监听事件的对象。

clientX、offsetX、screenX、pageX

clientX、clientY:点击位置距离当前body可视区域的x,y坐标

pageX、pageY:对于整个页面来说,包括了被卷去的body部分的长度

screenX、screenY:点击位置距离当前电脑屏幕的x,y坐标

offsetX、offsetY:相对于带有定位的父盒子的x,y坐标

x、y:和screenX、screenY一样

image-20190630205634822

元素变动

MutationObserver

Mutation Observer API 用来监视 DOM 变动,比如节点的增减、属性的变动、文本内容的变动。有以下特点:

  • 异步方式执行,等待所有脚本任务完成后,才会运行
  • 把 DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动
  • 既可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动
1
2
3
4
5
6
7
8
9
10
11
12
13
// 监听标题文本变动,清除标题通知
let title = document.querySelector('title')
let config = { characterData: true, childList: true }
let clearTitle = function(mutationsList) {
let titleText = title.text
console.log(titleText)
if(titleText.startsWith('(')) title.text = titleText.substring(titleText.indexOf(') ')+2)
}
let observer = new MutationObserver(clearTitle) //异步回调函数
observer.observe(title, config)

//关闭监听
observer.disconnect()
1
2
3
4
5
6
7
8
//监听后代节点属性变化,通过过滤指定属性,有效控制范围
let listenNode = document.querySelector('#top')
let config = { attributes: true, subtree: true, attributeFilter:['crossorigin'] }
let callback = function(mutationsList) {
console.log(mutationsList) //回调返回监听到的节点数组
}
let observer = new MutationObserver(callback)
observer.observe(listenNode, config)
config中option的选项

当调用observe() 时,至少有一个 childListattributes和/或characterData 必须是true,否则将引发异常。

  • attributeFilter 可选的

    要监视的特定属性名称的数组。如果不包括此属性,则所有属性的更改都将导致突变通知。无默认值。

  • attributeOldValue 可选的

    设置为true,记录一个或多个节点的属性更改时更改的属性的先前值;有关监视属性更改和值记录的详细信息,请参见在MutationObserver中监视属性值。无默认值。

  • attributes 可选的

    设置为true,监视一个或多个节点上的属性值的更改。默认值为false

  • characterData 可选的

    设置为true,监视指定的目标节点或子树,以查看该节点或节点中包含的字符数据的更改。无默认值。

  • characterDataOldValue 可选的

    设置为true,在监视节点上的文本发生更改时记录该节点文本的先前值。有关详细信息和示例,请参见监控文本内容的变化MutationObserver。无默认值。

  • childList 可选的

    设置为true,监视目标节点(如果subtreetrue子节点,则为其子节点),以添加新的子节点或删除现有的子节点。默认值为false

  • subtree 可选的

    设置为true,将监视范围扩展到以当前节点为根的整个子树targetMutationObserverInit然后将所有其他属性扩展到子树中的所有节点,而不是仅应用于该target节点。默认值为false

Mutation events (已废弃)

支持的事件列表如下:

  • DOMAttrModified Chrome/Safari不支持
  • DOMAttributeNameChanged
  • DOMCharacterDataModified
  • DOMElementNameChanged
  • DOMNodeInserted
  • DOMNodeInsertedIntoDocument IE不支持
  • DOMNodeRemoved
  • DOMNodeRemovedFromDocument IE不支持
  • DOMSubtreeModified
事件名称 事件描述
DOMAttrModified DOM属性发生修改
DOMAttributeNameChanged DOM属性名发生变化
DOMCharacterDataModified DOM文本数据发生修改
DOMElementNameChanged DOM元素名发生变化
DOMNodeInserted DOM节点插入
DOMNodeRemoved DOM节点删除
DOMSubtreeModified DOM子元素修改
1
2
3
4
//使用方式
element.addEventListener("DOMNodeInserted", function (event) {
// event.target就是依次插入的DOM节点
}, false);

keycode

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

keycode 8 = BackSpace BackSpace
keycode 9 = Tab Tab
keycode 12 = Clear
keycode 13 = Enter
keycode 16 = Shift_L
keycode 17 = Control_L
keycode 18 = Alt_L
keycode 19 = Pause
keycode 20 = Caps_Lock
keycode 27 = Escape Escape
keycode 32 = space space
keycode 33 = Prior
keycode 34 = Next
keycode 35 = End
keycode 36 = Home
keycode 37 = Left
keycode 38 = Up
keycode 39 = Right
keycode 40 = Down
keycode 41 = Select
keycode 42 = Print
keycode 43 = Execute
keycode 45 = Insert
keycode 46 = Delete
keycode 47 = Help
keycode 48 = 0 equal braceright
keycode 49 = 1 exclam onesuperior
keycode 50 = 2 quotedbl twosuperior
keycode 51 = 3 section threesuperior
keycode 52 = 4 dollar
keycode 53 = 5 percent
keycode 54 = 6 ampersand
keycode 55 = 7 slash braceleft
keycode 56 = 8 parenleft bracketleft
keycode 57 = 9 parenright bracketright
keycode 65 = a A
keycode 66 = b B
keycode 67 = c C
keycode 68 = d D
keycode 69 = e E EuroSign
keycode 70 = f F
keycode 71 = g G
keycode 72 = h H
keycode 73 = i I
keycode 74 = j J
keycode 75 = k K
keycode 76 = l L
keycode 77 = m M mu
keycode 78 = n N
keycode 79 = o O
keycode 80 = p P
keycode 81 = q Q at
keycode 82 = r R
keycode 83 = s S
keycode 84 = t T
keycode 85 = u U
keycode 86 = v V
keycode 87 = w W
keycode 88 = x X
keycode 89 = y Y
keycode 90 = z Z
keycode 96 = KP_0 KP_0
keycode 97 = KP_1 KP_1
keycode 98 = KP_2 KP_2
keycode 99 = KP_3 KP_3
keycode 100 = KP_4 KP_4
keycode 101 = KP_5 KP_5
keycode 102 = KP_6 KP_6
keycode 103 = KP_7 KP_7
keycode 104 = KP_8 KP_8
keycode 105 = KP_9 KP_9
keycode 106 = KP_Multiply KP_Multiply
keycode 107 = KP_Add KP_Add
keycode 108 = KP_Separator KP_Separator
keycode 109 = KP_Subtract KP_Subtract
keycode 110 = KP_Decimal KP_Decimal
keycode 111 = KP_Divide KP_Divide
keycode 112 = F1
keycode 113 = F2
keycode 114 = F3
keycode 115 = F4
keycode 116 = F5
keycode 117 = F6
keycode 118 = F7
keycode 119 = F8
keycode 120 = F9
keycode 121 = F10
keycode 122 = F11
keycode 123 = F12
keycode 124 = F13
keycode 125 = F14
keycode 126 = F15
keycode 127 = F16
keycode 128 = F17
keycode 129 = F18
keycode 130 = F19
keycode 131 = F20
keycode 132 = F21
keycode 133 = F22
keycode 134 = F23
keycode 135 = F24
keycode 136 = Num_Lock
keycode 137 = Scroll_Lock
keycode 187 = acute grave
keycode 188 = comma semicolon
keycode 189 = minus underscore
keycode 190 = period colon
keycode 192 = numbersign apostrophe
keycode 210 = plusminus hyphen macron
keycode 211 =
keycode 212 = copyright registered
keycode 213 = guillemotleft guillemotright
keycode 214 = masculine ordfeminine
keycode 215 = ae AE
keycode 216 = cent yen
keycode 217 = questiondown exclamdown
keycode 218 = onequarter onehalf threequarters
keycode 220 = less greater bar
keycode 221 = plus asterisk asciitilde
keycode 227 = multiply division
keycode 228 = acircumflex Acircumflex
keycode 229 = ecircumflex Ecircumflex
keycode 230 = icircumflex Icircumflex
keycode 231 = ocircumflex Ocircumflex
keycode 232 = ucircumflex Ucircumflex
keycode 233 = ntilde Ntilde
keycode 234 = yacute Yacute
keycode 235 = oslash Ooblique
keycode 236 = aring Aring
keycode 237 = ccedilla Ccedilla
keycode 238 = thorn THORN
keycode 239 = eth ETH
keycode 240 = diaeresis cedilla currency
keycode 241 = agrave Agrave atilde Atilde
keycode 242 = egrave Egrave
keycode 243 = igrave Igrave
keycode 244 = ograve Ograve otilde Otilde
keycode 245 = ugrave Ugrave
keycode 246 = adiaeresis Adiaeresis
keycode 247 = ediaeresis Ediaeresis
keycode 248 = idiaeresis Idiaeresis
keycode 249 = odiaeresis Odiaeresis
keycode 250 = udiaeresis Udiaeresis
keycode 251 = ssharp question backslash
keycode 252 = asciicircum degree
keycode 253 = 3 sterling
keycode 254 = Mode_switch

ES6(ES2015)

扩展运算符(…)

扩展运算符( spread )是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

1
2
3
4
5
6
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5 的写法
function f(x, y, z) {
//
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6 的写法
function f(x, y, z) {
//
}
var args = [0, 1, 2];
f(...args);
1
2
3
4
5
6
7
8
9
10
通过push函数,将一个数组添加到另一个数组的尾部。
// ES5 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

扩展运算符的应用

合并数组

1
2
3
4
5
6
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

结合解构赋值,用于生成数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
下面是另外一些例子。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []:
const [first, ...rest] = ["foo"];
first // "foo"
rest // []

函数返回多个值

1
2
var dateFields = readDateFields(database);
var d = new Date(...dateFields);

将字符串转为真正的数组

1
2
[...'hello']
// [ "h", "e", "l", "l", "o" ]

查看更多

复制对象的部分属性

1
2
3
4
5
6
7
8
9
let obj = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5
}
let {b, c, ...obj2} = obj
console.log(obj2); // {a: 1, d: 4, e: 5}

Promise对象

Promise对象代表一个异步操作,包含三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

Promise对象不受外界影响,一旦状态改变,就不会再变;

Promise一旦创建就会立即执行,无法中途取消(英语意思就是“承诺”);

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态(非必须)的回调函数。

Iterator(遍历器)

遍历器 的作用有三个:

一是为各种数据结构,提供一个统一的、简便的访问接口;

二是使得数据结构的成员能够按某种次序排列;

三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

遍历器为各种不同的数据结构提供统一的访问机制。方便依次处理该数据结构的所有成员。

遍历器对象本质是一个指针对象,每一次调用next方法,都会返回数据结构的下一成员的信息,包含valuedone两个属性的对象,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//模拟next方法返回值
var it = makeIterator(['a', 'b'])
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true}
}
}
}

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,它是默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对象obj具有Symbol.iterator属性,是可遍历的
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

数组的Symbol.iterator属性:

1
2
3
4
5
6
7
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。

async

1
2
3
4
5
6
7
8
9
10
11
const afile = async () => {
const file1 = await fs.readdir('/Users/fwk/Downloads/try/b1')
console.log(file1)
await timeout(10000) //超时完才执行下面语句
const file2 = await fs.readdir('/Users/fwk/Downloads/try')
await console.log(file2)
const fileAll = await [...file1, ...file2]
console.log(fileAll)
return 'cccc'
}
afile().then(console.log) //cccc

async函数对 Generator 函数的改进,体现在以下四点:

  1. 内置执行器,与普通函数使用相同;
  2. asyncawait,比起星号和yield,语义更清楚;
  3. 更广的适用性,await后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,会自动转成立即 resolved 的 Promise 对象)。
  4. async函数的返回值是 Promise 对象,比 Generator 函数的返回值是 Iterator 对象方便多了,可以用then方法指定下一步的操作

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

错误不中断

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

若不想中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//方法一 try...catch
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f().then(v => console.log(v)) // hello world


//方法二:Promise 对象后跟一个catch方法
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f().then(v => console.log(v)) // 出错了 // hello world

继发和顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//继发执行
let foo = await getFoo();
let bar = await getBar();

//for循环为继发执行
for (let doc of docs) {
await db.post(doc);
}


//并发执行
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

//forEach中为并发执行,可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});

callback使用await

需util.promisify将callback封装为promise

1
2
import { promisify } from 'util'
const osascriptAsync = promisify(osascript.execute)

变量名转字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getVarNameStr(v){
return Object.keys(v)[0];
}
let myVar = 100;
console.log(getVarNameStr({myVar}));//输出 'myVar'

// 原理:
var foo = 'bar';
var baz = {foo}; // 相当于{foo: "bar"} ,es6才支持
// 等同于
var baz = {foo: foo};

// 应用
const print = (x = {}) => { // 变量打印
Object.prototype.toString.call(Object.values(x)[0]) !== '[object Object]' //判断是否为对象类型
? console.log(Object.keys(x)[0] + ': ' + Object.values(x)[0])
: console.log(JSON.stringify(x, null, 4))
}
print({ dirPath }) // 打印变量的值 dirPath: /Users/fwk/Downloads/_test

延时

1
2
3
4
5
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}

模块(module)体系

ES6的设计思想是尽量静态化,在编译时就能确定模块的依赖关系和输入输出变量。从而实现静态分析,为引入宏(macro)和类型检验(type system)提供可能。

1
2
3
4
5
// ES6模块
import { stat, exists, readFile } from 'fs'; //只加载这3个方法,其他不加载

// CommonJS模块
let { stat, exists, readFile } = require('fs'); //整体加载fs模块(所有方法),生成一个对象

export命令

  • 用于规定模块的对外接口,接口名需与模块内部变量对应一一对应
  • 输出的接口与值是动态绑定,可以取到模块内部实时的值
  • 可以出现在模块的任何位置,只要处于模块顶层就可以

import命令

  • 输入的是其他模块的接口,因此输入的变量都是只读的,不能改写接口;
  • 若输入的是对象,对象的属性可改写,但这种写法很难查错;
  • import 命令会提升到整个模块的头部,首先执行,在编译阶段就会执行;
  • 由于是静态执行,所以不能使用表达式和变量等只有在运行时才能得到结果的语法结构;
  • 多次重复执行同一 import 语句,只会执行一次;
  • CommonJS 模块的require和 ES6 模块的import,最好不要写在同一模块中,因为import在编译时就执行,可能不符合预期;

ES6 与 CommonJS 模块差异

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

JSON

JSON是一种数据格式,不是编程语言,是JavaScript的严格子集。

只支持简单值(数字、字符串、布尔值、null)、对象、数组。

字符串必须使用双引号;对象中属性名必须加双引号;

对象值不能为undefined,转换为JSON时会被删掉;

方法

对象转文本

JSON.stringify( )将JS对象转为 JSON 文本

  • 非数组对象的顺序是不确定的
  • undefined、任意的函数以及 symbol 值,会被忽略(不在数组中)或转换成null(数组中)
  • 如果对象存在toJSON函数,会对toJSON的返回值进行序列化
  • 如果存在循环引用,会抛出异常
  • 可以传入第二个参数,在属性值被序列化之前改变它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var jsObj={
"name": "Good",
"phone":"1234567",
"age":18
};

jsonText = JSON.stringify(jsObj, ["name", "phone"], 2)
//参数二为过滤数组,只取其中的对应键值,参数三可格式化显示,数字表示空格缩进,'/t'tab缩进;

jsonText = JSON.stringify(jsObj, function(key,value){ //参数二为过滤函数
if(key==="phone")
return "(000)"+value
else if(typeof value === "number") //类型是数值就加1
return value + 1
else return value; //如果你把这个else分句删除,那么结果会是undefined
}, 2)

util.inspect( )

  • 不会忽略函数,显示[Function],也不会忽略undefined和symbol
  • 对于循环引用,会显示[Circular]
  • 可以设置对象深度,超过的不再继续向内显示,而是显示[Object]
  • 如果对象存在inspect函数,则会对其结果进行转换
1
jsonText = util.inspect(jsObj,{depth: 2})

文本转对象

1
var jsObj = eval('('+ jsonObjStr +')')

JSON转为JS对象

JSON.parse( )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var jsObj={
"name": "Good",
"phone":"1234567",
"age":18
"date":new Date(2018, 07, 07)
};

jsonText = JSON.stringify(jsObj) //Date对象变为具体值

jsObCOPY = JSON.parse(jsonText, function(key,value){
if(key==="date")
return new Date(value) //还原回Date对象
else {
return value
})

延时(setTimeout)

延时执行的只有setTimeout中的函数,后面的语句仍会立即执行

1
2
3
setTimeout(() => {
console.log('时间到')
},500) //到时间(毫秒)后执行函数

文件目录操作

检测文件是否存在

1
2
3
4
// 检测文件是否存在,异步
fs.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK, (err) => {
console.log(err ? 'no access!' : 'can read/write');
});

创建目录、写入、读取文件

异步操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 新建目录,0o777为目录读写权限
fs.mkdir('file', 0o777, function(err) {
if (err) throw err
console.log('新建文件夹成功')
})

// 写入文件
fs.writeFile('./file/1.md', '写入的内容', function(err) {
if (err) throw err
console.log('文件写入成功')
})

// 读取文件
fs.readFile('./file/1.md', 'utf8', function(err, data) {
if (err) throw err
console.log(data)
})
同步操作
1
2
3
4
5
6
7
8
9
10
11
// 新建目录
fs.mkdirSync('file', 0o777)
console.log('新建file文件夹成功')

// 写入文件
fs.writeFileSync('file/1.md', '随便写入一句话吧', 'utf8')
console.log('新建文件成功')

// 读取文件
var data = fs.readFileSync('file/1.md', 'utf8')
console.log('读取文件:' + data)

读取目录

1
2
3
4
5
fs.readdir('file', function(err, files) {
if (err) throw err
var length = files.length
console.log('一共有' + length + '个文件')
})

判断是文件还是目录

1
2
3
4
5
6
fs.stat('./fs.js', function(err, stat) {
if (stat.isFile())
console.log('这是个文件')
else if (stat.isDirectory())
console.log('这是个目录')
})

监听文件

1
2
3
4
5
6
7
8
9
10
// 监听文件
fs.watchFile('readme.md', function(curr, prev) {
console.log('the current mtime is: ' + curr.mtime)
console.log('the previous mtime was: ' + prev.mtime)
})

// 解除监听
setTimeout(function() {
fs.unwatchFile('readme.md')
}, 1000)

读写数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 读取数据流
var file = fs.createReadStream('readme.md');
var str = ''
file.on('data', function(data) {
str += data
})
file.on('end', function() {
console.log(str)
})

// 写入数据流
var file = fs.createWriteStream('readme.md', {encoding: 'utf8'})
file.write('写入一句\n')
file.write('再写入一句\n')
file.write('最后再来一句')
file.end()

框架、库

image-20180627223633650

vue 对比 react

vue的学习成本极低,体积更小更快

Vue.js在可读性、可维护性和趣味性之间做到了很好的平衡

感觉 React 从一开始的理念就比V,A更具现代化,也就是函数式编程。

vue的简单导致无法完成一些场景,而且它的路由也是坑比较多,而react其实要灵活的多,而且真的实践了才发现它的思想其实更加明确

vue 需要学模板语言

react需要学 JSX,JSX 代码可读性差

20171113

如果是以下需求选react

1、如果想组件式开发

2、如果想web和wap统一技术栈

3、如果想要代码更可维护和可测试性

4、如果你想要最大的生态系统,想利用丰富的组件库及第三方包

5、如果你想要构建大型应用程序

如果是以下需求选vue

1、如果你喜欢使用模板( 或需要一些其中的选项)构建应用程序

2、如果是在现有代码基础上快速升级

3、如果你喜欢简单的能正常工作的,想更加灵活性

4、如果你希望你的程序更小更快,想要更快的渲染速度和体积

2017-08-07

180628_075050

2016-10-21

vue 2 已发布

  • 如果你喜欢用(或希望能够用)模板搭建应用,请使用Vue
  • 如果你喜欢简单和“能用就行”的东西,请使用Vue
  • 如果你的应用需要尽可能的小和快,请使用Vue
  • 如果你计划构建一个大型应用程序,请使用React
  • 如果你想要一个同时适用于Web端和原生App的框架,请选择React
  • 如果你想要最大的生态圈,请使用React
  • 如果你已经对其中一个用得满意了,就没有必要换了

面向对象

多态

多态是面向对象编程(OOP)的宗旨之一。这是设计对象以共享行为并能够用特定行为覆盖共享行为的实践。多态性利用继承来实现这一点。

在OOP中,所有内容都被视为对象模型。这种抽象可以一直延伸到汽车的螺母和螺栓,也可以简单地扩展到具有年份,品牌和型号的汽车类型。

要具有多态汽车场景,将有基本的汽车类型,然后是子类,这些子类将从汽车继承并在汽车具有的基本行为之上提供自己的行为。例如,一个子类可能是TowTruck,它仍然具有年份和型号,但是可能还具有一些额外的行为和属性,这些行为和属性可能像IsTowing的标志一样基本,甚至与升降机的细节一样复杂。

回到人和雇员的例子,所有雇员都是人,但所有人都不是雇员。也就是说,人将是超阶级,雇员将是子阶级。人们可能有年龄和体重,但没有薪水。员工是人,所以他们天生就有年龄和体重,而且因为他们是员工,所以他们会有薪水。

因此,为了方便起见,我们将首先写出超类(Person)

1
2
3
4
function Person(age,weight){
this.age = age;
this.weight = weight;
}

我们将使Person能够共享他们的信息

1
2
3
4
Person.prototype.getInfo = function(){
return "I am " + this.age + " years old " +
"and weighs " + this.weight +" kilo.";
};

接下来,我们希望有一个Person,Employee的子类

1
2
3
4
5
6
function Employee(age,weight,salary){
this.age = age;
this.weight = weight;
this.salary = salary;
}
Employee.prototype = new Person();

我们将通过定义一个更适合Employee的方法来覆盖getInfo的行为

1
2
3
4
5
Employee.prototype.getInfo = function(){
return "I am " + this.age + " years old " +
"and weighs " + this.weight +" kilo " +
"and earns " + this.salary + " dollar.";
};

这些可以类似于原始代码使用

1
2
3
4
5
var person = new Person(50,90);
var employee = new Employee(43,80,50000);

console.log(person.getInfo());
console.log(employee.getInfo());

但是,在这里使用继承没有太多收获,因为Employee的构造函数与person的构造函数非常相似,并且原型中的唯一功能被覆盖。多态设计的力量在于共享行为。

函数式编程

在 JavaScript 中,函数表达式的值是函数本身:

1
2
const double = x => x * 2
double.toString(); // 'x => x * 2'

要将函数应用于某些参数,则必须使用函数调用来调用它。括号即表示调用,没有它们,函数将不会被调用。

1
double(2); // 4

reduce

在函数式编程中,reduce(也称为:fold,accumulate)允许你在一个序列上迭代,并应用一个函数来处理预先声明的累积值和当前迭代到的元素。当迭代完成时,将返回这个累积值。许多其他有用的功能都可以通过 reduce 实现。多数时候,reduce 可以说是处理集合(collection)最优雅的方式。

1
2
3
4
5
6
7
array.reduce(
reducer: (accumulator: Any, current: Any) => Any, //处理函数,acc默认为初始值,cur依次为数组从左到右各项
initialValue: Any //初始值
) => accumulator: Any //返回累计值

//例如数组各项求和
[2, 4, 6].reduce((acc, n) => acc + n, 0); // 12

柯里化与函数组合

柯里化函数是一种把接受多参数的函数变为接受单一参数的函数,通过使用第一个参数并返回使用余下参数的一系列函数,直到所有的参数都被使用,并且函数应用结束,此时结果就会被返回。

偏函数应用是一种已经应用一些但非全部参数的函数。函数已经应用的参数被称为固定参数(Fixed Parameters)

无点风格是一种不需要引用参数的函数定义风格。一般来说,无点函数通过调用返回函数的函数来创建,例如柯里化函数。

柯里化函数对于函数组合非常有用,因为由于函数组合的需要,你可以把 n 元函数轻松地转换成一元函数形式:管道内的函数必须是单一参数。

数据置后函数对于函数组合来说非常方便,因为它们可以轻松地被用在无点风格中。

函数式编程使用

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
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};

const g = n => n + 1;
const f = n => n * 2;

/*
现在函数应用的顺序是从上到下:
*/
const h = pipe(
g,
trace('after g'),
f,
trace('after f'),
);

h(20);
/*
after g: 21
after f: 42
*/

函数式编程理论

面向对象编程(OOP)通过封装变化使得代码更易理解。 函数式编程(FP)通过最小化变化使得代码更易理解。 – Michacel Feathers(Twitter)

函数式编程的目的是使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用减少对状态的改变。

这些情况常常会产生副作用:

  • 改变一个全局的变量、属性或数据结构
  • 改变一个函数参数的原始值
  • 处理用户输入
  • 抛出一个异常
  • 屏幕打印或记录日志
  • 查询 HTML 文档,浏览器的 Cookie 或访问数据库

对于纯函数有以下性质:

  • 仅取决于提供的输入,而不依赖于任何在函数求值或调用间隔时可能变化的隐藏状态和外部状态。
  • 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数。

有一些副作用是难以避免的,与外部的存储系统或 DOM 交互等,但是我们可以通过将其从主逻辑中分离出来,使他们易于管理。

一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,就是引用透明

测试

console.log()

使用JSON.stringify将json格式的对象或数组转为字符串输出,这样就可以在console.log时显示全部内容了。

1
console.log(JSON.stringify(数组或对象), null, '\t')) // '\t' 换行缩进,美化显示

单元测试

目的是保证一个系统的基本组成单元(类、模块或方法)能正常工作

概念

Mocha: Javascript测试框架

chai:断言库,需配合Mocha使用

describe:”测试组”,也称测试块,表示要进行一系列测试,相当于一个group

it:”测试项”,也称测试用例,表示这是”一系列测试”中的一项,相当于item,如何测试?测试逻辑?都是在it的回调函数中实现的

assert断言

1
2


power-assert(使用)

完全兼容assert

案例参考:lodash-id test

待续~~

expert 断言

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
// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);

测试块生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe('test', function() {
// 在本测试块的所有测试用例之前执行且仅执行一次
before(function() {
});
// 在本测试块的所有测试用例之后执行且仅执行一次
after(function() {
});

// 在测试块的每个测试用例之前执行(有几个测试用例it,就执行几次)
beforeEach(function() {
});
// 在测试块的每个测试用例之后执行(同上)
afterEach(function() {
});

// 测试用例
it('test item1', function () {
})
});

用例管理:only和skip

只想执行某个用例,我们就用only方式调用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var add = require('../src/add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
// 测试组中可以有多个only方式执行的用例
it.only('1 加 1 应该等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
it.only('1 加 0 应该等于 1', function() {
expect(add(1, 0)).to.be.equal(1);
});

// 组内有only,非only方式执行的用例不会被执行,切记
it('1 加 -1 应该等于 0', function() {
expect(add(1, -1)).to.be.equal(0);
});
});
1
2
3
4
5
describe('加法函数的测试', function() {
it.skip('1 加 0 应该等于 1', function() {
expect(add(1, 0)).to.be.equal(1);
});
});

端到端测试

和单元测试/集成测试相比,E2E测试可以用更少的代码覆盖更多的测试场景,代价则是测试的细致程度不足。

需要注意的是:不是所有项目都适合写E2E测试,以下几点可以帮助你判断自己的项目适不适用E2E测试。

  • 项目周期长。编写E2E测试会消耗不少时间和精力,如果不是长期维护的项目没必要进行这些投入。
  • 项目迭代频率高。如果迭代间隔非常长,有充足的测试时间和人手,那么细致的手动界面测试会更加可靠。
  • 页面结构稳定。E2E测试一般依靠css/xpath选择器作为定位元素的方法,如果页面的dom解构经常变更,那么维护测试用例的成本会上升。

此外在以下场景中E2E测试的效果比较好:

  • 纯静态页面,最好是连内容都不变的页面。可以通过在浏览器层面做截图,然后比较前后两个迭代版本截图的是否一致来判断变更是否破坏了页面的最终展示或者展示的改变是否和预期一致。
  • 兼容性测试。如果需要测试项目在各种浏览器下的兼容性问题,可以通过E2E测试一次覆盖多个不同浏览器。
  • 交互复杂、交互步骤多。例如一个复杂的多步表单的填写,人工操作成本越大,用E2E自动完成带来的收益就越多。
    但是要留心,和其他测试一样,如果你的测试流程有数据写入,那么在测试结束后应该清除写入的数据,保证再次运行时又是一个一致的环境。

服务器

http协议

HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

浏览器就是依靠Content-Type来判断响应的内容是网页还是图片,是视频还是音乐。浏览器并不靠URL来判断响应的内容

HTTP请求流程

一、浏览器首先向服务器发送HTTP请求,请求包括:

方法:GET还是POSTGET仅请求资源,POST会附带用户数据;

路径:/full/url/path

域名:由Host头指定:Host: www.sina.com.cn

二、服务器向浏览器返回HTTP响应,响应包括:

响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;

响应类型:由Content-Type指定,例如:Content-Type: text/html;charset=utf-8表示响应类型是HTML文本,并且编码是UTF-8Content-Type: image/jpeg表示响应类型是JPEG格式的图片;

响应的内容:在Body中,如网页的HTML源码

三、需要其他资源则再次发出HTTP请求,重复步骤一、二;

HTTP格式

HTTP协议是一种文本协议,一个HTTP包含Header和Body两部分,其中Body是可选的。

HTTP GET请求的格式
1
2
3
4
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3

每个Header一行一个,换行符是\r\n

HTTP POST请求的格式
1
2
3
4
5
6
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3

body data goes here...

当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。

HTTP响应的格式
1
2
3
4
5
6
200 OK
Header1: Value1
Header2: Value2
Header3: Value3

body data goes here...

HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。

当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,此时需要将Body数据先解压缩,才能得到真正的数据。

http服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 导入http模块:
let http = require('http')

// 创建http server,并传入回调函数:
let server = http.createServer(function(request, response) {
// 回调函数接收request和response对象,
// 获得HTTP请求的method和url:
console.log(request.method + ': ' + request.url)
// 将HTTP响应200写入response, 同时设置Content-Type: text/html:
response.writeHead(200, { 'Content-Type': 'text/html' })
// 将HTTP响应的HTML内容写入response:
response.end('<h1>Hello world!</h1>')
})

// 让服务器监听8080端口:
server.listen(8080)

console.log('Server is running at http://127.0.0.1:8080/')

//浏览器访问:http://localhost:8080/

文件服务器

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
// 创建服务器:
let server = http.createServer(function(request, response) {
// 获得URL的path,类似 '/css/bootstrap.css':
let pathname = url.parse(request.url).pathname
// 获得对应的本地文件路径,类似 '/srv/www/css/bootstrap.css':
let filepath = path.join(root, pathname)
// 获取文件状态:
fs.stat(filepath, function(err, stats) {
if (!err && stats.isFile()) {
// 没有出错并且文件存在:
console.log('200 ' + request.url)
// 发送200响应:
response.writeHead(200)
// 将文件流导向response:
fs.createReadStream(filepath).pipe(response)
} else {
// 出错了或者文件不存在:
console.log('404 ' + request.url)
// 发送404响应:
response.writeHead(404)
response.end('404 Not Found')
}
})
})

server.listen(8080)

console.log('Server is running at http://127.0.0.1:8080/')

//浏览器访问:http://localhost:8080/index.html

浏览器油猴脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//创建按钮
function initbutton(){
document.querySelector('#top .icons-wrap').insertAdjacentHTML('beforeend', `
<a class="vediobutton" target="_blank" style="
display: block;
border: 1px solid #d1d1d1;
color: #d1d1d1;
cursor: pointer;
width: 56px;
height: 56px;
padding: 0;
text-align: center;
border-radius: 50%;
border: 1px solid;
margin-top: 20px;
"> </a>`)

let vediobutton = document.querySelector('.vediobutton')
vediobutton.addEventListener('click', function (event) {

}, false)
}