目录
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有哪些特点:
- 基于“资源”,数据也好、服务也好,在RESTFul设计里一切都是资源。
- 无状态。一次调用一般就会返回结果,不存在类似于“打开连接-访问数据-关闭连接”这种依赖于上一次调用的情况。
- URL中通常不出现动词,只有名词
- URL语义清晰、明确
- 使用HTTP的GET、POST、DELETE、PUT来表示对于资源的增删改查
- 使用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、设计模式
待续。。。