Understanding behaviors I created a custom theme (a sub-theme of Radix base theme). 

I am loading a custom Javascript file (radix_sub.script.js) using the module libraries.yaml file like below:

style:
  version: VERSION
  css:
    theme:
      assets/css/radix_sub.style.css: {}
  js:
    '//cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js': { type: external }
    '//stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js': { type: external }
    assets/js/radix_sub.script.js: {}
  dependencies:
    - core/drupal
    - core/jquery
    - core/jquery.once
    - core/drupalSettings


In that file, I want to register some JavaScript events (Bootstrap modal).

(function ($, Drupal) {

  Drupal.behaviors.portfolio = {

    attach: function (context) {

      console.log('CONTEXT', context);

      $('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL')).each(function () {
        $(this).on('shown.bs.modal', function () {
          console.log('SHOWN BS MODAL EVENT TRIGGERED');
        });
      });

      $('#portfolioModal', context).once('bootstrap-modal-hidden', console.log('*** REGISTER HIDDEN.BS.MODAL')).each(function () {
        $(this).on('hidden.bs.modal', function () {
          console.log('HIDDEN BS MODAL EVENT TRIGGERED');
        });
      });
    }
  }
}(jQuery, Drupal));


It works. I see both SHOWN BS MODAL EVENT TRIGGERED and HIDDEN BS MODAL EVENT TRIGGERED logs when I show/hide the modal.

First, am I doing that the correct way?

This is what my developer console looks like when I load my page.


  


This happens only when I am logged in (as admin). When I am anon, I see the REGISTER logs only once but here my code seems to be executed 11 times.

Is this normal behavior? And/or why is it doing that?
The answer lies here:

$('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL'))


The code that comes after the call to $.once() will only be executed once. However, $.once() can and will be executed multiple times. That's why you are seeing your calls to the console being called multiple times. If you change this:

$('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL')).each(function () {
  // Do some stuff.
});


To this:

$('#portfolioModal', context).once('bootstrap-modal-shown').each(function () {
  console.log('*** REGISTER SHOWN.BS.MODAL');
  // Do some stuff.
});


You will see that your call to the console is only shown a single time.
Note that you also will not use $.on() here, as the Drupal behaviors API replaces any necessity of it. Each time Drupal.behaviors are executed, a context argument is passed to it. This will be either the full DOM, or any HTML being newly inserted into the DOM. So where in normal jQuery you might do this:

$(".some_selector").on(someCallbackFunction);


In Drupal, you would do this:

Drupal.behaviors.someArbitraryKey = {
  attach: function(context) {
    $(context).find(".some_selector").once("some-arbitrary-key").each(someCallbackFunction);
  }
};


This is the Drupal way of achieving the same goals as using $.on() in normal jQuery, while tying in with the Drupal APIs.

---- Edit ----

Original answer below, which turned out to not answer the question. Left for posterity's sake, and because it actually adds on well to the end of the above answer.

Drupal behaviors are called on page load. It is also called upon AJAX load. You need to wrap code in $.once() if it should only be executed a single time:

function someElementListener(context) {
  // Call to $.once():
  $(context).find(".some_selector").once("arbitrary-key").each(function () {
    // Code here will only be executed a single time on
    // each .some_selector element found
    $(this).click(function() {alert("clicked");});
  });
}

Drupal.behaviors.someModule = {
  attach: function (context) {
    someElementListener(context);
  }
};


If you wrap your code in $.once(), your console calls will only be logged a single time.