【javascript】高阶函数2 - 异步编程 初

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就是典型的观察者模式

  1. 发布订阅模式 是 订阅的时候 把方法 放到 数组中, 发布的时候 遍历 存方法的数组 然后执行数组里的方法;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('及格')

发布了57 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Eva3288/article/details/104147642
今日推荐