startup() is defined in _Widget, and is simply a "part of the lifecycle". It is the last step in the widget lifecycle, and is not required by all widgets. The most common case where it is absolutely required is when programatically creating layout widgets. It is used as a way to prevent redundant calculations when the children need sizing. For example, a BorderContainer.
var bc = new dijit.layout.BorderContainer({
style:"height:200px; width:200px"
});
// can call bc.startup() now, and the BorderContainer will resize
// all children each time a new child is added. Or, we can add all
// our children now, then trigger startup() and do it all at once.
var top = new dijit.layout.ContentPane({
region:"top", style:"height:100px"
}).placeAt(bc);
var mid = new dijit.layout.ContentPane({ region:"center" }).placeAt(bc);
// now BC will do the calculations, rather than in between each
// the above addChild/placeAt calls.
bc.startup();
Startup is automatically called by the parser in the case of parseOnLoad:true or manual execution. The parser delays calling startup() until all found child widgets have been appropriately instantiated.
dijit.Dialog is a strange case. startup() MUST be called on this widget as well.
var dialog = new dijit.Dialog({ title:"Hmm", href:"foo.html" });
dialog.startup();
dialog.show();
Most widgets do NOT require startup called, but in the cases where something inheriting from _Widget does not override the startup member, the call is essentially a no-op setting this._started = true; If you create your own startup() function, you should either call this.inherited(arguments) or simply set the _started trigger manually.
In Dojo 1.4, the lifecycle here has been adjusted slightly. Previously, a widget with widgetsInTemplate:true would call startup() on the child widgets BEFORE the startup() on the parent. In 1.4 the children's startup() will be called AFTER the parent startup(). This behavior is recursive for however many levels of nested widgets with widgetsInTemplate:true are instantiated.
It is always "safe" to call .startup(), though if you "know" (because it is a simple endpoint widget, or your own custom _Widget code) you can omit the call.