Node不是一门语言,只是运行环境(runtime),只是提供了基于V8引擎的运行时(运行的环境,如console),也不是js,不包含BOm,Dom,,为了能实现服务端的功能新增了许多模块,如https,fs,这种模块能够帮助咱们系统级的操做API,,使用了事件驱动(回调),非阻塞i/o(异步)的模型,包管理器npmjavascript
进程是操做系统分配资源和调度任务的基本单位php
线程是创建在进程上的一次运行单位,一个进程上能够有多个线程css
咱们常常用的浏览器就是多进程的html
浏览器由用户界面 -> 浏览器引擎(在用户界面和呈现引擎之间传递信息,是浏览器的主进程) -> 渲染引擎前端
通常咱们先渲染css js和UI是互斥的,执行js空闲下来了就会执行cssvue
js是单线程的,不能同时操做domjava
其余线程node
单线程特色是节约内存,而且不须要切换执行上下文,不须要管理锁的问题webpack
浏览器中的 Event loopes6
webPack多线程 happyPack
worker.html
console.log(1)
let worker = new Worker('./worker.js') ; //h5内置
worker.postMessage(1000);//发消息
worker.onmessage = function(e){//收消息
console.log(e.data);
};
console.log(2)
复制代码
worker.js
onmessage = function (e) { //收消息
let sum = 200;
this.postMessage(sum)
}
//不能操做dom
复制代码
咱们知道js是单线程的,在stack栈里执行,浏览器能够调一些方法,,,如setTimeout属于另外一个线程里的了,会先进行同步代码,将另外一个进程放到一个callback que里面去
console.log()
setTimeout(function(){
console.log(1)
},1000)
setTimeout(function(){
console.log(1)
},5000)
复制代码
若是调用异步代码,不会立刻放到那个到队列里去,栈中的代码执行完以后,回去callback queue(队列中的代码) 里取 ,若是此时已经到1秒,就将函数去除,放到栈里去执行,好比ajax 在成功的时候会放到队列中,造成事件环循环,,event loop死循环 若是事件到达的时候栈中的代码没有执行完,就不会执行队列中的内容
node 里面有个应用,他会请求V8引擎 请求后回调用node API 调用完以后会跑到 LIBUV 库, 阻塞 多线程 实现异步 当事情处理完以后经过i/o机制calback,,而后将事件放到队列里去,而后经过node返回给应用
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port1.postMEessage('hello')
异步代码 vue 规定就是宏任务
port2.onMessage = function(e){
console.log(e.data)
}
复制代码
setTimeout(()=>{
Promise.resolve('123').then(data =>{
conssole.log(3)
})
})
setTimeout(()=>{
conssole.log(3)
})
//先会执行栈中的内容,再去执行微任务,微任务清空后在执行宏任务,而后循环,宏任务会在栈中执行
复制代码
vue 前身nextick怎么实现的 (兼容性有问题已经被废掉了)
let observe = new MutationOberver(function(){
console.log('dom全塞进去了')
})
//微任务
observe.observe(div,{child:true}
复制代码
vscode 安装插件 code runner 或者爬虫,, 默认状况只认js
经常使用命令
.help
.break Sometimes you get stuck, this gets you out
.clear Alias for .break
.editor Enter editor mode
.exit Exit the repl
.help Print this help message
.load Load JS from a file into the REPL session
.save Save all evaluated commands in this REPL session to a file
console.log(process.argv)
[ '/usr/local/bin/node',//node的exe文件目录
'/Users/myloveyunyun/Desktop/node/node.js' ]//执行的文件
复制代码
在cmd执行
咱们想拿到参数,
console.log(process.argv)
let args ={};
process.argv.slice(2).forEach((item,index)=>{
if(item.includes('--')){
args[item] = process.argv.slice(2)[index+1]
}
})
console.log(args)
复制代码
let url;
//怎么配置process.env.NODE_ENV
//mac export 设置环境变量 windows set 能够根据环境变量打出对应的url
if(process.env.NODE_ENV== 'deveopment'){
url = "http://localhost:3000/api"
}else{
url = "http://baidu.com"
}
console.log(url)
复制代码
console.log(process.cwd())
let fs = require('fs')
console.log(fs.readFileSync('./1.txt','utf8'))
[Running] node "/Users/myloveyunyun/Desktop/node/2018/node.js"
/Users/myloveyunyun/Desktop/node` `/Users/myloveyunyun/Desktop/node/2018
Error: ENOENT: no such file or directory, open './1.txt'
//这是由于当前读取的是根目录,咱们须要对目录做出拼接
复制代码
咱们能够
process.chdir('./2018')
console.log(process.cwd())
let fs = require('fs')
console.log(fs.readFileSync('./1.txt','utf8'))
复制代码
process.stdin.on('data',function(data){
console.log(data)
})
process.stdin.on('data',function(data){
process.stdout.write(data)
})
111
<Buffer 31 31 31 0a>
复制代码
这个方法是异步的 process.nextTick
微任务,比宏任务快 和promise.then
比也快 两个都是微任务,这里额nextTick和vue里面的nextTick不是一个东西,一个前端,一个服务端 浏览器不存在此方法 setTimeout 和 setImmediate 顺序是不固定的
setTimeout(function(){
console.log('setTimeout')
},0)
setImmediate(function(){
console.log('setImmediate')
},0)
[Done] exited with code=0 in 0.083 seconds
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setImmediate
setTimeout
[Done] exited with code=0 in 0.066 seconds
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setTimeout
setImmediate
复制代码
每一个步骤里面都有一个队列
上面的代码咱们只看1/4/5 先走1,可是全部代码都是异步的,node执行的时候,有准备的时间
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve('p').then(()=>{console.log('p')})
},0)
setTimeout(()=>{
console.log('setTimeout2')
},0)
//在浏览器里
setTimeout1
p
setTimeout2
//node里
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
setTimeout1
setTimeout2
p
setTimeout(()=>{
console.log('setTimeout1')
},0)
setTimeout(()=>{
console.log('setTimeout2')
},0)
//从当前栈切换到队列的瞬间执行微任务
Promise.resolve('p').then(()=>{console.log('p')})
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
p
setTimeout1
setTimeout2
setImmediate(()=>{
console.log('setImmediate1');
setTimeout(()=>{
console.log('setTimeout1')
},0)
})
setTimeout(()=>{
console.log('setTimeout2');
setImmediate(()=>{
console.log('setImmediate2');
})
},0)
//三种状况
//setTimeout2,setImmediate1,setImmediate2,setTimeout1
//setImmediate1,setTimeout2,setTimeout1,setImmediate2
//setImmediate1,setTimeout2,setImmediate2,setTimeout1
//nextTick 插孔执行,在setTimeout,setImmediate切换的时候执行,不是按方法,而是按队列区分.且里面不能写递归,容易死循环,让特定值在下一个队列执行,好处是优先级高于setTimeout
fs.readFile('./1.txt','utf8',()=>{
setImmediate(()=>{
console.log('setImmediate1');
})
setTimeout(()=>{
console.log('setTimeout2');
},0)
})
//由于fs在轮询里,setImmediate1,setTimeout2
复制代码
chrome://inspect
进入调试CMD 就近依赖, AMD依赖前置 eval 闭包 有关模块化的实现请参考js模块化
(function(exports,require,moudle,_dirname,_filename){
})()
复制代码
fs readFile path http
package.json
若是文件夹下没有index文件,就去这个文件查找npm init -y
初始化npm install name
node_modules
文件夹查找fs.accessSync('1.txt')//判断文件是否存在
复制代码
//resolve // 解析绝对路径,传多个参数能够拼接
//join // 解析相对路径,传多个参数能够拼接
let path = require('path');
console.log(path.resolve('./2.txt','a','b'))
console.log(path.join(__dirname,'./2.txt','a','b'))
console.log(__dirname)
console.log(__filename)
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
/Users/myloveyunyun/Desktop/node/2.txt/a/b
/Users/myloveyunyun/Desktop/node/201803/2.txt/a/b
/Users/myloveyunyun/Desktop/node/201803
/Users/myloveyunyun/Desktop/node/201803/node.js
console.log(path.extname('1.a.d.f')) // 后最
console.log(path.basename('1.a.s.f.g','.g')) //基础名字
console.log(path.sep)//环境变量分隔符
console.log(path.posix.sep)//mac下分隔符 /windows \
console.log(path.delimiter)//mac下; /windows:
[Running] node "/Users/myloveyunyun/Desktop/node/201803/node.js"
.f
1.a.s.f
/
;
复制代码
let vm = require('vm');
let a = 'sd'
vm.runInThisContext('console.log(a)') //沙箱
eval('console.log(a)'),会查找
ReferenceError: a is not defined
at evalmachine.<anonymous>:1:1
复制代码
let vm = require('vm');
let a = 'sd'
vm.runInThisContext('console.log(a)') //沙箱
eval('console.log(a)')//会查找
ReferenceError: a is not defined
at evalmachine.<anonymous>:1:1
复制代码
ConmonJS规范
下面咱们根据以上三个特色,考虑各类状况,一步步实现一个简单的CommenJS引入功能
首先咱们要引入所用到的模块
a. 读文件
b. 获取文件路径
c. 运行环境
d. 加载策略=>针对js/json/node文件
e. Module
f. 缓存
g. requier方法
let fs = require('fs');
let path = require('path');
let vm = require('vm');
function Module(p) {
this.id = p; // 当前模块的标识
this.exports = {}; // 每一个模块都有一个exports属性
this.loaded = false; // 这个模块默认没有加载完
}
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {},
'.json': function (Module) {},
'.node': 'xxx'
}
Module._cacheModule = {}// 根据的是绝对路径进行缓存的
function require(moduleId){//咱们将加载模块以参数形式传递过来
}
复制代码
以上是咱们读取模块必备的条件,下面咱们挨个增长内容, 2. 在require接收到路径的时候,咱们首先要对此路径作解析,假设咱们给个方法_resolveFileName(moduleId)
对路径做出解析
// 解析绝对路径的方法 返回一个绝对路径
Module._resolveFileName = function (moduleId) {
let p = path.resolve(moduleId);
// 没有后缀在加上后缀 若是传过来的有后缀就不用加了
if (!path.extname(moduleId)) {//extname是path内部方法,在这里用到的相似用法,清自行查阅,笔者就先很少作解释了
//keys将一个对象转成数组
let arr = Object.keys(Module._extensions);
//若是没有后蕞名称,由于只有三种状况,咱们挨个对比,在这里是有前后顺序的,假设传过来a,咱们会现识别a.js
for (let i = 0; i < arr.length; i++) {
let file = p + arr[i];
try {
fs.accessSync(file);//accessSync 同步断定分拣是否存在
return file;
} catch (e) {
console.log(e)
}
}
} else {
return p;//若是有后坠就直接查找此文件,无需匹配
}
}
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一个绝对路径
}
复制代码
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {},//这里的function指的是加载该类型文件的方法
'.json': function (Module) {},
'.node': 'xxx'
}
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一个绝对路径
if (Module._cacheModule[p]) {
// 模块存在,若是有直接把对象返回便可稍后补充
}
// 表示没有缓存就生成一个模块
let module = new Module(p);
// 加载模块
let content = module.load(p); // 加载模块
Module._cacheModule[p] = module;
module.exports = content; //最终以module.export导出
return module.exports
}
复制代码
在此过程当中 ,咱们生成一个模块,new了一个moudle
, 将路径传过去,还记得上面的代码,在new的过程当中,咱们给模块加了id,exports,load
,而后我加载此模块,而且将它添加到缓存中 load
方法是在实例上调用的,咱们将吧这个方法写在Module
的原型上
//在new以后就有了这些标识
function Module(p) {
this.id = p; // 当前模块的标识
this.exports = {}; // 每一个模块都有一个exports属性
this.loaded = false; // 这个模块默认没有加载完
}
Module.prototype.load = function (filepath) {
//判断加载的文件是json仍是node,仍是js
let ext = path.extname(filepath);
//根据文件类型添加方法
let content = Module._extensions[ext](this); //成功读取文件内容
//this指当前模块的实例 有id exports loaded
return content;
}
复制代码
//exports,require,module
Module.warpper = ['(function(exports,require,module){', '\n})'];
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {
let script = fs.readFileSync(module.id, 'utf8');
let fn = Module.warpper[0] + script + Module.warpper[1];
//咱们须要执行此文件,可是eval会在当前做用域向上查找,咱们只想在require以后执行此文件,所以这里使用沙漏限制环境
vm.runInThisContext(fn).call(Module.exports, Module.exports, req, module)
return module.exports;
},
'.json': function (Module) {
return JSON.parse(fs.readFileSync(module.id, 'utf8')); // 读取那个文件
},
'.node': 'xxx'
}
复制代码
这样咱们的基本功能就实现了
// 什么是commonjs规范
// 定义了如何导入模块 require
// 还定义了如何导出模块 module.exports 导出xxx
// 还定义了一个js就是一个模块
let fs = require('fs');
let path = require('path');
let vm = require('vm');
//解析绝对路径方法,返回绝对路径
//因为es6不支持静态属性,咱们暂时用es5实现
//全部加载策略
function Module(p) {
this.id = p; // 当前模块的标识
this.exports = {}; // 每一个模块都有一个exports属性
this.loaded = false; // 这个模块默认没有加载完
}
// 全部的加载策略
Module.warpper = ['(function(exports,require,module){', '\n})'];
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {
let script = fs.readFileSync(module.id, 'utf8');
let fn = Module.warpper[0] + script + Module.warpper[1];
vm.runInThisContext(fn).call(Module.exports, Module.exports, req, module)
return module.exports;
},
'.json': function (Module) {
return JSON.parse(fs.readFileSync(module.id, 'utf8')); // 读取那个文件
},
'.node': 'xxx'
}
//根据绝对路径进行缓存
Module._cacheModule = {};
Module.resolveFileName = function (ModuleId) {
let p = path.join(ModuleId);
//若是后最不存在则查找
if (!path.extname(ModuleId)) {
//keys将一个对象转成数组
let arr = Object.keys(Module._extensions);
for (let i = 0; i < arr.length; i++) {
let file = p + arr[i];
//模块加载的整个过程都是同步的
try {
fs.accessSync(file);
return file;
} catch (e) {
console.log(e)
}
}
} else {
return p;
}
}
Module.prototype.load = function (filepath) {
//判断加载的文件是json仍是node,仍是js
let ext = path.extname(filepath)
let content = Module._extensions[ext](this); //成功读取文件内容
//this指当前模块的实例 有id exports loaded
return content
}
function req(ModuleId) {
//解析绝对路径,
//且判断文件类型json,js,node
//得倒真实路径去查找缓存
let p = Module.resolveFileName(ModuleId); //p是一个绝对路径
if (Module._cacheModule[p]) {
// 模块不存在,若是有直接把exports对象返回便可
return Module._cacheModule[p].exports;
}
//没有缓存则建立一个模块
let moudule = new Module(p);
let content = module.load(p);
Module._cacheModule[p] = module;
module.exports = content;
return module.exports;
//加载模块
}
let a = req('./a.js');
req('./a.js');
console.log(a);
复制代码
对于文件模块,若是js,json,node都不存在其实会找文件夹下的package.json,若是json文件没有,会找该文件夹下的index,在这里咱们不作过多的阐述。 文件模块查找规则