// 一般插件都是 new 类调用,一般情况下只能用 new 调用;// 插件封装时,简化构造函数中的调用步骤function Tab(options) { // 校验这个方法是否是被 new 调用的:如果用 new 调用 this 就是 Tab 的实例,如果不用 new 调用,this 就不是 Tab 的实例 if (!(this instanceof Tab)) { console.error('Tab is a constructor which should be called by new'); return; } // 1. 把配置对象保存实例上 // 在存之前做参数的合法性校验 if (!options || !options.el) { // 实参没有传递 options 或者 options 对象中没有 el 属性 console.error('缺少options或者options.el属性'); return; } this.options = options; this.init(); // 一般都会有这样一个 init 方法,表示这个插件从这里开始启动工作,一般类似这种名称有 _init、 main、 bootstrap....}// 1. 获取元素对象方法Tab.prototype.queryEle = function () { // this 当前 Tab 的实例,实例的 options 保存着 配置对象 // 获取容器元素 const EL_ID = this.options.el; // 从实例中取出 options.el let wrapper = document.querySelector(EL_ID); // 获取选项卡头,并且保存到实例上 this.headerList = wrapper.querySelectorAll('.header > li'); // 获取选项卡卡片,并且保存到实例上 this.cardList = wrapper.querySelectorAll('div');};// 2. 绑定事件的方法Tab.prototype.bindEvent = function () { // 给上一步获取来的选项卡头绑定事件 // ? 我们获取来元素对象存哪里了?上一步已经将选项卡头和卡片保存在实例中 let headerList = this.headerList; // 缓存 headerList // 事件函数this改变解决方案2:箭头函数 for (let i = 0; i < headerList.length; i++) { headerList[i].onclick = () => { // 这样写有一个问题,这个事件函数中的 this 是被点击的 li,我们希望 this 是 Tab 类的实例,因为只有 Tab 类的实例才能调用 clearClass 和 addClass 方法;箭头函数的 this 指向它声明时所在作用域(上级作用域)的 this // 1. 清除选中样式 this.clearClass(); // 2. 给被点击的添加选中样式 this.addClass(i); } }};// 3. 移除类名的方法Tab.prototype.clearClass = function () { // 移除所有的选项卡头、卡片的选中样式 // ? 这些元素对象在哪里存着?这些东西是 Tab 类实例的私有的属性 // let headerList = this.headerList; // let cardList = this.cardList; let { headerList, cardList } = this; // 和上面的写法等价 for (let i = 0; i < headerList.length; i++) { headerList[i].className = ''; cardList[i].className = ''; }};// 4. 添加类名的方法Tab.prototype.addClass = function (index) { // 给当前被点击的li以及对应的卡片增加选中样式 this.headerList[index].className = 'active'; this.cardList[index].className = 'active';};// 5. 简化构造函数中的调用Tab.prototype.init = function () { // 2. 执行获取元素对象 this.queryEle(); // this 就是 Tab 的实例 // 3. 绑定事件 this.bindEvent();};new Tab({ el: '#tab1'});