let fs = require('fs'); //引入fs api ;fs 是node的api: file system 文件读写
fs.readFile('./README.md','utf-8',function(err , data){
// 参数:文件名、 编码-不支持gbk 、callback-因为node的特点都是异步的
console.log(data); //这里打印的是文件里的内容
}); //读取文件, 默认查找的是 根目录,根路径;
目标
如果我们 读两个文件,希望最终拿到一个整体的结果 {name:‘lxz’,age:18}
let fs = require('fs');
let school= {};
fs.readFile('./name.txt','utf-8',function(err , data){
school.name = data;
});
fs.readFile('./age.txt','utf-8',function(err , data){
school.age= data;
});
console.log(school); //这里打印出来的是一个空对象,因为上面两个方法是异步的,还没有执行完给school赋值之前,这行已经打印了
上面两个方法fs.readFile()
都是异步的,同时写两个异步方法,谁先执行完不一定:看读取的速度 和 谁先完成
方案1:串行 -不推荐
第一个走完,再走第二个;这是最差的方法:本来两次读取文件时没有依赖关系的,但是却要第一个走完才能走第二个; 推荐使用 并行:并发 --这个最合理
let fs = require('fs'); //fs 是node的api: file system 文件读写
let school= {};
fs.readFile('./name.txt','utf-8',function(err , data){
// 参数:文件名、 编码-不支持gbk 、callback-因为node的特点都是异步的
school.name = data;
fs.readFile('./age.txt','utf-8',function(err , data){
school.age = data;
console.log(school);
});
}); //读取文件, 默认查找的是 根目录,根路径;
方案2:通过回调函数来解决-很麻烦
这样时并行的,但是也有缺点:1、产生的变量school,所有人都可以修改 2、写的方法out 要多次调用很麻烦,尤其要调用的地方多了
let fs = require('fs');
let school= {};
function out(){ //3. 定义一个out方法,每次执行完,判断下是不是school里的属性名 数量满足2个了,满足了再打印
if( Object.keys(school).length === 2 ){ //Object.keys()用于获得由 对象属性名 组成的 数组
console.log(school)
}
}
fs.readFile('./name.txt','utf-8',function(err , data){
school.name = data;
out(); //1.执行完这个方法后,调用out方法
});
fs.readFile('./age.txt','utf-8',function(err , data){
school.age = data;
out(); //2.执行完这个方法后,调用out方法
});
方案3:after函数的方式 - 相对好一些
用高阶函数:当函数A调用多少次后再去调用某个函数B,就是【javascript】高阶函数的应用 里面的 【4. 保存变量】部分
let fs = require('fs');
fs.readFile('./name.txt', 'utf-8', function (err, data) {
out('name', data); //1.执行完这个方法后,调用out方法
});
fs.readFile('./age.txt', 'utf-8', function (err, data) {
out('age', data); //2.执行完这个方法后,调用out方法
});
function after(times, callback) {
let school = {}; //在这里定义,在闭包里 永远不会被销毁
return function (key, value) { //这个就是 out
//school的定义不能写在这里,不然每次执行out school都被清空了;
school[key] = value; //往对象里村数据,操作的形参-函数定义部分
if (--times == 0) { //当执行王足够的次数,执行回调
callback(school); //执行回调,这里的参数是实参,实际要操作的 实参
}
}
}
let out = after(2, function (result) { // 几次后 , 执行什么操作 这里function 是定义回调,里面的参数是形参
console.log(result); //打印结果, 操作形参
})
方案4:发布订阅模式(发布 和 订阅)
发布(触发)-emit 、 订阅 -on
发布订阅模式 : 找一个中介数组,我们订阅方法的时候,把定义的方法存到 中间数组中;发布/触发 的时候直接遍历执行 中介数组中的方法即可
发布 和订阅之间没有任何关系,都是各自和中介数组进行联系
// 发布(触发)-emit 、 订阅 -on
// 找一个中介数组,我们订阅方法的时候,把定义的方法存到 中间数组中;发布/触发 的时候直接遍历执行 中介数组中的方法即可
// 发布 和订阅之间没有任何关系,都是各自和中介数组进行联系
let fs = require('fs');
let event = {
_arr: [], //这个数组不属于on 或者 emit ,所以发布和订阅之间没有任何关系
on(fn) { //fn 是每次执行event.on()方法时传的参数
this._arr.push(fn); //让on 绑定的fn 都存到_arr 数组里
},
emit() { //emit 发布(触发)的时候,让_arr数组里的方法依次执行
this._arr.forEach(function (ele) { //箭头函数 可以写成 fn => fn()
ele();
})
}
}
event.on(function () { //订阅:这个函数不会立即执行
console.log('ok');
})
event.on(function () { //而且 on 可以绑定多个函数,思路:就是把这些函数都存到数组里,到时候依次触发
if (Object.keys(school).length === 2) {
console.log(school);
}
})
let school = {};
fs.readFile('./name.txt', 'utf-8', function (err, data) {
school.name = data;
event.emit(); //触发:emit ,emit 方法里写的是触发 _arr里 订阅时候存的方法
// emit 这个执行的时候,会先执行emit里的方法,然后把on里的方法也执行了
})
fs.readFile('./age.txt', 'utf-8', function (err, data) {
school.age = data;
event.emit(); //触发:emit
// emit 这个执行的时候,会先执行emit里的方法,然后把on里的方法也执行了
})
问题:观察者模式 和 发布订阅模式 有什么区别?
观察者模式 是基于发布订阅模式的,而且 观察者模式下 发布 和 订阅 是有关系的。vue就是典型的观察者模式
- 发布订阅模式 是 订阅的时候 把方法 放到 数组中, 发布的时候 遍历 存方法的数组 然后执行数组里的方法;on 和 emit 是独立的
2.观察者模式 是 被观察者状态改变时, 在被观察者的方法里 通知观察者调用观察者的方法,也就是观察者的方法是 在 被观察者的 状态改变方法里 执行的。
引申:观察者模式
vue就是典型的观察者模式
vue 特点:1、监听数据的变化 2、数据变化后更新视图
// 被观察者
class Subject{ //类class //同学
constructor(name){
this.name = name;
this.state = '及格'; //被观察者实例 有一个属性state
this.observerArr = []; //1.写一个空数组,里面放观察者
}
attach(observer){ //2.写一个方法,想给被观察者绑定观察者就调用这个方法
this.observerArr.push(observer);
// console.log(this.name +'的观察者是'+observer+';'+this.name+'当前状态'+this.state);
}
setState(newState){ //被观察者实例有个方法,当state 改变时调用此方法
this.state = newState ; //冲刺你给state赋值
//本该在这里 状态修改时调用观察者的方法,但是由于 是多个观察者,所有我们先把观察者放在一个数组里,然后在这遍历观察者,然后调用其方法;
this.observerArr.forEach( o => o.updateState(this.name,newState))
// 被观察这数据状态改变时, 调用观察者的方法,并把修改后的状态传过去,然后在观察者的方法里写相应的逻辑
}
}
// observer 观察者
class Observer{ //老师
constructor(oname){
this.oname = oname;
}
updateState(sname,newState){ //观察者的一个方法
console.log(`${this.oname}反应:${sname}当前状态 ${newState}`);
}
}
let o1 = new Observer('李老师'); //注册观察者 new 一个被观察者实例,传一个实参:name,这个实参的形参写在构造器那里;
let o2 = new Observer('班主任');
let s = new Subject('王同学');
// let o2 = new Observer('你情敌')
s.attach(o1); //3. 给被观察者绑定观察者
s.attach(o2);
s.setState('不及格')
s.setState('及格')