node.js学习笔记二

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:

  1. 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
...
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宫若石阅读 1,052评论 0 1
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    w_zhuan阅读 3,607评论 2 41
  • 1 Node.js模块的实现 之前在网上查阅了许多介绍Node.js的文章,可惜对于Node.js的模块机制大都着...
    zlx_2017阅读 1,218评论 0 1
  • 1 Node.js模块的实现# 之前在网上查阅了许多介绍Node.js的文章,可惜对于Node.js的模块机制大都...
    七寸知架构阅读 2,053评论 1 50
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    Myselfyan阅读 4,062评论 2 58