There are serious advantages to non-member functions when dealing with operators.
Most operators are binary (take two arguments) and somewhat symmetrical, and with a member operator they only work if *this is the left-hand side argument.  So you need to use a non-member operator.
The friend pattern both gives the operator full access to the class (and, operators are usually intimate enough that this isn't harmful), and it makes it invisible outside of ADL.  In addition, there are significant advantages if it is a template class.
Invisible outside of ADL lets you do crazy stuff like this:
struct bob {
  template<class Lhs, class Rhs>
  friend bob operator+( Lhs&&, Rhs&& ) { /* implementation */ }
}
here our operator+ is a template that seemingly matches anything.  Except because it can only be found via ADL on bob, it will only be matched if it is used on at least one bob object.  This technique can let you pick up on rvalue/lvalue overloads, do SFINAE testing on properties of types, etc.
Another advantage with template types is that the operator ends up not being a template function.  look here:
template<class T>
struct X {};
template<class T>
bool operator==( X<T>, X<T> ) { return true; }
template<class T>
struct Y {
  friend bool operator==( Y, Y ) { return true; }
};
struct A {
  template<class T>
  operator X<T>() const { return {}; }
};
struct B {
  template<class T>
  operator Y<T>() const { return {}; }
};
int main() {
  A a;
  X<int> x;
  B b;
  Y<int> y;
  b == y; // <-- works!
  a == x; // <-- fails to compile!
}
X<T> has a template operator==, while Y<T> has a friend operator==.  The template version must pattern-match both arguments to be a X<T>, or it fails.  So when I pass in an X<T> and a type convertible to X<T>, it fails to compile, as pattern matching does not do user defined conversions.
On the other hand, Y<T>'s operator== is not a template function.  So when b == y is called, it is found (via ADL on y), then b is tested to see if it can convert to a y (it can be), and the call succeeds.
template operators with pattern matching are fragile.  You can see this problem in the standard library in a few points where an operator is overloaded in ways that prevent conversion from working.  Had the operator been declared a friend operator instead of a public free template operator, this problem can be avoided.