美文网首页
node.js学习笔记二

node.js学习笔记二

作者: 阡陌哥哥 | 来源:发表于2017-12-06 19:06 被阅读0次

    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
    ...
    

    相关文章

      网友评论

          本文标题:node.js学习笔记二

          本文链接:https://www.haomeiwen.com/subject/gamlixtx.html