NPM install -save 和 -save-dev 命令的区别
我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:
npm install moduleName # 安装模块到项目目录下
npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。
在项目中我们应该使用四个命令中的哪个呢?这个就要视情况而定了。下面对这四个命令进行对比,看完后你就不再这么问了。
npm install moduleName 命令
安装模块到项目node_modules目录下。
不会将模块依赖写入devDependencies或dependencies 节点。
运行 npm install 初始化项目时不会下载模块。
npm install -g moduleName 命令
安装模块到全局,不会在项目 node_modules 目录中保存模块包。
不会将模块依赖写入 devDependencies 或 dependencies 节点。
运行 npm install 初始化项目时不会下载模块。
npm install -save moduleName 命令
安装模块到项目node_modules目录下。
会将模块依赖写入dependencies 节点。
运行 npm install 初始化项目时,会将模块下载到项目目录下。
运行npm install –production或者注明NODE_ENV变量值为production时,会自动下载模块到node_modules目录中。
npm install -save-dev moduleName 命令
安装模块到项目node_modules目录下。
会将模块依赖写入devDependencies 节点。
运行 npm install 初始化项目时,会将模块下载到项目目录下。
运行npm install –production或者注明NODE_ENV变量值为production时,不会自动下载模块到node_modules目录中。
总结
devDependencies 节点下的模块是我们在开发时需要用的,比如项目中使用的 gulp ,压缩css、js的模块。这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装。像 express 这些模块是项目运行必备的,应该安装在 dependencies 节点下,所以我们应该使用 -save 的形式安装。
二、JavaScript 中的装箱与拆箱总结
今天学习了 Winter 老师的专栏 《重学前端》,看到了「拆箱」「装箱」的概念,看完之后不是很懂。于是去查了一波资料,总结记录一下。
首先简单说说什么是装箱和拆箱。
所谓装箱,就是把基本数据类型转换为对应的引用类型的过程。而拆箱与装箱相反,即把引用类型转换为基本的数据类型。
装箱
先来看看 JavaScript 的装箱过程,代码如下:
var p1 = 3.14159;
var p2 = p1.toFixed(2); // 3.14
上面的代码我定义了一个基本 Number 数据类型的变量 p1 ,它并不是一个对象,因此不应该有任何的方法,但是它任然可以调用 toFixed() 方法。这是因为 JavaScript 内部帮助我们实现了这个”装箱“的过程,实现的流程如下:
创建一个 Number 类型的实例
在实例上调用 toFixed 方法
销毁这个实例
因此,在 JavaScript 的机制中,每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。
拆箱
再来看看拆箱,上面说到,拆箱就是将引用类型转为基本的数据类型。一般会通过引用类型的 valueOf() 或者 toString() 方法实现。
valueOf:Returns the primitive value of the specified object.
即返回指定对象的原始类型
看下面的代码:
var num = new Number(3.14);
var str = new String('Hello World');
console.log(typeof num); // object
console.log(typeof str); // object
console.log(typeof num.valueOf()); // number
console.log(typeof str.valueOf()); // string
上面的代码分别实例化了一个 Number 和 String 对象,这两个都是引用类型,通过调用 valueOf() 方法返回了原始的基本类型,这就是拆箱的过程。
对象到 String 和 Number 的转换都遵循 “先拆箱再转换” 的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。
为了更深入的理解拆箱,举一个 Winter 老师专栏中提到的例子:
var o = {
valueOf: () => { console.log("valueOf"); return {} },
toString: () => { console.log("toString"); return {} }
}
o * 2
// valueOf
// toString
// TypeError
这里定义了一个对象 o,o 重写了 Object 中的 valueOf() 和 toString() 方法;两个方法都返回一个对象,当进行 o*2 这个运算的时候,可以看见先执行了 valueOf,接下来是 toString,最后抛出了一个 TypeError,这就说明了这个拆箱转换失败了。
在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。
var o = {
[Symbol.toPrimitive]: () => {console.log("toPrimitive"); return "hello" },
valueOf: () => { console.log("valueOf"); return {} },
toString: () => { console.log("toString"); return {} },
}
console.log(o + "")
// toPrimitive
// hello
完
三、说说 JavaScript 中的 async 和 await
ES7 引入了 async/await,这是 JavaScript 异步编程的一个比较大的改进。我们可以像写同步代码一些编写异步代码,避免了回调地狱,同时也代码也比 Promise 更易于阅读。
async 和 await 也是面试经常被问到的东西,之前一直只限于会用,并不太理解内部的实现原理。今天就来好好探究探究,JavaScript 中的 async 和 await 到底是怎么工作的。
async
async 就是异步的意思,在函数的定义前加上 async 关键字,表示这是一个异步函数,意味着该函数的执行不会阻塞后面代码的执行。
先来看一个简单的例子:
async function hello() {
console.log('hello');
}
hello();
console.log('world');
可以猜猜这段代码的输出顺序是什么?
输出的顺序如下:
hello
world
你可能要跳出来骂我了,前面不是说不会阻塞后面代码的执行么,为什么还是按顺序输出的?
先别急着骂,容我再进一步解释。
我们先一起来看看 hello 函数的返回值是什么。
async function hello() {
console.log('hello');
return 'hello';
}
console.log(hello());
console.log('world');
上面的代码输出如下:
hello
Promise {<resolved>: "hello"}
world
hello()函数并没有指定返回值,但是默认返回了一个 Promise 对象,而 Promise 的。
emm…看到这个 Promise,似乎有些清楚了。
也就是说,async 函数返回一个 Promise 对象,因此可以使用 then 方法添加回调函数。并且 async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
再修改一下上面的代码:
async function hello() {
console.log('hello before');
return 'hello after';
}
hello().then(value => console.log(value))
console.log('world');
上面的代码输出结果:
hello before
world
hello after
为什么 hello after 会在 world 后输出呢?这是因为 Promise 在 JavaScript 内部的运行机制,Promise 属于「微任务」,在当前脚本代码执行完后,立刻执行的,并没有参与事件循环。
既然返回值是 Promise,那么说明函数的执行也有对应的状态,如果函数正常执行,内部调用 Promise.resolve(),如果函数有错误,则调用 Promise.reject() 返回一个 Promise 对象。
继续修改上面的代码:
async function hello(flag) {
if (flag) {
return 'hello';
} else {
throw 'failed';
}
}
console.log(hello(true));
console.log(hello(false));
代码输出:
Promise { resolved }
Promise { rejected }
await
await 就是等待的意思,即等待请求或者资源。await 后面可以接任何普通表达式,但一般会在 await 后面放一个返回 promise 对象的表达式。
注意 :await 关键字只能放到 async 函数里面。
如果 await 等待的是一个 Promise,它就会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
看到「阻塞」两个字,心里咯噔一下。尼玛,这一阻塞,函数外的代码不会也暂停执行么。
这个你不用担心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,就像上文所描述的。它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
先来看下面的代码:
function delay(seconds) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(seconds)
}, seconds);
});
}
async function hello() {
console.log('hello');
let result = await delay(2000);
console.log(result);
}
hello();
console.log('world');
上面定义了一个 delay 函数,接受一个 seconds 参数。函数返回一个 Promise 对象,这个 Promise 对象传入的是一个定时器。
然后在 hello函数中去调用 delay 函数,指定 await 关键字。当代码执行到result 这一行是,会阻塞 2 秒钟。等 Promise 的 resolve,返回执行结果,然后赋值给 result,代码才开始继续执行,执行下一行的 console.log 语句。
输出结果如下:
hello
world
// 两秒后...
2000
这样我们就以同步代码的方式编写了一段异步请求的函数,如果遇到多个异步的代码,await 更能体现出写法上的可读性。
例如下面的代码:
async function hello() {
console.log('hello');
let result1 = await delay(2000);
let result2 = await delay(3000);
let result3 = await delay(4000);
console.log(result1+result2+result3);
}
如果用 Promise :
delay(2000).then(res=> {
console.log(res);
return delay(3000);
}).then(res => {
console.log(res);
return delay(4000);
}).then(res => {
console.log(res);
})
可以看到,使用 await 写异步代码真的非常的方便,而且代码的可读性非常的好,也没有回调地狱了,小伙伴们赶紧用起来。