I'm a D3 programmer and I must say that in all those years I never saw a single D3 code using the approach you claim is the only one you have seen, which is setting the onclick attribute:
selection.attr("onclick", "foo()")
It does work, but that's not idiomatic D3. In D3, we use the on method. However, the problem with your first snippet is that it's calling the function immediately:
d3.select("body")
.append("button")
.html("Click me")
.on("click", console.log("you clicked me"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
You can see that the console displays "you clicked me" without any click. So, for that to work, we should not call the function. Funnily enough, console.log accepts 3 arguments, have a look at what happens if we don't call console.log:
d3.select("body")
.append("button")
.html("Click me")
.on("click", console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Now console.log works when you click the button, because you passed it as the function reference. However, the first value is undefined because you didn't pass any string to console.log.
That being said, what you want is:
d3.select("body")
.append("button")
.html("Click me")
.on("click", () => {
console.log("you clicked me")
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Now you're passing a proper function to the click event, which will be called when you click.