1、谈谈js中的变量类型有哪些?
答:值类型:字符串(string)、数值(number)、布尔值(boolean)、undefined、null、symbol(es6)
引用类型:对象(Object)、数组(Array)、函数(Function)
2、谈谈null和undefined的区别?
答:null是一个表示 "无" 的对象,转为数值时为 0;undefined是一个表示 "无" 的原始值,转为数值时为NaN。
3、谈谈值类型和引用类型的区别?
答:值类型:
- 1、占用空间固定,保存在栈内存中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。)
- 2、保存与复制的是值本身。
- 3、使用typeof检测数据的类型。
引用类型:
- 1、占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。)
- 2、保存与复制的是指向对象的一个指针
- 3、使用instanceof检测数据类型
- 4、使用new()方法构造出的对象是引用型
4、谈谈栈内存和堆内存的区别?
答:首先要明白数据结构中的栈和堆:
- 栈 :这是一种连续存储的数据结构,具有先进后出的性质。
- 堆 :是一种非连续的树形存储数据结构,每个节点都有一个值,整棵树是经过排序的。
其次,内存中的栈区和堆区:
- 栈内存 :由程序自动向操作系统申请分配以及回收,速度快,使用方便,但程序员无法控制。
- 堆内存 :程序员向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。分配的速度较慢,地址不连续,容易碎片化。此外,由程序员申请,同时也必须由程序员负责销毁,否则可能导致内存泄露。
5、谈谈对js作用域的认识。(可能会引出变量提升)
答:在早期的ES5中,js的作用域分为两种,分别为全局作用域和局部作用域。之后在ES6中添加了块级作用域,主要通过命令let和const来实现。
ES5只有全局作用域和函数作用域,没有块级作用域,会带来以下问题:
- 1.变量提升导致内层变量可能会覆盖外层变量。
- 2.用来计数的循环变量泄露为全局变量等等。
5、简要说明var、let、const的区别。
答:1.const定义的变量不可以修改(引用类型可以修改,但是引用始终不变,变得是在堆中的对象或者数组或者函数),而且必须初始化。
2.var定义的变量可以修改,如果不初始化会输出undefined,不会报错。
3.let是块级作用域,函数内部使用let定义后,对函数外部无影响。
6、js中==和===的区别。
答:简单来说: == 代表相同, === 代表严格相同(类型也必须相同)。
当进行双等号比较时候:先检查两个操作数数据类型,如果相同,则进行===比较,如果不同,则愿意为你进行一次类型转换,转换成相同类型后再进行比较,而===比较时,如果类型不同,直接就是false。
7、遍历一个json的方法。
答:1、for … in
2、Object.keys(obj).forEach(key)
3、Object.getOwnPropertyNames(obj).forEach(key)
4、Reflect.ownKeys(obj).forEach(key)
8、合并数组的方法。
答:1、for循环 + push()
2、concat()
3、ES6的扩展运算符…
当数组中有引用对象时,第2和3种是浅拷贝。
9、谈谈浅拷贝和深拷贝的区别。
答:浅拷贝:只是拷贝索引,不拷贝对象。原对象变化时,新对象也会随之变化。
深拷贝:拷贝对象。数组的JSON.parse(JSON.stringify(arr))
6、谈谈node中的require加载原理。
答:在node中,每一个js文件相当于一个module(这里node和前端是有区别的),它都会有其独自的变量空间,require相当于在自己的模块中,拿到其他模块的引用。
同一个模块被多个模块引用时,该模块的内容只执行一次,第二次加载时会直接在缓存中加载(require.cache)。
两个模块互相循环引用,并不会造成死循环。(一个模块获取另一个模块的全部引用,而一个模块只会获取另一个模块的未完成的副本)。
大概加载流程:
- 计算绝对路径
- 如果有缓存,取出缓存
- 是否为内置模块
- 生成模块实例,存入缓存
- 加载模块
- 输出模块的exports属性
8、谈谈module.exports和exports的区别。
答:exports只是module.exports的引用,如果module.exports被直接赋值,则exports的赋值不会起作用,例如module.exports='hanjc'。
但是如果是module.exports.name这种增加属性,则exports会起作用。
8、谈谈js中的this指向。
答:1、普通函数调用:这个情况没特殊意外,就是指向全局对象-window。
2、对象函数调用:这个相信不难理解,就是哪个函数调用,this指向哪里。
9、谈谈apply, bind, call这些方法,他们之间的区别。
答:call、apply、bind 都是用来重定义 this 这个对象的。
call、apply、bind 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔。apply 的所有参数都必须放在一个数组里面传进去。bind 除了返回是函数以外,它的参数和 call 一样。
13、Promise的几种状态。
答:promise有三种状态:pending状态(进行中)reslove状态(已成功)reject状态(已失败)
14、Promise.all的功能及实现。
答:Promise.all接受一个Promise数组,并行执行数组中的所有异步操作,返回结果集。数组里面的promise是并行执行的。
15、async/await相比于promise的优点。
答:1. 简洁(if判断,嵌套等等地方)。2.错误处理(直接用try/catch)。
16、async/await大致的实现原理。
答:async函数是Generator函数的语法糖,用function*定义,方法内用yeild标识,用return返回({value,done}),执行时用next进行下一个步骤。
co 模块:用于 Generator 函数的自动执行,返回一个Promise对象。
17、谈谈express中的中间件。
答:express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。
- 1、中间件结构app.use([path],function)
- 2、中间件分类:内置中间件(express.static唯一的内置中间件)、自定义中间件(自己实现的)、第三方中间件(npm导入的中间件,例如bodypaser等等)
- 3、浏览器向服务器发送一个请求后,服务器直接通过request.定位属性的方式得到通过request携带过去的数据(有用户输入的数据和浏览器本身的数据信息)。这中间就一定有一个函数将这些数据分类做了处理,已经处理好了,最后让request对象调用使用,对的,这个处理数据处理函数就是我们要说的中间件。
由此可见,中间件可以总结以下几点:
- 1、封装了一些处理一个完整事件的功能函数。
- 2、非内置的中间件需要通过安装后,require到文件就可以运行。
- 3、封装了一些或许复杂但肯定是通用的功能。
18、谈谈node单线程、异步IO与事件驱动的理解。
答:nodejs的运行机制:nodejs主线程主要起一个任务调度的作用。nodejs用一个主线程处理所有的请求,将I/O操作交由底下的线程池处理。在所有主线程任务执行完成后,主线程处理事件队列。所以在同步初始化代码执行完成后,nodejs会基于事件队列不停的做事件循环。事实上,nodejs运行环境 = 主线程(单线程,包括事件队列) + 线程池(工作线程池,执行其他工作-多线程)
node 的初始化:
- 初始化 node 环境。
- 执行输入代码。
- 执行 process.nextTick 回调。
- 执行 microtasks。(Promise.then)
事件循环
- 1、进入 timers 阶段 (定时器阶段:本阶段执行已经安排的 setTimeout() 和 setInterval() 的回调函数。)
- 检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
- 检查是否有 process.nextTick 任务,如果有,全部执行。
- 检查是否有microtask,如果有,全部执行。
- 退出该阶段。
- 2、进入pending IO callbacks阶段。(对某些系统操作(如 TCP 错误类型)执行回调)
- 检查是否pending的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
- 检查是否有 process.nextTick 任务,如果有,全部执行。
- 检查是否有microtask,如果有,全部执行。
- 退出该阶段。
- 3、进入 idle,prepare 阶段:
- 仅系统内部使用。
- 4、进入 poll 阶段(检索新的 I/O 事件;执行与 I/O 相关的回调,除了定时器和关闭的回调函数,其余都在这里)
- 首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。
- 第一种情况:
- 如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出该阶段。
- 第二种情况:
- 如果没有可用回调,执行下一步;
- 检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。
- 如果不存在尚未完成的回调,退出poll阶段。
- 5、进入 check 阶段。(setImmediate() 回调函数在这里执行)
- 如果有immediate回调,则执行所有immediate回调。
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出 check 阶段
- 6、进入 closing 阶段。(检测关闭的回调函数,例如 xx.on('close'))
- 如果有immediate回调,则执行所有immediate回调。
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出 closing 阶段
- 检查是否有活跃的 handles(定时器、IO等事件句柄)。
- 如果有,继续下一轮循环。
- 如果没有,结束事件循环,退出程序。
- 注: 在主线程执行完和事件循环总共7个阶段,每一个阶段执行完都会调用一遍process.nextTick回调,一遍microtaks(promise);
5、谈谈nodejs中进程间通讯的方式?
答:1、通过stdin / stdout传递json。
用spawn方法拿到子进程的handle,然后用通过stdin/stdout传递json。
2、原生IPC支持。
同样要有一方能够拿到另一方的handle才行。
3、sockets。
4、message queue。
19、node内存管理情况。
答:在node.js中,内存主要分为两个部分,堆内存和栈内存。
- 堆内存(heap):存放对象和闭包上下文,v8使用垃圾回收机制管理堆内存。
- 栈内存(stack):存放局部变量,栈内存的分配比较简单,当程序离开某作用域后,其栈指针下移(回退),整个作用域的局部变量都会出栈被回收。
24、新建Buffer会占用V8分配的内存吗。
答:不会,Buffer属于堆外内存,不是V8分配的。
25、Buffer.alloc和Buffer.allocUnsafe的区别。
答:Buffer.allocUnsafe创建的 Buffer 实例的底层内存是未初始化的。 新创建的 Buffer 的内容是未知的,可能包含敏感数据。使用 Buffer.alloc() 可以创建以零初始化的 Buffer 实例。
26、Buffer的内存分配机制。
答:为了高效的使用申请来的内存,Node采用了slab分配机制。slab是一种动态的内存管理机制。Node以8kb为界限来来区分Buffer为大对象还是小对象,如果是小于8kb就是小Buffer,大于8kb就是大Buffer。
例如第一次分配一个1024字节的Buffer,Buffer.alloc(1024),那么这次分配就会用到一个slab,接着如果继续Buffer.alloc(1024),那么上一次用的slab的空间还没有用完,因为总共是8kb,1024+1024 = 2048个字节,没有8kb,所以就继续用这个slab给Buffer分配空间。
如果超过8kb,那么直接用C++底层地宫的SlowBuffer来给Buffer对象提供空间。
27、Buffer乱码问题。
答:rs.setEncoding('utf8')
(我试过iconv解决,只不过电脑得提前安装一下)