Vue 学习笔记(二十一):vue 相关外围:NPM, ES6

vue 相关外围:NPM,ES2015(ES6)

NPM

搜索安装包

https://npms.io

Downloading and installing packages locally

npm install <package_name> 将在当前目录下创建文件夹 node_modules(如果不存在)。

如果本地目录没有 package.json 文件,将会安装最新包。
如果本地目录有 package.json 文件,将会安装满足 package.json 声明的语义化版本规则( semver rule)的最新版本。

semantic versioning

https://semver.org

To keep the JavaScript ecosystem healthy, reliable, and secure, every time you make significant updates to an npm package you own, we recommend publishing a new version of the package with an updated version number in the package.json file that follows the semantic versioning spec. Following the semantic versioning spec helps other developers who depend on your code understand the extent of changes in a given version, and adjust their own code if necessary.
(来自翻译网站翻译)为了保持JavaScript生态系统的健康、可靠和安全,每次您对自己拥有的npm包进行重大更新时,我们都建议在遵循语义版本规范的package.json文件中发布一个带有更新版本号的新版本包。遵循语义版本规范有助于其他依赖您的代码的开发人员了解某个版本的变化程度,并在必要时调整自己的代码。

Incrementing semantic versions in published packages
Code status Stage Rule Example version
首次发布 新产品 从1.0.0开始 1.0.0
Backward 向后兼容的错误修复 补丁版本 增加第三位数字 1.0.1
向后兼容的新功能 小版本 增加中间数字并将最后一位数字重置为零 1.1.0
破坏向后兼容的更改(与之前版本不兼容) 主要版本 将第一位数字递增并将中间和最后一位数字重置为零 2.0.0

向前兼容:forward,兼容未来
向后兼容:backward,兼容旧版本

  1. 使用 Semantic Versioning 的软件必须声明一个公共的 API,这个 API 可能是定义在代码里的,或者仅仅存在于文档里,不论用什么方式实现,它都必须精确而全面。

  2. 一个正常的版本号必须使用 X.Y.Z 的格式,其中 X,Y,和 Z 都是非负的整数,并且 必须不能 包含前导零。X 是主版本号,Y 是次版本号,而 Z 是补丁版本号。 每个元素都必须以数字的方式递增。 举例: 1.9.0 -> 1.10.0 -> 1.11.0.

  3. 一旦一个打了版本的包被发布出去了,那个版本的内容就不能再修改了. 任何修改 必须 作为一个新的版本重新发布。

  4. 主版本为零 (0.y.z) 的版本,是用作初始开发阶段的。任何东西都可能在任意的时间被更改。这时候我们不应该认为它的 API 是稳定的。

  5. 1.0.0 版本表明对外公开 API 的形成。从此之后,版本号的递增方式取决于这个公开的API,以及它如何修订。

  6. 补丁版本号Z (x.y.Z | x > 0) 。如果只有向后兼容的bug修复被引入的化,补丁版本号 Z 必须递增. “Bug修复”是指一个修正错误行为的内部修改。

  7. 次版本号Y (x.Y.z | x > 0). 如果一个新的,向后兼容的功能被引入到了公开 API 里,次版本号 必须 递增。如果公开 API 的任何功能被标记为 “已弃用的”,次版本号必须递增. 如果大量的新功能或改进被引入到私有代码里的时候,次版本号可以递增。次版本号的改变可以包含补丁级别的改动。当递增了次版本号的时候,补丁版本号必须清零。

  8. 主版本号X (X.y.z | X > 0)。如果任何的向后不兼容的改动被引入到了公开 API中,主版本号必须递增。 它的递增可以包含次版本和补丁级的改动。 当主版本号递增时,次版本号和补丁版本号必须清零。

  9. 一个预发布版本可以通过在补丁版本号后面追加一个短线,以及一系列的用点分割的标识符来描述。标识符必须仅包含 ASCII 的阿拉伯数字和短线 [0-9A-Za-z-]。 标识符必须不为空. 数字标识符不能包含前导零。预发布版本比对应的正常版本的优先级要低。预发布版本表明,它不稳定,并且可能不满足其对应的正常版本所预定的兼容性要求。例:1.0.0-alpha,1.0.0-alpha.1,1.0.0-0.3.7,1.0.0-x.7.z.92.

  10. 编译时的附加信息,可以通过在补丁版本号后面追加一个加号,以及一系列的用点分割的标识符来描述。 标识符必须仅包含 ASCII 的 阿拉伯数字和短线 [0-9A-Za-z-]。标识符必须不为空。在比较版本优先级的时候,编译附加信息应该被忽略。因此,两个只有编译附加信息不同的版本,具有相同的优先级。编译附加信息的举例: 1.0.0-alpha+001,1.0.0+20130313144700,1.0.0-beta+exp.sha.5114f85.

  11. 优先级是指在排序的时候怎样比较不同的版本。

    1. 计算优先级的时候,必须将版本号以”主版本号”,”次版本号”,”补丁版本号”,”预发布标识符”的顺序拆分。
    2. 优先级取决于,在从左至右依次比较这些个标识符的时候,发现的第一个差别。”主版本号”,”次版本号”,”补丁版本号”总是以数字的方式参加比较。 举例: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1
    3. 当”主版本号”,”次版本号”,”补丁版本号” 都相同的时候,预发布版本比正常的版本优先级要低. 举例: 1.0.0-alpha < 1.0.0
    4. 如果两个预发布版本有相同的 “主版本号”,”次版本号”,”补丁版本号”,优先级就必须通过比较点分割的标识符来确定,从左至右依次比较,直到发现一个不同:

      1. 只有数字的标识符号以数值高低比较
      2. 有字母或连接号时则逐字以 ASCII 的排序来比较
      3. 数字的标识符号比非数字的标识符号优先级低
      4. 若开头的标识符号都相同时,字段比较多的预发布版本号优先层级高

        举例: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

Using semantic versioning to specify update types your package can accept

You can specify which update types your package can accept from dependencies in your package’s package.json file.
例如:

1
2
3
4
5
6
"dependencies": {
"foo": "1.0.0", // 指定了就是1.0.0版本
"bar": "~1.2.2", // 安装版本号不低于1.2.2的1.2.x的最新版本,例如:1.2.3, 1.2.4等等。1.2.1 ,1.3.x 等就不行了
"baz": "ˆ1.2.2", // 安装版本号不低于1.2.2的1.x.x的最新版本,例如: 1.2.7,1.3.1,1.7.8等。1.2.1 ,2.0.0 等就不行了。注意,如果配置是^0.x.x,则和~0.x.x的效果一样。
"lat": "latest" // 安装最新版本
}

安装 an unscoped package

npm install <package_name>

安装 a scoped public package

npm install @scope/package-name,需要指定 scope name

安装 a private package

必须有包的只读权限才能安装:npm install @scope/private-package-name

Testing package installation

检查本地 node_modules 目录下,有安装包的目录。

Installing a package with dist-tags

dist 是 distribution (分发、发行、发行版)的缩写。

1
npm install <package_name>@<tag>

例如,安装 example-package 的 beta 版本,npm install example-package@beta

Downloading and installing packages globally

1
npm install -g <package_name>

也可以修改全局安装目录,https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally

Updating packages downloaded from the registry

Updating local packages

  1. 进入工程目录,确保它有 package.json 文件
  2. 工程根目录:npm update
  3. 检查升级结果,npm outdated 不应该有任何输出

Updating globally-installed packages

  • 检查哪些包需要升级 npm outdated -g --depth=0
  • 只安装一个升级包,npm update -g <package_name>
  • 安装所有升级包,npm update -g

Using npm packages in your projects

Once you have installed a package in node_modules, you can use it in your code.

使用 unscoped packages

Node.js module

例如,使用 lodash in a Node.js module

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

to use the lodash package in a Node.js module, in the root directory of the module, create a file named index.js with the following contents:

1
2
3
4
5
// index.js
var lodash = require('lodash');

var output = lodash.without([1, 2, 3], 1);
console.log(output);

Run the code using node index.js. It should output [2, 3].

package.json

List the package under dependencies, you can optionally include a semantic version

1
2
3
4
5
{
"dependencies": {
"@package_name": "^1.0.0"
}
}

使用 scoped packages

Node.js module

必须指定 scope var projectName = require("@scope/package-name")

package.json
1
2
3
4
5
{
"dependencies": {
"@scope/package_name": "^1.0.0"
}
}

Using deprecated packages

建议按推荐更新。

A deprecation message doesn’t always mean the package or version is unusable; it may mean the package is unmaintained and will no longer be updated by the publisher.
弃用消息并不总是意味着软件包或版本不可用,它可能意味着该软件包无人维护,并且将不再被发布者更新。

Uninstalling packages and dependencies

To remove a package from the dependencies in package.json, use the --save flag. Include the scope if the package is scoped.

  • Uninstalling local packages

    • Unscoped package npm uninstall <package_name>
    • Scoped package npm uninstall --save <@scope/package_name>
    • 例:npm uninstall --save lodash

      If you installed a package as a “devDependency” (i.e. with –save-dev), use –save-dev to uninstall it: npm uninstall –save-dev package_name

  • 确认已卸载
    • 检查 node_modules 目录下不再有被卸载包的目录

Uninstalling global packages

To uninstall an unscoped global package, on the command line, use the uninstall command with the -g flag. Include the scope if the package is scoped.

  • Unscoped package npm uninstall -g <package_name>
  • Scoped package npm uninstall -g <@scope/package_name>
  • 举例:npm uninstall -g jshint

ECMAScript 2015(ES6:ECMAScript 6)

ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。也就是说,ES6就是ES2015。

不需要立刻记住每一个方法,可以后期再参考。
https://babeljs.io/docs/en/learn

REPL 在线交互式解释器 https://babeljs.io/repl

Babel 是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。大家可以选择自己习惯的工具来使用使用Babel,具体过程可直接在Babel官网查看。

最常用的ES6特性

1
2
3
4
5
6
7
let, const, 
class, extends, super,
arrow functions, template string,
destructuring,
default,
rest arguments,
Promise
let

var 类似,都是用来声明变量的,例

1
2
3
4
5
6
7
8
9
var name = 'zach'

while (true) {
var name = 'obama'
console.log(name) //obama
break
}

console.log(name) //obama

因为ES5只有全局作用域和函数作用域,没有块级作用域,两次输出都是obama,内层变量覆盖了外层变量。
为此,let 实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。

1
2
3
4
5
6
7
8
9
let name = 'zach'

while (true) {
let name = 'obama'
console.log(name) //obama
break
}

console.log(name) //zach

另外一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量:

1
2
3
4
5
6
7
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10

注意 a[6] 代表

1
2
3
ƒ () {
console.log(i);
}

变量 i 是 var 声明的,在全局范围内都有效。所以每一次循环,新的 i 值都会覆盖旧值,导致a6 最后输出的是最后一轮的 i 的值。
使用 let 可以避免这个问题:

1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

一个更常见的例子,了解下如果不用ES6,而用闭包如何解决这个问题。

1
2
3
4
5
6
var clickBoxs = document.querySelectorAll('.clickBox')
for (var i = 0; i < clickBoxs.length; i++){
clickBoxs[i].onclick = function(){
console.log(i)
}
}

本来希望的是点击不同的clickBox,显示不同的i,但事实是无论我们点击哪个clickBox,输出的都是5,用闭包搞定它:

1
2
3
4
5
6
7
8
9
10
function iteratorFactory(i){
var onclick = function(e){
console.log(i)
}
return onclick;
}
var clickBoxs = document.querySelectorAll('.clickBox')
for (var i = 0; i < clickBoxs.length; i++){
clickBoxs[i].onclick = iteratorFactory(i)
}
const

常量

class, extends, super

这三个特性涉及了ES5中最令人头疼的的几个部分:原型、构造函数,继承……你还在为它们复杂难懂的语法而烦恼吗?你还在为指针到底指向哪里而纠结万分吗?

有了ES6我们不再烦恼!

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
console.log(this.type + ' says ' + say)
}
}

let animal = new Animal()
animal.says('hello') //animal says hello

class Cat extends Animal {
constructor(){
super()
this.type = 'cat'
}
}

let cat = new Cat()
cat.says('hello') //cat says hello
arrow function

ES6最最常用的一个新特性。相当于其它语言中的lambda

1
2
function(i){ return i + 1; } //ES5
(i) => i + 1 //ES6

又例:

1
2
3
4
5
6
function(x, y) { 
x++;
y--;
return x + y;
} //ES5
(x, y) => {x++; y--; return x+y} //ES6

除了看上去更简洁以外,arrow function还有一项超级无敌的功能!
长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout(function(){
console.log(this.type + ' says ' + say)
}, 1000)
}
}

var animal = new Animal()
animal.says('hi') //undefined says hi

运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。所以为了让它能够正确的运行,传统的解决方法有两种:

  1. 将this传给self,再用self来指代this

    1
    2
    3
    4
    5
    says(say){
    var self = this;
    setTimeout(function(){
    console.log(self.type + ' says ' + say)
    }, 1000)
  2. bind(this)

    1
    2
    3
    4
    says(say){
    setTimeout(function(){
    console.log(self.type + ' says ' + say)
    }.bind(this), 1000)

现在有了箭头函数:

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout( () => {
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi') //animal says hi

使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

template string

当我们要插入大段的html内容到文档中时,传统的写法非常麻烦,所以之前我们通常会引用一些模板工具库,比如mustache等。

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!
`);

用反引号(\)来标识起始,用${}`来引用变量,而且所有的空格和缩进都会被保留在输出之中。

destructuring

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

数组的解构赋值,简单的例子

1
2
3
4
5
6
const arr = [1, 2, 3, 4];
let [a, b, c, d] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(d); // 4

数组的解构赋值,更复杂的匹配规则:

1
2
3
const arr = ["a", "b", ["c", "d", ["e", "f", "g"]]];
const [, , [, , [, , g]]] = arr;
console.log(g); // g

数组的解构赋值,扩展运算符…

1
2
3
4
5
6
7
8
9
10
const arr1 = [1, 2, 3];
const arr2 = ["a", "b", "c"];
const arr3 = [...arr1, ...arr2];
console.log(arr3); // [1, 2, 3, "a", "b", "c"]

const arr4 = [1, 2, 3, 4, 5, 6];
const [a, b, ...c] = arr4;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3, 4, 5, 6

数组的解构赋值,默认值

1
2
3
4
5
6
7
// 只有当值是undefined的时候才会使用默认值,即使值是null也不会使用默认值
const arr = [1, undefined, undefined, null];
const [a, b = 2, c, d = "aaa"] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // undefined
console.log(d); // null

数组的解构赋值,交换变量

1
2
3
4
5
6
7
// 不需要中间变量
let a = 20;
let b = 10;

[a, b] = [b, a];
console.log(a); // 10
console.log(b); // 20

对象的解构赋值,一般的例子

1
2
3
4
5
6
7
8
9
// 使用对象的结构赋值是,属性名必须跟解构对象的属性名一致
// 因为对象与数组不同,对象是无序的,只能通过属性名来标识
const xiaoming = {
name: "小明",
age: 10
};
const { name, age } = xiaoming;
console.log(name); // 小明
console.log(age); // 10

对象的解构赋值,更复杂的结构条件

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
// 可以使用:重命名变量
const player = {
nickname: "感情的戏我没演技",
master: "东海龙王",
skill: [
{
skillName: "龙吟",
mp: "100",
time: 6000
},
{
skillName: "龙卷雨击",
mp: "400",
time: 3000
},
{
skillName: "龙腾",
mp: "900",
time: 60000
}
]
};

const { nickname } = player;
const { master } = player;
const {
skill: [skill1, { skillName }, { skillName: sklName }]
} = player;

console.log(skill1); // {skillName: "龙吟", mp: "100", time: 6000}
console.log(skillName); // 龙卷雨击
console.log(sklName); // 龙腾

对象的解构赋值,结合扩展运算符

1
2
3
4
5
6
7
8
const obj = {
saber: "阿尔托利亚",
archer: "卫宫",
lancer: "瑟坦达"
};
const { saber, ...oth } = obj;
console.log(saber); // 阿尔托利亚
console.log(oth); // {archer: "卫宫", lancer: "瑟坦达"}

对象的解构赋值,对已经申明了的变量进行对象的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let age;
const obj = {
name:'小明',
age:22
};
{ age } = obj; // 这样是会报错的,因为这里的{}被认为是一个块级作用域
({ age } = obj); // <= 解决办法
// 不过,最好还是在声明的同时进行解构赋值
```

对象的解构赋值,默认值

```js
// 默认值
let stu = {
name: "小红"
};
let { name, age = 24, hobby = ["学习"] } = stu;
console.log(name); // 小红
console.log(age); // 24
console.log(hobby); // ["学习"]

字符串的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const str = "I am the bone of my sword"; // 我是剑骨头
const [a, b, c, ...oth] = str;
console.log(a); // "I"
console.log(b); // " "
console.log(c); // "a"
console.log(oth); // ["m", " ", "t", "h", "e", " ", "b", "o", "n", "e", " ",
// "o", "f", " ", "m", "y", " ", "s", "w", "o", "r", "d"]

// 以下三个的结果是一样的
const [...spStr1] = str;
const spStr2 = str.split("");
const spStr3 = [...str];

console.log(spStr1); // ["I", " ", "a", "m", " ", "t", "h", "e", " ", "b", "o", "n",
// "e", " ", "o", "f", " ", "m", "y", " ", "s", "w", "o", "r", "d"]
console.log(spStr2); // ["I", " ", "a", "m", " ", "t", "h", "e", " ", "b", "o", "n",
// "e", " ", "o", "f", " ", "m", "y", " ", "s", "w", "o", "r", "d"]
console.log(spStr3); // ["I", " ", "a", "m", " ", "t", "h", "e", " ", "b", "o", "n",
// "e", " ", "o", "f", " ", "m", "y", " ", "s", "w", "o", "r", "d"]
default

默认值
下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || ‘cat’来指定默认值。

1
2
3
4
5
function animal(type){
type = type || 'cat'
console.log(type)
}
animal()

ES6:

1
2
3
4
function animal(type = 'cat'){
console.log(type)
}
animal()
rest

Rest 参数接受函数的多余参数,组成一个数组,放在形参的最后,形式如下

1
2
3
function func(a, b, ...theArgs){
// ...
}

例:

1
2
3
4
function animals(...types){
console.log(types)
}
animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]

Rest参数和arguments对象的区别:rest参数只包括那些没有给出名称的参数,arguments包含所有参数;arguments 对象不是真正的数组,而rest 参数是数组实例,可以直接应用sort, map, forEach, pop等方法;arguments 对象拥有一些自己额外的功能。

Promise

在 JavaScript 中,所有代码都是单线程的,也就是同步执行的。而 Promise 为异步编程提供了一种解决方案。

更多 ES6 学习

阮一峰 ECMAScript 6