まずこの二つ、esc_htmlとesc_attrの挙動は、基本的に全く一緒です。
この二つのソースコードを見ると、一番最後が
return apply_filters( 'esc_html', $safe_text, $text );
と
return apply_filters( 'attribute_escape', $safe_text, $text );
というように、最後にapply_filtersで適用するフィルタが違います。
これらのフィルタはデフォルトでは何もしません。従って挙動は完全に同じです。
この二つは目的が違います。
esc_htmlは、テキストとして表示したい内容をエスケープします。
esc_attrは、HTMLのタグの属性値をエスケープします。
タグの属性値とは、
のcolor:red;の事を言います。
WordPressで普通にテンプレートを作っている限りでは、初心者にとってタグの属性値なんて大抵は直打ちで埋め込む物なので、何でわざわざエスケープしなければいけないかわからない(入力の時に気をつければ良いだけ)かもしれないのですが、ユーザーの入力を属性値にするときには特に必要になります。
例えば、こんなフォームを作ったら、出力するときに属性値をエスケープする必要があります。
もっと一般的な例では、このinputフィールドは、以下のように属性値を用いてデフォルトのテキストを指定できます。
<input type="text" value="最初から入力されているテキスト" >
例えばユーザー名や住所を入力させて、確認画面でそれらが入力されている状態にしたいようなインタフェースを作る時には、属性値にユーザー入力を指定することになるので、esc_attrを使うことになります。
参考資料
What is the difference between esc_html filter vs attribute_escape filter?