我正在构建一个包含各种各个部分的网页。用户将通过,每个都有自己的逻辑。例如:
由于其中一些部分有很多特定的逻辑,我将它们分成几个部分。几乎所有这些逻辑都是组件的内部逻辑,但偶尔,我想从组件B中调用组件A中的函数。请参阅后面的 main.js 和行。 Splash.js
main.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;
});
Loading.js:
const Loading = {
init() {
// watches for events, controls loading UI, etc.
},
// ... other functions for this section
}
export default Loading;
Splash.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;
Premiere.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()
有效,但我觉得我仍然忽略了这一点。
什么了?
(可能相关的子问题:我将这些组件定义为类会受益,即使它们只会被用一次吗?)
答案 0 :(得分:4)
因此,要回答您的问题,为什么未定义视频是因为您尝试访问已更改上下文的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
是Null对象模式的一部分,其实现是noop(无操作)。这有效地结束了责任。 当然你不需要FinishComponent
,你可以不打电话给this.nextWorkItem.doWork()
,链就会在那里结束。我有它,因为它更容易看到< em>其中链条停止。
从最后四行可以看出,责任链很容易看到:
var finish = new FinishComponentHandler();
var premiere = new PremiereComponentHandler(finish);
var splash = new SplashComponentHandler(premiere);
var loading = new LoadingComponentHandler(splash);
加载组件会调用doWork
对象上的splash
,doWork
对象将依次调用premiere
对象上的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
已在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
:
doWork()
关于使用this.nextWorkItem.doWork()
的最后一点,您可以使用promises使整个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()
让它全部使用response
的诀窍是确保始终从console.log(body)
返回承诺。