V8最大的特点就是单线程,一次只能运行一个任务
node存在大量异步操作
在异步操作中,无法通过try...catch...捕获异常
异步回调相比传统代码:
采用异步事件驱动
不容易阅读
不容易调试
不容易维护
什么是进程?
每一个正在运行的程序都可称之为进程
每一个应用程序至少有一个进程
多线程
如果是多线程:
node进程启动后会默认创建一个线程(主线程),用于执行代码, 对于比较花费
时间的操作,再给它创建一个线程去执行。主线程往 下走,碰到比较耗时的操作,再给这端代码创建一个线程去执行...
那么问题来了,线程越多越好吗?
答案是否定的
----多线程带来的问题:
--线程之间共享某些数据,同步某个状态都很麻烦
--创建线程花费时间
--能够创建的线程数量有限
--CPU切换线程时要切换上下文,非常耗时
node单线程执行过程:
node内部有一个事件队列,当碰到耗时操作比如文件操作,将该事件加入事件队列,主线程往下执行,又碰到耗时操作,再往事件队列中加入该事件,以此类推,
主线程执行完下面的代码(主线程空闲),去执行事件队列中的代码操作,执行完之后,执行它的回调,回调完成,执行跟它的回调相关的代码操作(如果有),如果这个操作是耗时的比如文件操作(如果是不耗时的,即非阻塞的,则不用交出去,直接自己执行了就好),将该操作加入事件队列,主线程空闲时,执行后来的这个事件队列中的事件,执行完该事件,执行它的回调...
对于事件队列,不管是主线程开始碰到的事件还是回调函数中的事件,都是执行到
(碰到)这部分代码的时候,将其加入事件队列的队尾,而事件队列中的事件的执行顺序是按照队列的顺序执行的
注意,本质上node程序的处理还是多线程的,node主线程负责处理源程序事件队
列中的非阻塞的操作,而阻塞的操作,则交给线程池中的线程去处理,线程池中的
线程帮助处理完这个事件,利用回调函数将结果送给主线程。总之,node本身主
线程主要做调度工作(和非阻塞操作)。
线程池中有一些已经创建好的线程,供node主线程调用
非阻塞的好处:
--提高代码响应效率
--充分利用单核CPU的优势
--改善I/O的不可预测带来的问题
但是目前市面上大多是多核CPU,大多通过硬件虚拟化将多核虚拟成单核
在node中,启用严格模式‘use strict’
V8对ES6支持情况分为三种情况:不支持、直接支持、严格模式下支持
比如let 就需要启用严格模式,或者用其他方式做些转换
chrome浏览器在打开网页请求的时候,会自动请求项目根目录下面的favicon.ico
图标
node采用CommonJS规范,模块和文件是一一对应关系,加载一个模块,实际上就是加载对应的一个模块文件。
CommonJS
CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,从
而填补了这个空白。它的终极目标是提供一个类似Python,Ruby和Java标 准库。
这样的话,开发者可以使用CommonJS API编写应用程序,然后这些应用可以运行
在不同的JavaScript解释器和不同的主机环境中。
在兼容CommonJS的系统中,你可以使用 JavaScript程序开发:
--服务器端JavaScript应用程序
--命令行工具
--图形界面应用程序
--混合应用程序(如,Titanium或Adobe AIR)
Node.js采用了这个规范,根据CommonJS规范,一个单独的文件就是一个模块。
加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的
exports对象。
//sum.js
exports.sum = function(){...做加操作..};
//calculate.js
var math = require('sum');
exports.add = function(n){
return math.sum(val,n);
};
--------------------
ES6中箭头函数与普通函数this的区别
普通函数中的this:
- this总是代表它的直接调用者, 例如 obj.func ,那么func中的this就是obj
2.在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window
3.在严格模式下,没有直接调用者的函数中的this是 undefined
4.使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
箭头函数中的this
默认指向在定义它时,它所处的对象,而不是执行时的对象, 定义它的时候,可能环境是
window(即继承父级的this);
模块的分类
----文件模块,就是我们自己写的功能模块文件
自定义模块开发流程:
创建模块--------new calc.js(新建一个calc.js文件)
导出模块--------module.exports = {}
载入模块--------const calc = require('./calc.js')
使用模块--------calc.add(1, 2)
----核心模块,Node平台自带的一套基本功能模块,也称为Node平台的API
----第三方模块,社区或第三方个人开发好的功能模块,可以直接拿来用
模块中的全局成员
__dirname 获取当前脚本所在目录路径
__filename 获取当前脚本文件所在目录路径
console.log(__dirname);
console.log(__filename);
输出:
F:\fore-end\materials\Node.js\projs\proj2
F:\fore-end\materials\Node.js\projs\proj2\test.js
如果想要在test.js中读取上一个目录中的content.txt文件
const fs = require('fs');
//所有的文件操作路径都应该是绝对路径(物理路径)
fs.readFile(dirname + '/../content.txt', (error, content) => {
if (error) throw error;
console.log(content.toString());
});
每个模块内部都是私有空间
//test.js
const fs = require('./error.js')
console.log(a);
//error.js
let a;
//执行结果:
a is not defined
模块内部是一个独立的作用域,所以模块内部变量和方法不会污染全局,而在客户
端通过script标签引入JS文件,那么他们具有相同的作用域
node有一个module对象,我们打印一下这个对象
//test.js
const fs = require('./error.js')
module.exports = {
print: () => (console.log(1))
}
console.log(mudole);
//error.js
let a;
//执行test.js打印module对象
Module {
id: '.',
exports: { print: [Function: print] },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children:
[ Module {
id: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\error.js',
exports: {},
parent: [Circular],
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\error.js',
loaded: true,
children: [],
paths: [Object] } ],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
当每个js文件在执行或被require的时候,NodeJS其实创建了一个新的实例var module = new Module(),这个实例名叫module。
可以发现module对象有一个id属性,表示这个module是哪个module,点.表示当前modulo
parent和children属性用来表示当前module的parent和children模块是哪个模块
看到module对象还封装了一个exports对象,初始值是个空对象,module.export
其实是给Module实例中的exports对象中添加方法/属性。
exports对象
通常使用exports的时候,是这么用的:
exports.print = function(){console.log(12345)}
假设我有一个JS文件内容如下:
console.log(module); //你会看到Module中的exports为空对象{}
console.log(exports); //你会看到Module中的exports为空对象{}
module.exports = {
print : function(){console.log(12345)}
}
console.log(module); //你会看到Module中的exports对象有了print()方法
exports.name = '小白妹妹';
console.log(module); //你会看到Module中的exports对象不仅有了print()方法,
还有了name属性
由此也能看出,传说中的exports其实是module.exports的引用,你可以这么理解,NodeJS在你的代码之前悄悄的加了以下代码:
var module = new Module();
var exports = module.exports;
exports是module.exports的引用的意思就是两者指向同一个对象,当然也可以改变exports的指向,是她不再和module.exports指向同一个对象。
改变/设置对象的指向方法是module.exports = {对象} | exports = {对象},只要发
生了这个操作,就确定了指向哪个对象。当然,module.exports.属性/方法 |
exports.属性/方法,这样子添加属性或方法并不会改变指向。
你可以这样:
module.exports.name = 'hello';
exports.age = 10;
module.exports.print = function(){console.log(12345)};
如果只是使用.来添加属性和方法,module.exports和exports混用是完全可以的.
也可以这样:
module.exports = {
name : 'hello'
};
exports.age = 10;
module.exports.print = function(){console.log(12345)};
但不可以这样:
module.exports = {
name : 'hello'
};
exports = {age:10}; // exports现在是{age:10}这个对象的引用,不再是
module.exports的引用了
console.log(module); //你会看到Module的exports中只有name属性!!!
也不可以这样:
exports.age = 10;
console.log(module); //你会看到Module的exports中多了age属性
module.exports = {
name : 'hello'
};
console.log(module);
//执行结果
Module {
id: '.',
exports: { age: 10 },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children: [],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
Module {
id: '.',
exports: { name: 'hello' },
parent: null,
filename: 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\test.js',
loaded: false,
children: [],
paths:
[ 'F:\\fore-end\\materials\\Node.js\\projs\\proj2\\node_modules',
'F:\\fore-end\\materials\\Node.js\\projs\\node_modules',
'F:\\fore-end\\materials\\Node.js\\node_modules',
'F:\\fore-end\\materials\\node_modules',
'F:\\fore-end\\node_modules',
'F:\\node_modules' ] }
现在就很明白了,改变exports的指向后所添加的exports.xxx都是无效的。因为
require返回的只会是module.exports。
require使用规则
require加载文件可以省略扩展名
require不仅仅可以载入JS模块,也可以载入JSON对象(JSON文件,大部分用于读取配置信息)
----require的参数如果不以“./”或“/”开头,则表示加载的是一个默认的核心模块,比如require('fs')加载核心模块中的文件系统模块
一旦出现模块名重复,系统模块的优先级最高
自己写的模块只要放在node_modules文件夹下,就可以像node自己的核心模块一样,require不用写路径,直接写模块名来加载
模块的缓存
第一次加载某个模块的时候,node会缓存该模块,以后再加载该模块,就直接从
缓存取出该模块的module.exports属性
cache对象
cache对象里面有node执行后的所有缓存
删除缓存
Object.keys(require.cache).forEach((key) =>{
delete require.cache[key];
} )
当然,一般我们不需要手动清空node的缓存。
如果我们不去手动删除缓存,又想每次require加载模块都执行模块中的代码,比如
下面这种
//time.js
console.log('模块的的代码');
module.exports = new Date();
//index.js
setInterval(() => {
let date = require('./time.js');
console.log(date.getTime());
}, 1000);
因为node的缓存,第一次载入模块,执行了time.js中的代码,而接下来执行载入
模块的时候,不会真的去调用time.js文件,而是从缓存中调入缓存的该模块(其实
是该模块上次执行结果的缓存)
执行结果
模块的的代码
1512556841717
//第一次执行结束,打印了‘模块的的代码’,创建了一个Date对象,并将这些结
果存入缓存
1512556841717
1512556841717
1512556841717
1512556841717
1512556841717
1512556841717
...
我们该如何处理这个问题呢?
很简单,反正都是冲缓存中取出这个模块,如果缓存的是一个方法,那么每次从缓
冲中取出的都是这个方法,取出方法之后都去执行一下,不就是每次都重新执行了
一下方法中的代码吗,所以将我们的代码放在一个方法中并导出,修改如下
//time.js
module.exports = () => {
console.log('模块的的代码');
return new Date();//这个方法返回一个Date对象
}
//index.js
setInterval(() => {
let date = require('./error.js');
console.log(date().getTime());//date是个函数,执行一下,返回Date
对象
}, 1000);
执行结果
模块的的代码
1512557968297
模块的的代码
1512557969303
模块的的代码
1512557970304
模块的的代码
1512557971306
模块的的代码
1512557972308
模块的的代码
1512557973310
...
网友评论