I've run into this in numerous projects, and have a few rules we follow to avoid this kind of problem:
In general, all of the little things that reduce the chances of merge conflicts in commonly changed are a great help (these only prevent the easy to resolve kind of conflicts though):
* Keep requires() in one place and in alphabetical order, so if two people add a new require to the same file, it is unlikely to conflict or be added twice
* Always leave trailing commas in multi-line array and object definitions so adds/removes/edits only touch a single line
* Avoid var statements declaring multiple variables so any new/removed/edited variables only touch a single line
As for the larger problems you're referring to, these are more of an issue in Javascript compared to other kinds of code because of a couple reasons, one being the lack of static type checking - any refactoring where you rename a variable or object member at the same time someone else added code that uses that member is near impossible to detect (without something like TypeScript on top of JS), and even worse the code will usually just continue to work without error, since accessing a member that does not exist simply returns undefined. Another reason is that slightly changing a function, perhaps to require one more asynchronous call to get required data, often changes the indentation level of the entire body of the function, which soundly defeats the default automatic merging in Git (e.g. if someone else modified one of those lines). To avoid this second situation, keeping things in small subfuctions helps a lot - if you're making this kind of change, and can refactor the function body into a sub-function which happens to have the same indentation level as the original code, it usually merges perfectly, since it (correctly) looks to Git like you have not changed the body of the function significantly.
In general dealing with large-scale code modifications or refactoring is where the big problems come up though. The things that seem to have helped there are:
* Don't do it! Sure, the system you're looking at could really use a refactoring and it's spread out throughout your codebase, but (especially if you've got a product currently live in production) refactors like that in Javascript are risky business, chances are high you'll miss something and not notice until far later (I've lost count of, since switching to Javascript, how many times we've collectively asked "how did this ever work?")
* Don't get into these situations - follow the example of the NPM ecosystem we love so much - lots of small modules with very small, highly tested interfaces - if the scope of each sub-system is small enough that only one developer on your team possibly needs to touch it at any time, there will be no merge conflicts
* Effectively go into code lock-down relating to a system - if you absolutely need to make modifications to a far-reaching system that will likely cause merge conflicts, plan it out - have someone figure out what roughly what changes will be made, notify the rest of the team that on this date at this time they'll be starting the change, and that no one should commit any changes to the systems that interact with this change until the change is done, or otherwise they need to wait until after the change is done and check their own merges (of, hopefully, just their small change) very carefully. In order to be efficient, this often means doing things in two stages - instead of just making modifications to the system you may need to make a completely new version (or at least a new API that looks like what you want the new version to be, even if it's calling back to the guts of the old one), get that tested and functional, then do the lockdown where you switch everything to use the new version, get that refactoring committed, and then back in your own branch go about changing the underlying guts.
Hope this helps!
Jimb Esser