Webpack+Babel迁移ES6项目:页面加载触发toggleMobileNavigation,点击事件失效
我正在用Webpack搭配Babel将项目迁移至ES6,当前代码如下:
entry.js
import Header from './components/header'; (function() { const header = new Header(); })();
header.js
export default class Header { constructor() { this.bodyTag = document.querySelector('body'); this.toggleNavigationButton = document.querySelector('#toggleNavigationButton'); this.toggleNavigationButton.addEventListener('click', this.toggleMobileNavigation()); } toggleMobileNavigation() { this.bodyTag.classList.toggle('CLICK'); } }
webpack.config.js
module.exports = { entry: "./js/entry.js", output: { filename: "./js/bundle.js" }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['env'] } } }, { test: /\.scss$/, use: [ { loader: "style-loader" }, { loader: "css-loader" }, { loader: "sass-loader" } ] } ] }, resolve: { extensions: ['*', '.js', '.jsx'], }, };
遇到的问题:页面加载时,toggleMobileNavigation方法会被自动触发,而点击按钮时该点击事件完全不起作用,请问我遗漏了什么?
这个问题其实是事件绑定里的两个常见小坑,我来给你拆解清楚:
1. 立即执行函数的错误传递
你在addEventListener里写的this.toggleMobileNavigation(),末尾的括号会让函数立即执行——也就是说,页面一加载这个方法就被调用了,而不是把它作为点击事件的回调函数传递进去。这就是为什么页面加载时方法自动触发,点击按钮反而没反应的核心原因。
解决第一步:去掉括号,直接传递函数引用
this.toggleNavigationButton.addEventListener('click', this.toggleMobileNavigation);
不过这时候你会发现新问题:点击按钮会报错,提示this.bodyTag是undefined。这就涉及到第二个关键点——this的指向问题。
2. 回调函数中this的指向偏移
当你把类的方法直接作为事件回调时,触发事件时this会指向触发事件的DOM元素(也就是按钮本身),而不是Header类的实例。所以this.bodyTag自然找不到对应的属性。
这里有三种常用的解决方式,你可以根据自己的习惯选一种:
方式一:用bind绑定this到类实例
在构造函数里提前绑定方法的this指向:
constructor() { this.bodyTag = document.querySelector('body'); this.toggleNavigationButton = document.querySelector('#toggleNavigationButton'); // 把this绑定到当前类实例 this.toggleMobileNavigation = this.toggleMobileNavigation.bind(this); this.toggleNavigationButton.addEventListener('click', this.toggleMobileNavigation); }
方式二:用箭头函数包裹回调
箭头函数会继承外层作用域的this,所以可以直接这样写:
this.toggleNavigationButton.addEventListener('click', () => this.toggleMobileNavigation());
方式三:用类字段定义箭头函数(ESNext语法)
如果你的Babel配置支持类字段(可以通过@babel/plugin-proposal-class-properties插件开启),可以直接把方法定义成箭头函数,这样this会始终指向类实例:
export default class Header { constructor() { this.bodyTag = document.querySelector('body'); this.toggleNavigationButton = document.querySelector('#toggleNavigationButton'); this.toggleNavigationButton.addEventListener('click', this.toggleMobileNavigation); } // 用箭头函数定义方法,自动绑定this toggleMobileNavigation = () => { this.bodyTag.classList.toggle('CLICK'); } }
选其中一种方式修改后,你的点击事件就能正常工作,页面加载时也不会自动触发方法了。
内容的提问来源于stack exchange,提问作者Axiol

