I would argue that one of the great things about ggplot2 is that it's intuitive and users can spend more time making quick and easy visualisations like this and less time worrying about the code.
Base R solution
x1<-rnorm(1000,50,10) x2<-rnorm(1000,50,20) x3<-rnorm(1000,50,30) x4<-rnorm(1000,50,40) xMin=min(x1,x2,x3,x4) xMax=max(x1,x2,x3,x4) par(mfrow=c(4,1), oma = c(5,2,2,2), mar = c(3, 1, 1, 1)) hist(x1, xlim = c(xMin,xMax), col='#4281C3', main = NULL) hist(x2, xlim = c(xMin,xMax), col = '#008437', main = NULL) hist(x3, xlim = c(xMin,xMax), col= '#F49824', main = NULL) hist(x4, xlim = c(xMin,xMax), col = '#E42313', main = NULL) legend('bottom',legend=c("x1", "x2", "x3", 'x4'), fill=c("#4281C3", '#008437', "#F49824", '#E42313'), horiz = T, inset=c(0,-1), xpd=NA)

ggplot2 solution
library(tidyr) library(ggplot2) df1 <- gather(data.frame(x1, x2,x3,x4) , 'variable', 'value') ggplot(df1) + geom_histogram(aes(x = value, fill = variable), colour = '#000000') + facet_wrap(.~ variable, nrow = 4) + scale_fill_manual(values = c("#4281C3", '#008437', "#F49824", '#E42313'))

The key to ggplot2 is to have one column per variable, hence the use of tidyr::gather but we could equally have done:
df1 <- data.frame(value = c(x1,x2,x3,x4), variable = rep(c('x1','x2','x3','x4'), each =1000))