1. 事件对象差异

原生JavaScript事件

// 原生事件对象
document.getElementById('btn').addEventListener('click', function(e) {
  console.log(e); // 原生Event对象
  console.log(e.target); // 真实DOM元素
  e.preventDefault(); // 原生方法
  e.stopPropagation(); // 原生方法
});

React合成事件

// React合成事件
function Button() {
  const handleClick = (e) => {
    console.log(e); // SyntheticEvent对象
    console.log(e.target); // 真实DOM元素
    console.log(e.currentTarget); // 真实DOM元素
    console.log(e.nativeEvent); // 可以访问原生事件
    e.preventDefault(); // React封装的方法
    e.stopPropagation(); // React封装的方法
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

2. 事件委托机制

原生JavaScript

// 需要给每个元素绑定事件
document.getElementById('btn1').addEventListener('click', handler1);
document.getElementById('btn2').addEventListener('click', handler2);
document.getElementById('btn3').addEventListener('click', handler3);

React事件委托

// React在根节点统一管理所有事件
// React 17之前:事件委托到document
// React 17之后:事件委托到根容器
function App() {
  return (
    <div>
      <button onClick={handler1}>Button 1</button>
      <button onClick={handler2}>Button 2</button>
      <button onClick={handler3}>Button 3</button>
    </div>
  );
}
// 实际上只在根容器上绑定了一个事件监听器

3. 跨浏览器兼容性

原生JavaScript

// 需要处理浏览器兼容性
function addEvent(element, type, handler) {
  if (element.addEventListener) {
    element.addEventListener(type, handler, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + type, handler);
  } else {
    element['on' + type] = handler;
  }
}

React合成事件

// React自动处理兼容性
function Button() {
  const handleClick = (e) => {
    // 在所有浏览器中行为一致
    console.log(e.type); // 'click'
    console.log(e.bubbles); // true
  };
  
  return <button onClick={handleClick}>Click</button>;
}

4. 事件池化(React 16及之前)

React 16事件池化

function Button() {
  const handleClick = (e) => {
    console.log(e.type); // 'click'
    
    setTimeout(() => {
      console.log(e.type); // null(事件对象被回收)
      // 如果需要异步访问,需要调用persist()
    }, 100);
  };
  
  const handleClickPersist = (e) => {
    e.persist(); // 阻止事件池化
    setTimeout(() => {
      console.log(e.type); // 'click'(正常访问)
    }, 100);
  };
  
  return <button onClick={handleClick}>Click</button>;
}

React 17+取消事件池化

function Button() {
  const handleClick = (e) => {
    setTimeout(() => {
      console.log(e.type); // 'click'(可以正常访问)
      // 不再需要persist()
    }, 100);
  };
  
  return <button onClick={handleClick}>Click</button>;
}

5. 事件绑定方式

原生JavaScript

// 多种绑定方式
// 1. HTML内联
<button onclick="handleClick()">Click</button>
 
// 2. DOM属性
document.getElementById('btn').onclick = handleClick;
 
// 3. addEventListener
document.getElementById('btn').addEventListener('click', handleClick);

React事件绑定

// React只支持props方式绑定
function Button() {
  const handleClick = () => {
    console.log('clicked');
  };
  
  // 正确的绑定方式
  return <button onClick={handleClick}>Click</button>;
  
  // 错误的绑定方式(不推荐)
  // return <button onclick="handleClick()">Click</button>;
}

6. 事件执行顺序

混合使用时的执行顺序

function App() {
  useEffect(() => {
    // 原生事件绑定
    document.addEventListener('click', () => {
      console.log('1. document原生事件');
    });
    
    document.getElementById('container').addEventListener('click', () => {
      console.log('2. container原生事件');
    });
  }, []);
  
  const handleReactClick = () => {
    console.log('3. React合成事件');
  };
  
  return (
    <div id="container" onClick={handleReactClick}>
      <button>Click me</button>
    </div>
  );
}
 
// 点击按钮的执行顺序:
// 1. container原生事件
// 2. React合成事件  
// 3. document原生事件

7. 阻止事件传播的差异

原生事件阻止传播

document.getElementById('btn').addEventListener('click', (e) => {
  e.stopPropagation(); // 阻止原生事件冒泡
});

React合成事件阻止传播

function Button() {
  const handleClick = (e) => {
    e.stopPropagation(); // 只能阻止React合成事件冒泡
    // 无法阻止原生事件的传播
    
    // 如果要阻止原生事件,需要:
    e.nativeEvent.stopImmediatePropagation();
  };
  
  return <button onClick={handleClick}>Click</button>;
}

8. 性能差异

原生JavaScript

// 每个元素都有独立的事件监听器
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick); // 多个监听器
});

React事件委托

// 只在根容器有一个监听器
function ButtonList() {
  return (
    <div>
      {buttons.map(btn => (
        <button key={btn.id} onClick={handleClick}>
          {btn.text}
        </button>
      ))}
    </div>
  );
}
// 更少的内存占用,更好的性能

9. 访问原生事件

通过nativeEvent访问

function Button() {
  const handleClick = (e) => {
    // React合成事件
    console.log(e.type); // 'click'
    console.log(e.bubbles); // true
    
    // 访问原生事件
    const nativeEvent = e.nativeEvent;
    console.log(nativeEvent); // 原生Event对象
    console.log(nativeEvent.timeStamp); // 原生时间戳
  };
  
  return <button onClick={handleClick}>Click</button>;
}

10. 事件名称差异

命名规范不同

// 原生JavaScript事件名
element.addEventListener('click', handler);
element.addEventListener('mouseenter', handler);
element.addEventListener('keydown', handler);
 
// React合成事件名(驼峰命名)
<button onClick={handler} />
<div onMouseEnter={handler} />
<input onKeyDown={handler} />

总结对比表

特性原生JavaScript事件React合成事件
事件对象原生Event对象SyntheticEvent对象
浏览器兼容需要手动处理自动处理
事件绑定直接绑定到DOM元素事件委托到根容器
内存占用每个元素独立监听器统一管理,内存占用少
事件池化不存在React 16有,17+取消
命名规范小写驼峰命名
性能元素多时性能较差事件委托,性能更好

面试加分点

  1. 理解事件委托的优势:减少内存占用,提高性能
  2. 知道React 17的变化:事件委托从document改为根容器,取消事件池化
  3. 能解释执行顺序:原生事件 → React合成事件 → document原生事件
  4. 了解兼容性处理:React自动处理跨浏览器兼容性问题
  5. 掌握混合使用场景:知道何时需要使用nativeEvent

这些知识点展现了你对React事件系统的深入理解,是高级React开发者必备的知识。