node.js知识点

目录

1、node技能图解

2、node 事件循环机制:

3、热启动:

4、exports和module.exports区别:

5、REST与RESTFul API 接口设计风格

6、nodejs中的async模块学习

7、js中==和===区别

8、读取文件

9、排序

 

10、面向过程与面向对象

11、什么是作用域链,什么是原型链

12、apply,call,bind的区别

13、JS中的六大数据类型

14、闭包

15、设计模式


1、node技能图解

 

2、node 事件循环机制:

 

事件循环

Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 I/O 任务,就从 线程池 中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。

当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 事件循环 (Event Loop)

 

3、热启动:

我们中的大部分人可能都是这样编写和调试代码的,在编辑器中保存代码,然后在控制台按CTRL+C键停止应用,随后通过向上键找到之前执行过的启动命令,按回车来重新启动应用。不过,通过使用下面这些工具可以自动完成应用的重启并简化开发流程:

 

 

npm i nodemon -g

 然后,在终端通过nodemon代替node命令来启动应用:

 

4、exports和module.exports区别:

**exports **返回的是模块函数

**module.exports **返回的是模块对象本身,返回的是一个类

1)exports 的属性和方法都可以被 module.exports 替代

2)但 exports 不能替代 module.exports 方法

 

5、REST与RESTFul API 接口设计风格

 

导入:

var methodOverride = require('method-override');

 

为什么需要method-override ?

这个需求主要来自前端的form。比如我们在后端提供一个针对HTTP PUT的API, 前端的数据提交时,我们自然希望FORM能够产生一个PUT请求。然而,浏览器的FORM只能GET或者POST。怎么办? 改变后端的API吗?如果这个API是别的服务商提供,我们无权更改呢?这时,我们就需要method-override来帮助我们。

 

REST即表述性状态传递

RESTFul API有哪些特点:

  1. 基于“资源”,数据也好、服务也好,在RESTFul设计里一切都是资源。
  2. 无状态。一次调用一般就会返回结果,不存在类似于“打开连接-访问数据-关闭连接”这种依赖于上一次调用的情况。
  3. URL中通常不出现动词,只有名词
  4. URL语义清晰、明确
  5. 使用HTTP的GET、POST、DELETE、PUT来表示对于资源的增删改查
  6. 使用JSON不使用XML

我举个例子:

网站:/get_user?id=3

RESTFul: GET /user/3 (GET是HTTP类型)

 

 

 

 

6、nodejs中的async模块学习

nodejs中的async模块学习 - 意外金喜 - CSDN博客

https://blog.csdn.net/zzwwjjdj1/article/details/51857959

 

async/await:

async 是让方法变成异步。

await 是等待异步方法执行完成。

await 是等待异步方法执行完成,可以获取异步方法里面的数据,但是必须得用在异步方法里面。

//await 阻塞的功能 ,把异步改成一个同步

解决循环嵌套:https://www.cnblogs.com/gouge/p/7097880.html

 

async/await执行顺序面试题:

 

 

 

7、js中==和===区别

简单来说: == 代表相同, ===代表严格相同

当进行双等号比较时候: 先检查两个操作数数据类型,如果相同, 则进行===比较, 如果不同, 则进行一次类型转换, 转换成相同类型后再进行比较

而===比较时, 如果类型不同,直接就是false

8、读取文件

 

 

var fs= require('fs');
var path = require('path');
      
 fs.readFile(path.join(__dirname, 'account.js'),{encoding:'utf-8'}, function (err,bytesRead) {
            if (err) throw err;
            var data=JSON.parse(bytesRead);
            console.log(data[0]);
            console.log("readFile success");
    });

 

9、排序

(1)、冒泡排序

 

 

//冒泡排序
function quickSort(arr){
    for(var i=0; i<arr.length-1; i++){
        console.log(i);
        for(var j=0; j<arr.length-i-1; j++){
            
            if(arr[j] > arr[j+1]){
                
                var oldVal = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = oldVal;
            }
        }
        
    }
}

(2)、二分法排序

 

//二分法排序
function quickSort(arr){
    if(arr.length<=1){
        return arr;
    }
    var nowNober = arr.splice( Math.floor(arr.length/2), 1 );  //取得数组中间的值
    var leftArr = [];
    var reightArr = [];
    for(var i=0; i<arr.length; i++){
        if(parseInt(arr[i])<=nowNober){
            leftArr.push(arr[i]);                              //把比中间值小的放一个数组
        }else{
            reightArr.push(arr[i]);                            //把比中间值大的放另一个数组
        }
    }
    return quickSort(leftArr).concat(nowNober,quickSort(reightArr))  //在对小数组 、 大数组 继续回调上面的分组方法,最后当数组长度只有一的时候,不再往下执行,把返回的单个数组层层拼装新数组,即最后返回的排序好的数组
}

floor():

 

10、面向过程与面向对象

 

区别:

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

 

优缺点:

面向过程

  优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 

  缺点:没有面向对象易维护、易复用、易扩展

 

面向对象

  优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 

  缺点:性能比面向过程低

 

11、什么是作用域链,什么是原型链

作用域是针对变量的,比如我们创建了一个函数,函数里面又包含了一个函数,那么现在就有三个作用域

  全局作用域==>函数1作用域==>函数2作用域

作用域的特点就是,先在自己的变量范围中查找,如果找不到,就会沿着作用域往上找。

如:

 

var a = 1;
function b(){
    var a = 2;
    function c(){
        var a = 3;
        console.log(a);
    }
    c();
}
b();

 

最后打印出来的是3,因为执行函数c()的时候它在自己的范围内找到了变量a所以就不会越上继续查找,如果在函数c()中没有找到则会继续向上找,一直会找到全局变量a,这个查找的过程就叫作用域链

 

也就是说函数c的作用域包括了函数b的作用域,当然也包括了全局作用域,但是函数b不能向函数c中查找变量,因为作用域只会向上查找。

 

变量的作用域:

函数内部可以直接读取全局变量

var n=999;

function f1(){
    console.log(n);
}
f1(); // 999

 

函数外部自然无法读取函数内的局部变量

function f1(){
    var n=999;
}
console.log(n); // error

 

什么是原型链呢?

 

原型链是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链。

Object ==> 构造函数1 ==> 构造函数2

 

function a(){};
a.prototype.name = "追梦子";
var b = new a();
console.log(b.name); //追梦子

我们经常会这么写:

function Person () {
    this.name = 'John';
}
var person = new Person();
Person.prototype.say = function() {
    console.log('Hello,' + this.name);
};
person.say();//Hello,John

 

原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。

 

可能我们也会这么写:

function Person () {
    this.name = 'John';
}
var person = new Person();
Person.prototype = {
    say: function() {
        console.log('Hello,' + this.name);
    }
};
person.say();//person.say is not a function

person.say方法没有找到,所以报错了。

因为如果想在原型对象上添加更多的属性和方法,我们不得不每次都要写一行Person.prototype,还不如提炼成一个Object来的直接。但是此例子巧就巧在构造实例对象操作是在添加原型方法之前,这样就会造成一个问题:

当var person = new Person()时,Person.prototype为:Person {}(当然了,内部还有constructor属性),即

Person.prototype指向一个空的对象{}。而对于实例person而言,其内部有一个原型链指针proto,该指针指向了

Person.prototype指向的对象,即{}。接下来重置了Person的原型对象,使其指向了另外一个对象,即

Object {say: function},

这时person.proto的指向还是没有变,它指向的{}对象里面是没有say方法的,因为报错。

从这个现象我们可以得出:

在js中,对象在调用一个方法时会首先在自身里寻找是否有该方法,若没有,则去原型链上去寻找,依次层层递进,这里的原型链就是实例对象的__proto__属性。

 

若想让上述例子成功运行,最简单有效的方法就是交换构造对象和重置原型对象的顺序,即:

function Person () {
    this.name = 'John';
}
Person.prototype = {
    say: function() {
        console.log('Hello,' + this.name);
    }
};
var person = new Person();
person.say();//Hello,John

其实,只需要明白原型对象的结构即可:

unction.prototype = {
    constructor : Function,
    __proto__ : parent prototype,
    some prototype properties: ...
};

 

 

总结:函数的原型对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针指向上一层的原型对象,而上一层的原型对象的结构依然类似,这样利用__proto__一直指向Object的原型对象上,而Object的原型对象用Object.prototype.__proto__ = null表示原型链的最顶端,如此变形成了javascript的原型链继承,同时也解释了为什么所有的javascript对象都具有Object的基本方法。

 

关于原型和原型链,讲的真好 - Meiko记录 - CSDN博客

https://blog.csdn.net/u010365819/article/details/81326349

https://www.jianshu.com/p/dee9f8b14771

12、apply,call,bind的区别

call()、apply()、bind() 都是用来重定义 this 这个对象的!

apply和call区别在于apply第二个参数是Array,而call是将一个个传入

bind() 方法会创建一个新函数。

例一:

 

 

obj.objAge;  // 17
obj.myFun()  // 小张年龄 undefined

 

例二:

shows()  // 盲僧 

 

比较一下这两者 this 的差别,第一个打印里面的 this 指向 obj,第二个全局声明的 shows() 函数 this 是 window ;

对比call 、bind 、 apply 传参情况下:

obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined

总结:

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'成都', ... ,'string' )。

apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['成都', ..., 'string' ])。

bind 除了返回是函数以外,它 的参数和 call 一样。

当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!

 

三者都可以把一个函数应用到其他对象上,注意不是自身对象.

apply,call是直接执行函数调用,

bind是绑定,执行需要再次调用.

apply和call的区别是 apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表(一个个传入)

代码演示:

function Person() {
}
Person.prototype.sayName() { alert(this.name); }
var obj = {name: 'michaelqin'}; // 注意这是一个普通对象,它不是Person的实例
1) apply
Person.prototype.sayName.apply(obj, [param1, param2, param3]);

2) call
Person.prototype.sayName.call(obj, param1, param2, param3);

3) bind
var sn = Person.prototype.sayName.bind(obj);
sn([param1, param2, param3]); // bind需要先绑定,再执行
sn(param1, param2, param3); // bind需要先绑定,再执行

 

 

 

 

 

 

 

 

用 法

  • apply用法

语法:apply([thisObj[,arg]])

apply方法的第一个参数是要绑定到this的对象,第二个参数是个类数组,表示原方法的参数列表。

看代码:

class Cat{
  constructor(){
    this.message = '喵~';
  }
  setColor(color){
    this.color = color;
    console.log(this.message,this.color);
  }
}
class Dog{
  constructor(){
    this.message = '汪~';
  }
  setColor(color){
    this.color = color;
    console.log(this.message,this.color);
  }
}
let cat = new Cat();
let dog = new Dog();
cat.setColor('white');  // '喵~' 'white'
dog.setColor('white');  // '汪~' 'white'
cat.setColor.apply(dog,['black']);  // '汪~' 'black'
dog.setColor.apply(cat,['yellow']);  // '喵~' 'yellow'
// apply方法只有两个参数,如果原方法有多个参数(setColor(color1,color2)),
// 则apply写法为cat.setColor.apply(dog,[color1,color2])
  • call用法

语法:call([thisObj[,param1,param2,...]])

call方法其实和apply方法一样,差别就在向原方法传值上,还拿上面的例子:

class Cat{
  constructor(){
    this.message = '喵~';
  }
  setColor(color){
    this.color = color;
    console.log(this.message,this.color);
  }
}
class Dog{
  constructor(){
    this.message = '汪~';
  }
  setColor(color){
    this.color = color;
    console.log(this.message,this.color);
  }
}
let cat = new Cat();
let dog = new Dog();
cat.setColor('white');  // '喵~' 'white'
dog.setColor('white');  // '汪~' 'white'
cat.setColor.call(dog,'black');  // '汪~' 'black'
dog.setColor.call(cat,'yellow');  // '喵~' 'yellow'
// call方法有多个参数,如果原方法有多个参数(setColor(color1,color2)),
// 则call写法为cat.setColor.call(dog,color1,color2)

 

 

  • bind用法

语法:bind([thisObj[,param1,param2,...]])

bind方法返回一个函数,称为绑定函数,当调用这个绑定函数时,它会以创建绑定函数时传入bind()的第一个参数绑定this,第二个以及后面的参数会作为原函数的实参传入。

看代码:

class Cat{
  constructor(){
    this.message = '喵~';
  }
  say(){
    console.log(this.message);
  }
}
class Dog{
  constructor(){
    this.message = '汪~';
  }
  say(){
    console.log(this.message);
  }
}
let cat = new Cat();
let dog = new Dog();
cat.say()  // '喵~'
dog.say()  // '汪~'
cat.say.bind(dog)()  // '汪~',因为bind方法返回一个函数,所以要加()让其执行
dog.say.bind(cat)()  // '喵~'

 

13、JS中的六大数据类型

 

1.Number类型

2.String类型

3.Boolean类型

4.Undefined类型

5.Null类型

6.Object类型

14、闭包

变量的作用域无非就是两种:全局变量和局部变量。

概念:

闭包就是能够读取其他函数内部变量的函数。

闭包的用途:

一个是前面提到的可以读取函数内部的变量

另一个就是让这些变量的值始终保持在内存中。

例1:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());  //The Window

 

 

 

例2:

function f1(){
    let n=999;
    nAdd=function(){n+=1}

    function f2(){
        console.log(n);
    }
    return f2;
}

let result=f1();

result(); // 999

nAdd();

result(); // 1000

在这段代码中, result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

最简单的闭包:

​
function A(){
    function B(){
        console.log('Hello Closure!');
    }
    return B;
}
var C = A();
C();// Hello Closure!

​

有了初步认识后,我们简单分析一下它和普通函数有什么不同,上面代码翻译成自然语言如下:

  • 定义普通函数 A
  • 在 A 中定义普通函数 B
  • 在 A 中返回 B
  • 执行 A,并把 A 的返回结果赋值给变量 C
  • 执行 C 

把这5步操作总结成一句话就是:

函数A的内部函数B被函数A外的一个变量 c 引用。

 

15、设计模式

待续。。。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值