Q:

Como utilizar o 'else if' corretamente?

Construi uma função chamada min que retorna o menor número que é passado em um de seus dois argumentos. Quando os números são iguais ela retorna null. Consegui fazer o código funcionar da seguinte maneira:
function min(a,b){
    if(a != b){
        if(a<b){
            return a; 
        }else{
            return b; 
        }
    }else{
        return null;
    }
}

Inicialmente eu fiz o código da seguinte forma:
function min(a, b){
    if(a<b){
        return a; 
    }else if(a = b){
        console.log('They are the same')
    }else{
        return b;
    }
}

Só que ele não funcionou, ele retorna corretamente somente o primeiro if(a<b), caso os argumentos não caiam nesse primeiro caso, a função retorna continuamente null.
Qual o erro de lógica estou cometendo nesse segundo caso?

A:

O = é o operador de atribuição, então a = b está na verdade setando o valor da variável a, que passa a ser o mesmo valor de b. Em seguida, esse valor é testado de acordo com essas regras (e como qualquer número diferente de zero é considerado "true", quaisquer valores de b que sejam diferentes de zero fará com que entre neste if - caso não tenha entrado no if anterior, claro).
Por exemplo:

let a;
let b = 10;

if (a = b) { // a recebe o valor de b
    console.log(a); // 10
}

a = b faz com que a receba o valor de b, que é 10.
Então no fundo estamos testando if (10), e como números diferentes de zero são considerados verdadeiros, ele entra no if e imprime o valor de a (no caso, 10).
Então para corrigir, você pode usar == (o operador de igualdade) ou === (operador de igualdade estrita). A diferença é que o primeiro pode fazer conversão de tipos entre os valores (por exemplo, '10' == 10 é considerado "true"), enquanto o segundo não faz essa conversão e verifica se os valores também são do mesmo tipo (ex: '10' === 10 é "false", mas 10 === 10 e 1 + 9 === 10 são "true"). Leia aqui para mais detalhes.

Dito isso, se a ideia é retornar o menor valor (ou null se ambos forem iguais), nem precisaria testar igualdade, daria para fazer assim:

function min(a, b) {
    if (a < b) return a;
    if (a > b) return b;
    return null;
}

console.log(min(1, 2)); // 1
console.log(min(10, 2)); // 2
console.log(min(2, 2)); // null

O return retorna o valor indicado e sai da função (ou seja, se chegou em um return, ela não executa o restante do código da função). Por isso nem precisa do else.
Se entrar no primeiro if é porque a é menor, então retorna ele e nem precisa executar o resto. Se não entrou nesse if, é porque a é maior ou igual a b.
Se entrar no segundo if é porque b é menor, então retorna ele e nem precisa executar o resto.
Se não entrar em nenhum dos if's, é porque a e b são iguais, então eu posso retornar null sem nem precisar de outro if ali (afinal, se não entrou em nenhum if é porque a não é menor e nem maior que b, então a única alternativa que sobrou é que são iguais, e por isso nem precisa testar isso de novo, é redundante e desnecessário).

Outro detalhe é que eu não gosto da ideia de uma função que, dependendo do caso, retorna algo ou imprime algo e não retorna nada (que é o que você tentou fazer no segundo código).
Eu prefiro que a função sempre retorne algo, e quem a chamou que faça o que quiser com o valor retornado (inclusive imprimir uma mensagem, se for o caso):

function min(a, b) {
    if (a < b) return a;
    if (a > b) return b;
    return null;
}

let result = min(2, 2);
if (result === null) {
    console.log('Não tem menor, os números são iguais');
} else {
    console.log(`O menor número é ${result}`);
}

Desta forma você separa bem as responsabilidades: a função só retorna o resultado (o menor dos números, ou null se forem iguais), e quem chamou a função toma alguma ação conforme o resultado - podendo inclusive imprimir a mensagem, e apesar de parecer que dá no mesmo, não dá, porque assim você não fica dependente da mensagem que estiver dentro da função e pode tomar a ação que quiser em cada caso (por exemplo, se eu não quiser imprimir nenhuma mensagem, ou fazer qualquer outra coisa, etc).

E só pra constar, quando uma função não encontra nenhum return, ela acaba retornando undefined. Ou seja, nesta função:

function min(a, b){
    if (a < b){
        return a; 
    } else if (a = b) {
        console.log('They are the same');
    } else {
        return b;
    }
}
console.log(min(2, 1)); // undefined

Como a é maior que b, ele não entra no primeiro if. Aí no else if acontece o que já foi explicado: a recebe o valor de b, e como este não é zero, é avaliado como "true". Então entra neste if, imprime a mensagem "They are the same" e como nenhum return é executado, a função retorna undefined (e não null, como você afirmou, pois eles não são a mesma coisa).

