前端转载。

本文详细介绍了npm install的各种用法,包括普通安装、全局安装、保存依赖和保存开发依赖的区别。同时,文章阐述了JavaScript中的装箱和拆箱概念,解释了基本类型和引用类型之间的转换过程。最后,讨论了ES7引入的async/await异步编程特性,解释了它们的工作原理,以及如何避免回调地狱,提高代码可读性。
摘要由CSDN通过智能技术生成

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 写异步代码真的非常的方便,而且代码的可读性非常的好,也没有回调地狱了,小伙伴们赶紧用起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值