Vale lembrar que tem algumas diferenças entre eles.
Por exemplo, uma function declaration pode aparecer depois que esta é chamada, e funciona normalmente devido a um processo chamado hoisting (basicamente, é como se o interpretador "movesse" algumas declarações para o topo). Exemplo:
f(); // olá
function f() {
console.log('olá');
}
Apesar de estar chamando a função antes da sua declaração, o código acima funciona normalmente (devido ao hoisting, a declaração é avaliada antes).
Mas function expressions não sofrem os efeitos do hoisting, então assim já não funciona:
f(); // TypeError: f is not a function
var f = function () {
console.log('olá');
}
Mesmo se eu usasse uma named function expression (var f = function f() { etc
), também daria o mesmo erro. E com arrow function, também não funcionaria, pois para estas também não é feito o hoisting.
E tem ainda um outro tipo (se bem que podemos considerar um "sub-tipo"), que é o IIFE (Immediately Invoked Function Expression), quando você declara a função e já a executa:
(function (texto) {
console.log('olá', texto);
})('mundo'); // olá mundo
Por fim, sobre as diferenças entre function
e arrow functions, não deixe de ler a documentação.
Faltou a forma IIFE, obrigado por ressaltar isso e em relação ao hosting também, parabéns pelo comentário 👾