ES6:对象之间的回调函数

ES6: callback functions between objects

本文关键字:回调 函数 之间 对象 ES6      更新时间:2023-09-26

我正在构建一个页面,该页面将包含用户将要经过的各种"部分",每个部分都有自己的逻辑。例如:

    加载
  1. 部分:加载闪光灯。完成后,继续:
  2. 启动部分:介绍一些需要与之交互才能继续的 UI 元素。(假设有一个"解锁幻灯片")
  3. 视频首映:使用 Youtube 嵌入式 Javascript API 的自定义播放器。

由于其中一些部分有很多特定的逻辑,因此我将它们分成组件。几乎所有这些逻辑都是组件内部的,但偶尔,我想从组件 B 调用组件 A 中的函数。请参阅main.js和Splash的后几行.js

主.js

import $ from 'jquery';
import Loading from './components/Loading.js';
import Splash from './components/Splash.js';
import Premiere from './components/Premiere.js';
$(() => {
    Loading.init();
    Splash.init();
    Premiere.init();
    Splash.onUnlock = Premiere.playVideo;
});

加载.js:

const Loading = {
    init() {
        // watches for events, controls loading UI, etc.
    },
    // ... other functions for this section
}
export default Loading;

飞溅.js

const Splash = {
    init() {
        // watches for events, controls unlocking UX, etc.
    },
    // ... other functions for this section
    unlock() {
        // called when UX is completed
        this.onUnlock();
    }
}
export default Splash;

首映.js

const Premiere = {
    init() {
        this.video = $('#premiereVideo');
        // watches for events, binds API & player controls, etc.
    },
    // ... other functions for this section
    playVideo() {
        this.video.play()
    },
}
export default Premiere;

现在,我希望当this.onUnlock()Splash内被调用时,Premiere.playVideo()会被触发。但是,我得到一个错误:video没有定义 - 因为它正在寻找Splash.video,而不是Premiere.video.

据我了解,将对象或其属性分配给变量会创建对该属性的引用,而不是重复的实例。看来我没有正确理解这一点。

this.video.play()更改为Premiere.video.play()有效,但我觉得我仍然没有抓住重点。

怎么了?

(可能相关的子问题:将这些组件定义为类是否会受益,即使它们只会使用一次?

因此,

要回答您的问题,为什么没有定义视频是因为您正在尝试访问已更改上下文this

Premiere.playVideo.bind(Premiere)

绑定将确保在调用playVideo时,它是在premiere上下文中调用的,而不是在splash的上下文中调用的。这意味着premiere的上下文this.video

我用来验证的代码:

const Splash = {
    init() {
        // watches for events, controls unlocking UX, etc.
    },
    // ... other functions for this section
    unlock() {
        // called when UX is completed
        this.onUnlock();
    }
}
const Premiere = {
    init() {
        this.video = { 
            play() {
                console.log("playing");
            } 
        };
        // watches for events, binds API & player controls, etc.
    },
    // ... other functions for this section
    playVideo() {
        console.log(this);
        
        this.video.play()
    },
}
Premiere.init();
Splash.onUnlock = Premiere.playVideo.bind(Premiere);
console.log(Splash);
Splash.unlock();

然而,这种特殊的"架构"对我来说有点臭。您可以使用责任链模式。这是当前对象在完成工作后知道下一步要调用什么的地方。

class DoWork {
    constructor(nextWorkItem) {
        this.nextWorkItem = nextWorkItem;
    }
    
    doWork() {
        
    }
}
class LoadingComponentHandler extends DoWork {
    constructor(nextWorkItem) {
        super(nextWorkItem);
    }
    
    doWork() {
        // do what you need here
        console.log("Doing loading work")
        
        // at the end call
        this.nextWorkItem.doWork();
    }
}
class SplashComponentHandler extends DoWork {
    constructor(nextWorkItem) {
        super(nextWorkItem);
    }
    
    doWork() {
        // do what you need here
        console.log("Doing Splash Work")
        
        // at the end call
        this.nextWorkItem.doWork();
    }
}
class PremiereComponentHandler extends DoWork {
    constructor(nextWorkItem) {
        super(nextWorkItem);
    }
    
    doWork() {
        // do what you need here
        console.log("Doing Premiere Work")
        
        // at the end call
        this.nextWorkItem.doWork();
    }   
}
class FinishComponentHandler extends DoWork {
    constructor() {
        super(null);
    }
    
    doWork() {
        console.log("End of the line now");
    }
}
var finish = new FinishComponentHandler();
var premiere = new PremiereComponentHandler(finish);
var splash = new SplashComponentHandler(premiere);
var loading = new LoadingComponentHandler(splash);
loading.doWork();

FinishComponent是空对象模式的一部分,其实现执行 noop(无操作)。这有效地结束了责任线。当然你不需要FinishComponent,你可以不打电话给this.nextWorkItem.doWork(),链条就会到此结束。我把它放在那里,因为它更容易看到链条停止的地方

从最后四行可以看出,责任链很容易看出:

var finish = new FinishComponentHandler();
var premiere = new PremiereComponentHandler(finish);
var splash = new SplashComponentHandler(premiere);
var loading = new LoadingComponentHandler(splash);

加载组件将在splash对象上调用doWork,而对象又会在premiere对象上调用doWork,依此类推,第四。

此模式依赖于 DoWork 的继承, 是处理程序的接口类型。

这可能不是最好的实现,但您可以看到如何不必担心调用的最后一个东西,或者如何专门调用下一个。您只需将您希望接下来进入的对象传入构造函数,并确保在操作结束时调用。

我注意到你有

// watches for events, controls unlocking UX, etc.

doWork()函数可以执行绑定,将它们委托给处理此问题的适当组件。所以像SplashComponentHandler可以委托给SplashComponent.保持这些关注点的分离是一种很好的做法。

这如何解决您的问题

Splash.onUnlock = Premiere.playVideo.bind(Premiere);

首先,Splash.onUnlock没有实现,直到你给它一个。其次,你必须将上下文绑定到你的函数,因为它在不同的上下文下执行,这听起来并不好。

所以你可以想象SplashComponentHandler.doWork()

doWork() {
    var component = new SplashComponent();
    component.initialise(); // when this is finished we want to execute the premiere
 
    this.nextWorkItem.doWork();
}

PremiereComponentHandler.doWork()

doWork() {
    var component = new PremiereComponent();
    component.bind(); // makes sure there is a this.video.
    component.playVideo();
}

现在看到SplashComponentHandler现在不知道下一个处理程序,它只知道当它完成工作时,它需要调用下一个处理程序。

没有this绑定,因为doWork()已在PremiereComponentHandler或传递给SplashComponentHandler的任何处理程序的上下文中执行。

此外

从技术上讲,您不仅限于执行一个又一个处理程序。您可以创建一个执行许多其他处理程序的处理程序。每个执行的处理程序都将知道下一个处理程序,直到您停止调用。

另一个问题是,如果首映完成后会发生什么,之后Splash如何做其他事情?很简单,所以从前面的解耦场景来看,这是SplashComponentHandler.doWork()

doWork() {
    var component = new SplashComponent();
    component.initialise(); // when this is finished we want to execute the premiere
 
    this.nextWorkItem.doWork();
    // so when we get to this execution step 
    // the next work item (PremiereComponentHandler)
    // has finished executing. So now you can do something after that.
    component.destroy(); // cleanup after the component
    fetch('api/profile') // i don't know, what ever you want.
        .then(profileContent => {
            component.splashProfile = profileContent.toJson();;
        });
}

在使用Promise的最后一点,您可以使用 promise 使整个doWork()异步。只需返回this.nextWorkItem.doWork(),然后初始化步骤如下所示:

var finish = new FinishComponentHandler();
var premiere = new PremiereComponentHandler(finish);
var splash = new SplashComponentHandler(premiere);
var loading = new LoadingComponentHandler(splash);
loading
   .doWork()
   .then(() => {
       // here we have finished do work asynchronously.
   })
   .catch(() => {
       // there was an error executing a handler.
   });

让它全部使用Promises诀窍是确保您始终返回doWork()的承诺。