WebKong的网络日志

分享工作中的所用、所想、所感。

0%

前端不能少了模块化、压缩、打包等等的工具,就来用一下webpack。Mac OX 系统为例。

webpack官网

安装webpack

需要的依赖

  1. node.js
  2. npm

执行命令

1
sudo npm install webpack -g

输入密码,安装完成之后,就在全局安装了webpack,并且webpack命令就可以使用了。

在项目中使用

1
2
3
4
$ mkdir test
$ cd test
$ npm init

通过npm 初始化一个项目,生成一个package.json文件。

1
npm install webpack --save-dev

就会在项目中添加webpack的依赖,也可以选择安装哪个版本

1
$ npm install webpack@1.2.x --save-dev

后面会说到如何配置webpack,和如何添加jQuery和jQuery的插件。

ES6字符的Unicode表示法

JavaScript允许采用\uxxxx形式表示一个字符,其中“xxxx”表示字符的码点。

1
2
"\u0061"
// "a"

但是,这种表示法只限于\u0000——\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表达。

1
2
3
4
5
"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// " 7"

上面代码表示,如果直接在“\u”后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript会理解成“\u20BB+7”。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。

ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

1
2
3
4
5
6
7
8
9
10
11
"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true

上面代码中,最后一个例子表明,大括号表示法与四字节的UTF-16编码是等价的。

有了这种表示法之后,JavaScript共有6种方法可以表示一个字符。

1
2
3
4
5
6
'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
codePointAt()

JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode码点大于0xFFFF的字符),JavaScript会认为它们是两个字符。

1
2
3
4
5
6
var s = "𠮷";
s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271

上面代码中,汉字“𠮷”的码点是0x20BB7,UTF-16编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。

ES6提供了codePointAt方法,能够正确处理4个字节储存的字符,返回一个字符的码点。

1
2
3
4
5
6
var s = '𠮷a';

s.codePointAt(0) // 134071
s.codePointAt(1) // 57271

s.charCodeAt(2) // 97

codePointAt方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。

总之,codePointAt方法会正确返回32位的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。

codePointAt方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString方法转换一下。

1
2
3
var s = '𠮷a';
s.codePointAt(0).toString(16) // "20bb7"
s.charCodeAt(2).toString(16) // "61"

你可能注意到了,codePointAt方法的参数,仍然是不正确的。比如,上面代码中,字符a在字符串s的正确位置序号应该是1,但是必须向charCodeAt方法传入2。解决这个问题的一个办法是使用for…of循环,因为它会正确识别32位的UTF-16字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var s = '𠮷a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false
String.fromCodePoint()

ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于0xFFFF)。

1
2
String.fromCharCode(0x20BB7)
// "ஷ"

上面代码中,String.fromCharCode不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。

ES6提供了String.fromCodePoint方法,可以识别0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。

1
2
3
4
String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

上面代码中,如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。

注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。

字符串的遍历器接口

ES6为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。

1
2
3
4
5
6
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"

除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

1
2
3
4
5
6
7
8
9
10
11
12
var text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "

for (let i of text) {
console.log(i);
}
// "𠮷"

上面代码中,字符串text只有一个字符,但是for循环会认为它包含两个字符(都不可打印),而for…of循环会正确识别出这一个字符。

at()
ES5对字符串对象提供charAt方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF的字符。
'abc'.charAt(0) // "a"
'𠮷'.charAt(0) // "\uD842"
上面代码中,charAt方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。

目前,有一个提案,提出字符串实例的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。
‘abc’.at(0) // “a”
‘𠮷’.at(0) // “𠮷”
这个方法可以通过垫片库实现。

normalize()

许多欧洲语言有语调符号和重音符合。为了表示它们,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。

1
2
3
'\u01D1'==='\u004F\u030C' //false
'\u01D1'.length // 1
'\u004F\u030C'.length // 2

上面代码表示,JavaScript将合成字符视为两个字符,导致两种表示方法不相等。

ES6提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。

1
2
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true

normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。

  • NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
  • NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
  • NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
  • NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
1
2
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2

上面代码表示,NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。

不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间判断。

includes(), startsWith(), endsWith()

传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var s = 'Hello world!';

    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true
    这三个方法都支持第二个参数,表示开始搜索的位置。

    var s = 'Hello world!';

    s.startsWith('world', 6) // true
    s.endsWith('Hello', 5) // true
    s.includes('Hello', 6) // false
    >上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
参数如果是小数,会被取整。

'na'.repeat(2.9) // "nana"
如果repeat的参数是负数或者Infinity,会报错。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0,repeat视同为0。

'na'.repeat(-0.9) // ""
参数NaN等同于0。

'na'.repeat(NaN) // ""
如果repeat的参数是字符串,则会先转换成数字。

'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

padStart(),padEnd()

ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。

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
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
>padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

1.如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
2.如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

'abc'.padStart(10, '0123456789')
// '0123456abc'
3.如果省略第二个参数,则会用空格补全长度。

'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
4.padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
另一个用途是提示字符串格式。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

模板字符串

传统的JavaScript语言,输出模板通常是这样写的。

1
2
3
4
5
6
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。

1
2
3
4
5
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

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
// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

var greeting = `\`Yo\` World!`;
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。

$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
模板字符串中嵌入变量,需要将变量名写在${}之中。

function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}
大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

var x = 1;
var y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3
模板字符串之中还能调用函数。

function fn() {
return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

如果模板字符串中的变量没有声明,将报错。

// 变量place没有声明
var msg = `Hello, ${place}`;
// 报错
由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。

`Hello ${'World'}`
// "Hello World"
模板字符串甚至还能嵌套。

const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。

const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>
如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。

// 写法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 写法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"
实例:模板编译
下面,我们来看一个通过模板字符串,生成正式模板的实例。

var template = `
<ul>
<% for(var i=0; i < data.supplies.length; i++) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%...%>放置JavaScript代码,使用<%= ... %>输出JavaScript表达式。

怎么编译这个模板字符串呢?

一种思路是将其转换为JavaScript表达式字符串。

echo('<ul>');
for(var i=0; i < data.supplies.length; i++) {
echo('<li>');
echo(data.supplies[i]);
echo('</li>');
};
echo('</ul>');
这个转换使用正则表达式就行了。

var evalExpr = /<%=(.+?)%>/g;
var expr = /<%([\s\S]+?)%>/g;

template = template
.replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');

template = 'echo(`' + template + '`);';
然后,将template封装在一个函数里面返回,就可以了。

var script =
`(function parse(data){
var output = "";

function echo(html){
output += html;
}

${ template }

return output;
})`;

return script;
将上面的内容拼装成一个模板编译函数compile。

function compile(template){
var evalExpr = /<%=(.+?)%>/g;
var expr = /<%([\s\S]+?)%>/g;

template = template
.replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');

template = 'echo(`' + template + '`);';

var script =
`(function parse(data){
var output = "";

function echo(html){
output += html;
}

${ template }

return output;
})`;

return script;
}
compile函数的用法如下。

var parse = eval(compile(template));
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });
// <ul>
// <li>broom</li>
// <li>mop</li>
// <li>cleaner</li>
// </ul>

标签模板

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

alert123
// 等同于
alert(123)
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

var a = 5;
var b = 10;

tagHello ${ a + b } world ${ a * b };
// 等同于
tag([‘Hello ‘, ‘ world ‘, ‘’], 15, 50);
上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。

函数tag依次会接收到多个参数。

function tag(stringArr, value1, value2){
// …
}

// 等同于

function tag(stringArr, …values){
// …
}
tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。

tag函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此tag会接受到value1和value2两个参数。

tag函数所有参数的实际值如下。

第一个参数:[‘Hello ‘, ‘ world ‘, ‘’]
第二个参数: 15
第三个参数:50
也就是说,tag函数实际上以下面的形式调用。

tag([‘Hello ‘, ‘ world ‘, ‘’], 15, 50)
我们可以按照需要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。

var a = 5;
var b = 10;

function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);

return “OK”;
}

tagHello ${ a + b } world ${ a * b};
// “Hello “
// “ world “
// “”
// 15
// 50
// “OK”
下面是一个更复杂的例子。

var total = 30;
var msg = passthruThe total is ${total} (${total*1.05} with tax);

function passthru(literals) {
var result = ‘’;
var i = 0;

while (i < literals.length) {
result += literals[i++];
if (i < arguments.length) {
result += arguments[i];
}
}

return result;
}

msg // “The total is 30 (31.5 with tax)”
上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。

passthru函数采用rest参数的写法如下。

function passthru(literals, …values) {
var output = “”;
for (var index = 0; index < values.length; index++) {
output += literals[index] + values[index];
}

output += literals[index]
return output;
}
“标签模板”的一个重要应用,就是过滤HTML字符串,防止用户输入恶意内容。

var message =
SaferHTML<p>${sender} has sent you a message.</p>;

function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);

// Escape special characters in the substitution.
s += arg.replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");

// Don't escape special characters in the template.
s += templateData[i];

}
return s;
}
上面代码中,经过SaferHTML函数处理,HTML字符串的特殊字符都会被转义。

标签模板的另一个应用,就是多语言转换(国际化处理)。

i18nWelcome to ${siteName}, you are visitor number ${visitorNumber}!
// “欢迎访问xxx,您是第xxxx位访问者!”
模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。

// 下面的hashTemplate函数
// 是一个自定义的模板处理函数
var libraryHtml = hashTemplate <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul>;
除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。

jsx <div> <input ref='input' onChange='${this.handleChange}' defaultValue='${this.state.value}' /> ${this.state.value} </div>
上面的代码通过jsx函数,将一个DOM字符串转为React对象。你可以在Github找到jsx函数的具体实现。

下面则是一个假想的例子,通过java函数,在JavaScript代码之中运行Java代码。

javaclass HelloWorldApp { public static void main(String[] args) { System.out.println(“Hello World!”); // Display the string. } }
HelloWorldApp.main();
模板处理函数的第一个参数(模板字符串数组),还有一个raw属性。

tagFirst line\nSecond line

function tag(strings) {
console.log(strings.raw[0]);
// “First line\nSecond line”
}
上面代码中,tag函数的第一个参数strings,有一个raw属性,也指向一个数组。该数组的成员与strings数组完全一致。比如,strings数组是[“First line\nSecond line”],那么strings.raw数组就是[“First line\nSecond line”]。两者唯一的区别,就是字符串里面的斜杠都被转义了。比如,strings.raw数组会将\n视为\和n两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。

String.raw()
ES6还为原生的String对象,提供了一个raw方法。

String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。

String.rawHi\n${2+3}!;
// “Hi\n5!”

String.rawHi\u000A!;
// ‘Hi\u000A!’
如果原字符串的斜杠已经转义,那么String.raw不会做任何处理。

String.rawHi\\n
// “Hi\n”
String.raw的代码基本如下。

String.raw = function (strings, …values) {
var output = “”;
for (var index = 0; index < values.length; index++) {
output += strings.raw[index] + values[index];
}

output += strings.raw[index]
return output;
}
String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。

String.raw方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。

String.raw({ raw: ‘test’ }, 0, 1, 2);
// ‘t0e1s2t’

// 等同于
String.raw({ raw: [‘t’,’e’,’s’,’t’] }, 0, 1, 2);

来源

说一下JavaScript的连等赋值,遇到的坑,例如

1
2
3
4
5
6
var a = {n:1};
var b = a; // 持有a,以回查
a.x = a = {n:2};
alert(a.x);// --> undefined
alert(b.x);// --> {n:2}

我的思路出发点

1.运算符优先级来考虑,.运算优先于=赋值运算
2.右结合性

1
2
3
4
5
6
7
8
9
10
11
var a = {n:1}; /*定义a,赋值为{n:1},假设指向存储地址为add_1*/
var b = a; /*定义b,赋值为a,指向同一存储地址add_1*/
a.x = a = {n:2};
/*(1):定义a.x,赋值为a,即{n:1},存储地址add_1上面的内容被更改
(2):a.x重新赋值为{n:2},存储地址add_1上面的内容被更改
(3):a重新赋值为{n:2},更改存储地址add_2*/
alert(a.x);
/*现在a的存储地址add_2,内容为{n:2},上面并不存在a.x属性,所以为undefined*/
alert(b.x);
/*现在b的存储地址add_1,内容为{n:1,x:{n:2}},所以b.x为{n:2}*/

基本用法

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

以前,为变量赋值,只能直接指定值。

1
2
3
var a = 1;
var b = 2;
var c = 3;

ES6允许写成下面这样。
var [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就等于undefined

1
2
var [foo] = [];
var [bar, foo] = [1];

以上两种情况都属于解构不成功,foo的值都会等于undefined

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

上面两个例子,都属于不完全解构,但是可以成功。

如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。

1
2
3
4
5
6
7
8
9
10
11
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
````
上面的表达式都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。

>解构赋值不仅适用于var命令,也适用于let和const命令。

var [v1, v2, …, vN ] = array;
let [v1, v2, …, vN ] = array;
const [v1, v2, …, vN ] = array;

1
对于Set结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set([“a”, “b”, “c”]);
x // “a”

1
事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

var [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
上面代码中,fibs是一个Generator函数,原生具有Iterator接口。解构赋值会依次从这个接口获取值。

1
2
3
默认值
-----
解构赋值允许指定默认值。

var [foo = true] = [];
foo // true

[x, y = ‘b’] = [‘a’]; // x=’a’, y=’b’
[x, y = ‘b’] = [‘a’, undefined]; // x=’a’, y=’b’

1
>注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

var [x = 1] = [undefined];
x // 1

var [x = 1] = [null];
x // null

1
2
3
>上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

function f() {
console.log(‘aaa’);
}

let [x = f()] = [1];
上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError
上面最后一个表达式之所以会报错,是因为x用到默认值y时,y还没有声明。

1
2
3
4
对象的解构赋值
-------

解构不仅可以用于数组,还可以用于对象。

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

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

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

var { baz } = { foo: “aaa”, bar: “bbb” };
baz // undefined

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

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

var { 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
2
>这实际上说明,对象的解构赋值是下面形式的简写。

var { foo: foo, bar: bar } = { foo: “aaa”, bar: “bbb” };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

var { foo: baz } = { foo: “aaa”, bar: “bbb” };
baz // “aaa”
foo // error: foo is not defined
上面代码中,真正被赋值的是变量baz,而不是模式foo。

1
2
>注意,采用这种写法时,变量的声明和赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。

let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration “foo”

let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration “baz”
上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。如果没有第二个let命令,上面的代码就不会报错。

let foo;
({foo} = {foo: 1}); // 成功

let baz;
({bar: baz} = {bar: 1}); // 成功
上面代码中,let命令下面一行的圆括号是必须的,否则会报错。因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。

和数组一样,解构也可以用于嵌套结构的对象。

var obj = {
p: [
‘Hello’,
{ y: ‘World’ }
]
};

var { p: [x, { y }] } = obj;
x // “Hello”
y // “World”
注意,这时p是模式,不是变量,因此不会被赋值。

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

var { loc: { start: { line }} } = node;
line // 1
loc // error: loc is undefined
start // error: start is undefined
上面代码中,只有line是变量,loc和start都是模式,不会被赋值。

1
下面是嵌套赋值的例子。

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

1
对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x:y = 3} = {};
y // 3

var {x:y = 3} = {x: 5};
y // 5

var { message: msg = ‘Something went wrong’ } = {};
msg // “Something went wrong”

1
>默认值生效的条件是,对象的属性值严格等于undefined。

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null
上面代码中,如果x属性等于null,就不严格相等于undefined,导致默认值不会生效。

1
如果解构失败,变量的值等于undefined。

var {foo} = {bar: ‘baz’};
foo // undefined

1
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

// 报错
var {foo: {bar}} = {baz: ‘baz’};
上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码。

var _tmp = {baz: ‘baz’};
_tmp.foo.bar // 报错
如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
var x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。

// 正确的写法
({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。

解构赋值允许,等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false]);
({} = ‘abc’);
({} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

let { log, sin, cos } = Math;
上面代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。

1
由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

var arr = [1, 2, 3];
var {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

1
2
3
4
5
上面代码对数组进行对象结构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”。

字符串的解构赋值
-------
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = ‘hello’;
a // “h”
b // “e”
c // “l”
d // “l”
e // “o”
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = ‘hello’;
len // 5

1
2
3
4
5

数值和布尔值的解构赋值
-----
>解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

1
2

>解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

1
2
3
4

函数参数的解构赋值
-----
函数的参数也可以使用解构赋值。

function add([x, y]){
return x + y;
}

add([1, 2]); // 3

1
2
3
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。

下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

1
函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

1
2
3
上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。

注意,下面的写法会得到不一样的结果。

function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

1
2
3
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

`undefined`就会触发函数参数的默认值。

[1, undefined, 3].map((x = ‘yes’) => x);
// [ 1, ‘yes’, 3 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
圆括号问题
------
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。

由此带来的问题是,如果模式中出现圆括号怎么处理。

>ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。

但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。

不能使用圆括号的情况
以下三种解构赋值不得使用圆括号。

(1)变量声明语句中,不能带有圆括号。

// 全部报错
var [(a)] = [1];

var {x: (c)} = {};
var ({x: c}) = {};
var {(x: c)} = {};
var {(x): c} = {};}

var { o: ({ p: p }) } = { o: { p: 2 } };
上面三个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

1
2
3
(2)函数参数中,模式不能带有圆括号。

函数参数也属于变量声明,因此不能带有圆括号。

// 报错
function f([(z)]) { return z; }

1
(3)赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。

// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
上面代码将整个模式放在圆括号之中,导致报错。

// 报错
[({ p: a }), { x: c }] = [{}, {}];
上面代码将嵌套模式的一层,放在圆括号之中,导致报错。

可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。

1
2
3
4
5
6
7
8
9
10
11
12
13

用途
-----
变量的解构赋值用途很多。

* 交换变量的值

`[x, y] = [y, x];`
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。

* 从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {
return [1, 2, 3];
}
var [a, b, c] = example();

// 返回一个对象

function example() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = example();

1
2
3
* 函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

// 参数是一组有次序的值
function f([x, y, z]) { … }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { … }
f({z: 3, y: 2, x: 1});

1
2
3
* 提取JSON数据

解构赋值对提取JSON对象中的数据,尤其有用。

var jsonData = {
id: 42,
status: “OK”,
data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, “OK”, [867, 5309]
上面代码可以快速提取JSON数据的值。

1
* 函数参数的默认值

jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// … more config
}) {
// … do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。
1
2
3
* 遍历Map结构

任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。

var map = new Map();
map.set(‘first’, ‘hello’);
map.set(‘second’, ‘world’);

for (let [key, value] of map) {
console.log(key + “ is “ + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。

// 获取键名
for (let [key] of map) {
// …
}

// 获取键值
for (let [,value] of map) {
// …
}

1
2
3
4
* 输入模块的指定方法

加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。


const { SourceMapConsumer, SourceNode } = require(“source-map”);



>[来源](http://es6.ruanyifeng.com/#docs/destructuring)

转载自阮一峰的日志http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html

我每天使用 Git ,但是很多命令记不住。

一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。

image

下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。

1
2
3
4
* Workspace:工作区
* Index / Stage:暂存区
* Repository:仓库区(或本地仓库)
* Remote:远程仓库

新建代码库

1
2
3
4
5
6
7
8
# 在当前目录新建一个Git代码库
$ git init

# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]

# 下载一个项目和它的整个代码历史
$ git clone [url]

配置

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

1
2
3
4
5
6
7
8
9
# 显示当前的Git配置
$ git config --list

# 编辑Git配置文件
$ git config -e [--global]

# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

增加/删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
$ git add -p

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

代码提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 提交暂存区到仓库区
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a

# 提交时显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

分支

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
# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 列出所有本地分支和远程分支
$ git branch -a

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

标签

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
# 列出所有tag
$ git tag

# 新建一个tag在当前commit
$ git tag [tag]

# 新建一个tag在指定commit
$ git tag [tag] [commit]

# 删除本地tag
$ git tag -d [tag]

# 删除远程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]

查看信息

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
# 显示有变更的文件
$ git status

# 显示当前分支的版本历史
$ git log

# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat

# 搜索提交历史,根据关键词
$ git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff
$ git log -p [file]

# 显示过去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn

# 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# 显示暂存区和工作区的差异
$ git diff

# 显示暂存区和上一个commit的差异
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# 显示某次提交的元数据和内容变化
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交
$ git reflog

远程同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

撤销

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
# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

其他

1
2
# 生成一个可供发布的压缩包
$ git archive

当一些操作涉及到不一样的数据类型,就会出现类型转换(隐形)

  • 符号+ -
    1
    2
    3
    4
    5
    + 有字符串拼接和加法运算的功能

    如果把一个变量a ,转换成数字,最简单的 方法就是 用 a-0
    同理如果想转化成字符串就 a+'' ,用变量加上一个空字符串

  • 等于”==”和严格等于”===”
    1
    2
    3
    4
    5
    6
    严格等于 ===
    判断两边的类型(相同or不同),直接返回true OR false
    null === null true == 也是true
    undefined === undefined true == 也是true
    NaN === NaN 返回false ,NaN 不等于自己 。 == 也返回false
    new Object 不等于 new Object 因为他们不是相同的对象,返回false 。 == 也返回false
  • 类型不同会尝试转换类型后比较
    1
    2
    3
    4
    5
    null == undifined  true
    number == string 转 number 1 == '1.0' true
    boolean == ? 转 number true == 1 true
    Object == number | string 尝试转换成基础类型
    new String('hi') == 'hi' true 其他的返回FALSE

Javascript的变量是松散类型的,它可以存储Javascript支持的任何数据类型,其变量的类型可以在运行时被动态改变。

正因为Javascript中变量类型具有动态性,在程序实际执行的过程中就需要用到类型转换的概念。类型转换可以分为隐式转换和显式转换,所谓隐 式转换即程序在运行时进行的自动转换,显式转换则是人为的对类型进行强制转换。

Javascript提供了以下转型函数:
转换为数值类型:Number(mix)、parseInt(string,radix)、parseFloat(string)
转换为字符串类型:toString(radix)、String(mix)
转换为布尔类型:Boolean(mix)

  • Number(mix)函数,可以将任意类型的参数mix转换为数值类型。其规则为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Boolean->Number : true,false --> 1,0
    Number->Number : 返回其本身
    undefined->Number : undefined --> NaN
    String->Number :
    + 如果字符串中只包含数字,则将其转换为十进制(忽略前导0)
    + 如果字符串中包含有效的浮点格式,将其转换为浮点数值(忽略前导0)
    + 如果是空字符串,将其转换为0
    + 如果字符串中包含非以上格式,则将其转换为NaN
    + 如果是对象,则调用对象的valueOf()方法,然后依据前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,再次依照前面的规则转换返回的字符串值。

    下表列出了对象的valueOf()的返回值:

对象 返回值
Array 数组的元素被转换为字符串,这些字符串由逗号分隔,连接在一起。其操作与 Array.toString 和 Array.join 方法相同。
Boolean Boolean 值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数本身。
Number 数字值。
Object 对象本身。这是默认情况。
String 字符串值。
  • parseInt(string, radix)函数,将字符串转换为整数类型的数值。它也有一定的规则:
    1
    2
    3
    4
    5
    忽略字符串前面的空格,直至找到第一个非空字符
    如果第一个字符不是数字符号或者负号,返回NaN
    如果第一个字符是数字,则继续解析直至字符串解析完毕或者遇到一个非数字符号为止
    如果上步解析的结果以0开头,则将其当作八进制来解析;如果以0x开头,则将其当作十六进制来解析
    如果指定radix参数,则以radix为基数进行解析
  • parseFloat(string)函数,将字符串转换为浮点数类型的数值。
    1
    2
    它的规则与parseInt基本相同,但也有点区别:
    字符串中第一个小数点符号是有效的,另外parseFloat会忽略所有前导0,如果字符串包含一个可解析为整数的数,则返回整数值而不是浮点数值。
  • toString(radix)方法。除undefined和null之外的所有类型的值都具有toString()方法,其作用是返回对象的字符串表示。
对象 操作
Array 将 Array 的元素转换为字符串。结果字符串由逗号分隔,且连接起来。
Boolean 如果 Boolean 值是 true,则返回 “true”。否则,返回 “false”。
Date 返回日期的文字表示法。
Error 返回一个包含相关错误信息的字符串。
Function 返回如下格式的字符串,其中 functionname 是被调用 toString 方法函数的名称:
function functionname( ) { [native code] }
Number 返回数字的文字表示。
String 返回 String 对象的值。

默认 返回 “[object objectname]”,其中 objectname 是对象类型的名称。

  • String(mix)函数,将任何类型的值转换为字符串,其规则为:
    1
    2
    3
    如果有toString()方法,则调用该方法(不传递radix参数)并返回结果
    如果是null,返回”null”
    如果是undefined,返回”undefined”
  • Boolean(mix)函数,将任何类型的值转换为布尔值。
1
以下值会被转换为false:fals* ”"、0、NaN、null、undefined,其余任何值都会被转换为true。

编码规则

Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码。它将需要编码的数据拆分成字节数组。以3个字节为一组。按顺序排列24 位数据,再把这24位数据分成4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。这样就把一个3字节为一组的数据重新编码成了4个字节。当所要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。这时在最后一组填充1到2个0字节。并在最后编码完成后在结尾添加1到2个 “=”。

代码实现

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
  //下面是64个基本的编码
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64DecodeChars = new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 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, -1, -1, -1, -1, -1,
-1, 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, -1, -1, -1, -1, -1);
//编码的方法
function base64encode(str) {
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while(i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
out += base64EncodeChars.charAt(c3 & 0x3F);
}
return out;
}
//解码的方法
function base64decode(str) {
var c1, c2, c3, c4;
var i, len, out;
len = str.length;
i = 0;
out = "";
while(i < len) {

do {
c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
} while(i < len && c1 == -1);
if(c1 == -1)
break;

do {
c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
} while(i < len && c2 == -1);
if(c2 == -1)
break;
out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));

do {
c3 = str.charCodeAt(i++) & 0xff;
if(c3 == 61)
return out;
c3 = base64DecodeChars[c3];
} while(i < len && c3 == -1);
if(c3 == -1)
break;
out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));

do {
c4 = str.charCodeAt(i++) & 0xff;
if(c4 == 61)
return out;
c4 = base64DecodeChars[c4];
} while(i < len && c4 == -1);
if(c4 == -1)
break;
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
}
return out;
}
function utf16to8(str) {
var out, i, len, c;
out = "";
len = str.length;
for(i = 0; i < len; i++) {
c = str.charCodeAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
} else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
} else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
}
return out;
}
function utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while(i < len) {
c = str.charCodeAt(i++);
switch(c >> 4)
{
case case case case case case case case
// 0xxxxxxx
out += str.charAt(i-1);
break;
case case
// 110x xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
}

调用

1
2
3
4
5
//编码
value = base64encode(utf16to8(src))

//解码
value = utf8to16(base64decode(src))

列举常用的JavaScript字符串操作的方法

concat

将两个或多个字符的文本组合起来,返回一个新的字符串。

1
2
3
4
5
var a = "hello";
var b = ",world";
var c = a.concat(b);
alert(c);
//c = "hello,world"

indexOf

返回字符串中一个子串第一处出现的索引(从左到右搜索)。如果没有匹配项,返回 -1 。

1
2
3
4
5
var index1 = a.indexOf("l");
//index1 = 2
var index2 = a.indexOf("l",3);
//index2 = 3

charAt

返回指定位置的字符。

1
2
var get_char = a.charAt(0);
//get_char = "h"

lastIndexOf

返回字符串中一个子串最后一处出现的索引(从右到左搜索),如果没有匹配项,返回 -1 。

1
2
3
4
var index1 = lastIndexOf('l');
//index1 = 3
var index2 = lastIndexOf('l',2)
//index2 = 2

match

检查一个字符串匹配一个正则表达式内容,如果么有匹配返回 null。

1
2
3
4
5
var re = new RegExp(/^\w+$/);
var is_alpha1 = a.match(re);
//is_alpha1 = "hello"
var is_alpha2 = b.match(re);
//is_alpha2 = null

substring

返回字符串的一个子串,传入参数是起始位置和结束位置。

1
2
3
4
var sub_string1 = a.substring(1);
//sub_string1 = "ello"
var sub_string2 = a.substring(1,4);
//sub_string2 = "ell"

substr

返回字符串的一个子串,传入参数是起始位置和长度

1
2
3
4
var sub_string1 = a.substr(1);
//sub_string1 = "ello"
var sub_string2 = a.substr(1,4);
//sub_string2 = "ello"

replace

用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。

1
2
3
4
var result1 = a.replace(re,"Hello");
//result1 = "Hello"
var result2 = b.replace(re,"Hello");
//result2 = ",world"

执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。

1
2
3
4
var index1 = a.search(re);
//index1 = 0
var index2 = b.search(re);
//index2 = -1

slice

提取字符串的一部分,并返回一个新字符串(与 substring 相同)。

1
2
3
4
var sub_string1 = a.slice(1);
//sub_string1 = "ello"
var sub_string2 = a.slice(1,4);
//sub_string2 = "ell"

split

通过将字符串划分成子串,将一个字符串做成一个字符串数组。

1
2
var arr1 = a.split("");
//arr1 = [h,e,l,l,o]

length

返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。

1
2
var len = a.length();
//len = 5

toLowerCase
——

将整个字符串转成小写字母。

1
2
var lower_string = a.toLowerCase();
//lower_string = "hello"

toUpperCase
——

将整个字符串转成大写字母。

1
2
var upper_string = a.toUpperCase();
//upper_string = "HELLO"

package.json 的版本的匹配规则

用version来表示一个具体的版本

一般范围语法

  • version //必须严格匹配到 version版本
  • >version //必须大于 version的版本
  • >=version //大于等于 version的版本
  • <version //小于version的版本
  • <=version //小于等于
  • 带alpha的版本 // 例如 >1.2.3-alpha.3,允许匹配版本1.2.3-alpha.7,但它不匹配 3.4.5-alpha.9,即使3.4.5-alpha.9在技术上是“大于” 1.2.3-alpha.3的。SemVer排序规则

高级区域语法

  • version1 - version2 //连字符范围, 1.2.3 - 2.3.4 等价于 >=1.2.3 <=2.3.4

  • ~version //一般是补丁级别的修改 例如:~1.2.3 等价于 >=1.2.3 <1.(2+1).0 等价于 >=1.2.3 <1.3.0

  • ^version //“兼容的版本” 参考

  • 1.2.x //匹配 >=1.2.0 <1.3.0

  • * //匹配任意版本
    “” // ""(空字符串)等价于 * 等价于 >=0.0.0

  • range1 || range2 //range1 或者 range2 的任一版本.

更多的使用方法请访问参考链接

参考

The semantic versioner for npm
Semantic Versioning 2.0.0