There are many ways to do that as you have stated. It is like a variation on a theme in musical composition. All the variations you have shown is correct to my eyes. First one and last one are clever in a way too. But there comes a pay off with those cleverness. I would direct you to concepts like desructive
and non-desructive
procedures, or functions if you want to call them so. I know a lot of entries will tell you about changing the object or creating a new one, to me it is clearer and more basic to refer to them as I have denoted.
Some functions are defined to be non-desructive, and others are not. I would suggest the way to go with non-destrcutive approach. Although the non-desructive operations in an interpreted language like JavaScript hints the inherent resource hungriness and themselves must be mitigation for resource wasting, their use might not worth it provided the class of bugs they might introduce if not somethings kept in mind. unless huge amounts of data is moved around and the resources is not enough for non-d approach I would not use destructive functions.
Everyday use, I always use non-destructive procedures. I almost never need desructive methods like splice, push, pop, shift, unshift. Non-desructive methods like concat, slice are enough for not only me for almost every need.
For specific example above I would code like:
arr2 = arr2.concat(arr1); // or arr2 = [...arr2, ...arr1];
arr1 = []; // or arr1 = arr1.slice(arr1.length) but that seems a waste since you really want to empty the arr1.
In addition it would be better if you completely code in a way there is no need to those self assignations anyways. It is possible. If you just return from functions, and pass them arguments whenever you need anything from outside of the functions.