JS高级(一):类、原生tab栏切换、原型和原型链

一、类(之前ES6学过)

抽取了对象的公共部分,它泛指某一大类
对象特指某一个通过类实例化出来的东西

1.类的用法

ES6中的class其实就类似于ES5中的构造函数
比如下面两个写法是等价的:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.sum = function () {
  return this.x + this.y ;
};

var p = new Point(1, 2);
p.sum();

也就是说在类中,方法写出来直接相当于定义在了原型对象上

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  sum() {
    return this.x + this.y ;
  }
}

var p = new Point(1, 2);
p.sum();

定义sum()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加逗号会报错

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

2.类的继承

2.1 extends关键字

Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。

class Father {
}

class Son extends Father {
}

2.2 super关键字

1、如果父类和子类属性一致,子类的constructor可以省略
2、子类在继承父类的方法后,可以重写父类的方法,也可以添加其他方法,super必须在子类添加独有属性之前调用。
3、其实super也可以理解成调用父类构造函数,并改变父类构造函数中的this指向,让this指向子类的实例,这样的话父类中的属性都会被添加到子类上。
4、所有子类继承的父类且要赋值的属性都作为super的参数。
5、如果在constructor中调用super不传参,意思是在子类中添加父类的全部属性,但是不赋值(全都是undefined

(1)子类属性和父类一致,constructor可以省略

如果子类和父类在构造器函数中的属性一致,那么子类中的constructor完全可以省略

   class Person {
      constructor(name,age) {
          this.name = name;
          this.age = age;
      }

      speak() {
          console.log(this.name, this.age);
      }
   }

   class Student extends Person {
       say() {
           console.log(this.name + '11');
       }
   } 

   let zzy = new Student('zzy',18);
   console.log(zzy.age);  //18
   console.log(zzy.name);  //zzy
   zzy.speak();  //zzy 18
   zzy.say();  //zzy11
(2)子类要添加属性,必须要先写super

1、子类在继承父类的方法后,可以重写父类的方法,也可以添加其他方法,super必须在子类的this之前调用。
2、其实super也可以理解成改变父类构造函数中的this指向,让this指向子类的实例。
3、所有子类继承的父类的属性都写到super里,子类独有的要在super后添加
4、如果父类和子类属性一致,子类的constructor可以省略

  class Person {
       constructor(name,age) {
           this.name = name;
           this.age = age;
       }

       speak() {
           console.log(this.name, this.age);
       }
   }

   class Student extends Person {
       constructor(name,age,sex) {
           // super调用父类构造函数,也可以理解成让父类中this指向子类的实例
           super(name,age); //这里传name和age是子类实例传的,这样的话继承的父类的属性就可以有值。
           this.sex = sex; //添加子类独有的属性
       }
       //添加子类独有的方法
       say() {
           console.log(this.name + '666');
       }
       //重写父类的方法
       speak() {
           console.log(this); //Student
           console.log(this.name, this.age, this.sex);
       }
   } 

   let zzy = new Student('zzy',18,'男');
   console.log(zzy.name);  //zzy  super让父类中的this指向子类的实例了
   console.log(zzy.sex);  //男
   zzy.say();  //zzy666
   zzy.speak();  //zzy 18 男
(3)super关键字调用父类中的普通函数

通过super.函数名() 调用。
如果子类继承父类,子类和父类中有同名方法,那么子类中的实例调用时优先调用子类中的方法,如果子类没有该方法,才去调用父类的该方法。

class Father {
    say() {
        return '我是爸爸';
    }
}

class Son extends Father {
    say() {
        console.log(super.say() + '的儿子');
    }
}

let son = new Son();
son.say();  //我是爸爸的儿子   
//就近原则,如果子类有方法,优先调用子类,子类没有才去调用父类里的

3.类中this的指向问题

永远记住,谁调用函数,this就指向谁。不过下面这个如果我要调用公共方法,用箭头函数怎么解决this的问题?奇怪。其实可以用bind也行, this.btn.onclick = this.sing.bind(this)。这样调用的话,sing里面的this指向的就是constructor里面的this

<button>点击</button>
let that;
class Father {
    constructor(uname, uage) {
        that = this;
        this.name = uname;
        this.age = uage;
        // this.sing();
        this.btn = document.querySelector('button');
        this.btn.onclick = this.sing;  //这个this指向的是btn
        //谁调用方法,this就指向谁
    }

    sing() {
        console.log(this);  //<button>点击</button>
        console.log(that.name);  //zzy
    }
}

let f = new Father('zzy', 18);
f.sing();

二、面向对象tab栏

学完vue再来敲原生js感觉真的是非常麻烦,vue一行就能搞定的事情原生js要写几十行。
这个案例值得学习的地方就是老师很多代码的思路,比如利用逻辑短路去做优化。
可以去github下载:https://github.com/ForMyselfs/tabSwitch-by-nativeJs

var that;
class Tab {
    constructor(id) {
        that = this;
        //获取大的容器
        this.main = document.querySelector(id);

        //获取添加按钮
        this.add = this.main.querySelector('.tabadd');
        this.ul = this.main.querySelector('.fisrstnav ul');
        this.fsection = this.main.querySelector('.tabscon');

        this.init();
    }

    //获取标题和内容节点(由于更新dom需要重新获取,所以要单独定义个方法)
    updateNode() {
        this.lis = this.main.querySelectorAll('li');
        this.sections = this.main.querySelectorAll('section');

        //获取删除按钮
        this.remove = this.main.querySelectorAll('.iconfont');

        //获取修改元素
        this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
    }

    //0.初始化操作,让相关的元素绑定事件
    init() {
        this.updateNode(); //重新获取更新后的节点
        //0.1切换绑定事件
        for (let i = 0; i < this.lis.length; i++) {
            this.lis[i].index = i;  //给每个li添加index属性保存索引值
            this.lis[i].onclick = this.toggleTab;
            this.remove[i].onclick = this.removeTab; /* 删除绑定事件 */
            this.spans[i].ondblclick = this.editTab; //编辑功能
            this.sections[i].ondblclick = this.editTab; //编辑功能
        }
        //0.2添加绑定事件
        this.add.onclick = this.addTab;
    }

    //1.切换
    toggleTab() {
        //排他思想
        that.clearClass();
        this.className = 'liactive';
        that.sections[this.index].className = 'conactive';
    }

    clearClass() {
        for (let i = 0; i < this.lis.length; i++) {
            this.lis[i].className = '';
            this.sections[i].className = '';
        }
    }

    //2.添加
    addTab() {
        that.clearClass();
        let random = Math.random();
        //2.1创建元素
        let li = `<li class="liactive"><span>测试${random}</span><span class="iconfont icon-guanbi"></span></li>`
        let section = `<section class="conactive">测试${random}</section>`
        //2.2追加到父元素中
        that.ul.insertAdjacentHTML('beforeend', li);
        that.fsection.insertAdjacentHTML('beforeend', section);
        //2.3重新初始化,获取节点并绑定事件(不重新初始化,获取的还是旧DOM)
        that.init();
    }

    //3.删除
    removeTab(e) {
        e.stopPropagation();  //阻止事件冒泡到li的切换,只删除就行
        let index = this.parentNode.index;
        that.lis[index].remove();
        that.sections[index].remove();
        that.init(); //重新获取更新后的DOM,避免出现不必要的问题

        //删除之后让它前边那个卡高亮
        if(document.querySelector('.liactive')) return;
        index--;
        that.lis[index] && that.lis[index].click(); //逻辑短路,如果index=-1就不点击
    }

    //4.修改
    editTab() {
        let value = this.innerHTML;
        // 双击禁止选定文字
        window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
        //双击生成文本框
        this.innerHTML = `<input type="text" value=${value} >`;
        let input = this.children[0];
        input.select();
        //离开文本框时把文本框的值给span
        input.onblur = function() {
            this.parentNode.innerHTML = this.value; 
        }
        input.onkeyup = function(e) {
            if(e.key === 'Enter') {
                this.blur();
            }
        }
    }
}

new Tab('#tab');

三、原型和原型链

1.构造函数中的方法浪费内存的问题

每次new一个实例,就会单独开辟一个内存空间存放实例中的sing函数,如果我们new一百个实例,就要开辟100个内存空间来存放sing函数,这显然很low

function Star(name, age) {
    this.name = name;
    this.age = age;
    this.sing = function() {
        console.log('我会唱歌');
    }
}

let ldh = new Star('刘德华','18');
let zxy = new Star('张学友','17');
console.log(ldh.sing === zxy.sing);  //false…………

2.原型对象

1、原型对象prototype可以解决上面的问题,每一个构造函数都有一个prototype原型对象。
2、一般情况下,公共属性定义在构造函数中,公共方法定义在原型对象上。
3、我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的实例都可以共享这些方法,不需要再另外开辟内存空间。
4、可以看到,写到原型对象上后,ldh.sing === zxy.singtrue

 
function Star(name, age) {
    this.name = name;
    this.age = age;
}

Star.prototype.sing = function() {
    console.log('sing');
}

let ldh = new Star('刘德华','18');
let zxy = new Star('张学友','17');
console.log(ldh.sing === zxy.sing);  //true!!!

3.对象的__proto__属性

对象身上系统会自动添加__proto__属性,这个属性会自动指向构造函数的原型对象

console.log(ldh.__proto__ === Star.prototype)  //true

方法的查找规则:
1、首先看实例上有没有sing方法, 如果有就执行。
2、如果实例没有sing方法, 因为有__proto__的存在,就通过__proto__去构造函数的prototype身上去查找sing这个方法。
3、实际开发中不使用__proto__这个属性,它只是提供了一条路线指向prototype

在这里插入图片描述

4.原型中的constructor属性

对象的原型(__ proto__)和构造函数的原型对象(prototype)里都有一个属性constructor,我们成为构造函数,因为它指向构造函数本身(无限套娃,原型对象里有constructor,constructor里是构造函数,构造函数又有原型对象,原型对象又有constructor…………)。

在这里插入图片描述

有的时候我们需要手动让原型对象重新指向构造函数,比如我们要重写prototype对象

function Star(name, age) {
    this.name = name;
    this.age = age;
}

Star.prototype = {
    constructor: Star,  //这句话如果不加,就不知道实例指向谁了
    sing: function () {
        console.log('sing');
    }
}

let ldh = new Star('刘德华', '18');
console.log(ldh.__proto__);  //加constructor:{constructor: ƒ, sing: ƒ}
console.log(ldh.__proto__);  //不加constructor:{sing: ƒ}

不过这个地方有点没搞懂constructor有什么用。。。目前的理解是可以通过constructor找到当前对象指向谁。

三角关系:

在这里插入图片描述

5.原型链

理解下面这个图,原型链你就明白了
在这里插入图片描述

console.log(Star.prototype.__proto__ === Object.prototype);  //true
console.log(Object.prototype.__proto__);  //null

对象成员的查找规则:先从自身找,如果没有 => 通过__proto__向上找原型对象,如果没有 => 继续通过__proto__向上找原型对象,一直找到null => 如果都没就是undefined

console.log(ldh.dj);  //undefined

6.利用原型对象扩展内置对象的方法

比如我可以在数组对象中添加一个求和的方法,数组就可以直接调用

Array.prototype.sum = function() {
    let sum = 0;
    //这里面的this指向的是函数的调用者
    for (let i = 0; i < this.length; i++) {
        sum += this[i];
    }
    return sum;
}

let arr = [1,2,3,4,5];
console.log(arr.sum());  //15
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
实现原生 tab 切换三级的方法有很多种,以下是一种简单的实现方式: 1. HTML 结构 ```html <div class="tab-container"> <div class="tabs"> <ul> <li class="active"><a href="#tab1">Tab 1</a></li> <li><a href="#tab2">Tab 2</a></li> <li><a href="#tab3">Tab 3</a></li> </ul> </div> <div class="tab-content"> <div id="tab1" class="tab-pane active"> <ul class="sub-tabs"> <li class="active"><a href="#tab1-1">Sub Tab 1</a></li> <li><a href="#tab1-2">Sub Tab 2</a></li> <li><a href="#tab1-3">Sub Tab 3</a></li> </ul> <div class="sub-tab-content"> <div id="tab1-1" class="sub-tab-pane active">Tab 1, Sub Tab 1 Content</div> <div id="tab1-2" class="sub-tab-pane">Tab 1, Sub Tab 2 Content</div> <div id="tab1-3" class="sub-tab-pane">Tab 1, Sub Tab 3 Content</div> </div> </div> <div id="tab2" class="tab-pane"> <ul class="sub-tabs"> <li class="active"><a href="#tab2-1">Sub Tab 1</a></li> <li><a href="#tab2-2">Sub Tab 2</a></li> <li><a href="#tab2-3">Sub Tab 3</a></li> </ul> <div class="sub-tab-content"> <div id="tab2-1" class="sub-tab-pane active">Tab 2, Sub Tab 1 Content</div> <div id="tab2-2" class="sub-tab-pane">Tab 2, Sub Tab 2 Content</div> <div id="tab2-3" class="sub-tab-pane">Tab 2, Sub Tab 3 Content</div> </div> </div> <div id="tab3" class="tab-pane"> <ul class="sub-tabs"> <li class="active"><a href="#tab3-1">Sub Tab 1</a></li> <li><a href="#tab3-2">Sub Tab 2</a></li> <li><a href="#tab3-3">Sub Tab 3</a></li> </ul> <div class="sub-tab-content"> <div id="tab3-1" class="sub-tab-pane active">Tab 3, Sub Tab 1 Content</div> <div id="tab3-2" class="sub-tab-pane">Tab 3, Sub Tab 2 Content</div> <div id="tab3-3" class="sub-tab-pane">Tab 3, Sub Tab 3 Content</div> </div> </div> </div> </div> ``` 2. CSS 样式 ```css /* tab 样式 */ .tabs ul { list-style: none; margin: 0; padding: 0; } .tabs li { display: inline-block; margin: 0; padding: 0; } .tabs a { display: block; padding: 10px; background-color: #f2f2f2; color: #333; text-decoration: none; border-radius: 5px 5px 0 0; } .tabs a:hover { background-color: #ddd; } .tabs .active a { background-color: #ddd; } /* sub-tab 样式 */ .sub-tabs { list-style: none; margin: 0; padding: 0; border-bottom: 1px solid #ddd; } .sub-tabs li { display: inline-block; margin: 0; padding: 0; } .sub-tabs a { display: block; padding: 10px; background-color: #f2f2f2; color: #333; text-decoration: none; border-radius: 5px 5px 0 0; } .sub-tabs a:hover { background-color: #ddd; } .sub-tabs .active a { background-color: #ddd; } /* tab 内容样式 */ .tab-content .tab-pane { display: none; } .tab-content .tab-pane.active { display: block; } .sub-tab-content .sub-tab-pane { display: none; } .sub-tab-content .sub-tab-pane.active { display: block; } ``` 3. JavaScript 代码 ```javascript // 获取所有 tab 标签和内容 const tabs = document.querySelectorAll('.tabs li'); const tabContent = document.querySelectorAll('.tab-content .tab-pane'); // 获取所有 sub-tab 标签和内容 const subTabs = document.querySelectorAll('.sub-tabs li'); const subTabContent = document.querySelectorAll('.sub-tab-content .sub-tab-pane'); // 给每个 tab 标签绑定点击事件 tabs.forEach((tab, index) => { tab.addEventListener('click', () => { // 切换 active 样式 tabs.forEach((tab) => { tab.classList.remove('active'); }); tab.classList.add('active'); // 切换 tab 内容 tabContent.forEach((content) => { content.classList.remove('active'); }); tabContent[index].classList.add('active'); // 切换 sub-tab 样式和内容 subTabs.forEach((subTab, subIndex) => { subTab.classList.remove('active'); subTabContent[subIndex].classList.remove('active'); }); subTabs[0].classList.add('active'); subTabContent[index * 3].classList.add('active'); }); }); // 给每个 sub-tab 标签绑定点击事件 subTabs.forEach((subTab, index) => { subTab.addEventListener('click', () => { // 切换 active 样式 subTabs.forEach((subTab) => { subTab.classList.remove('active'); }); subTab.classList.add('active'); // 切换 sub-tab 内容 subTabContent.forEach((content) => { content.classList.remove('active'); }); subTabContent[index].classList.add('active'); }); }); ``` 以上代码实现了一个三级 tab 切换效果。其中,每个 tab 标签下有三个 sub-tab 标签,每个 sub-tab 标签对应一个内容区域。点击 tab 标签时,切换 tab 内容和 sub-tab 样式和内容;点击 sub-tab 标签时,切换 sub-tab 内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值