JavaScript从入门到放弃 - ES6中的对象和类

重点讲解Tab栏切换、增、删、改

  • 1. 面向过程与面向对象
  • 2.ES6 中的对象与类
    • 2.1 对象
    • 2.2 类
      • 2.2.1 创建类
        • 2.2.1.1 语法
        • 2.2.1.2 实例
      • 2.2.2 类创建添加属性和方法
      • 2.2.3 类的继承
        • 2.2.3.1 语法
        • 2.2.3.2 实例
        • 2.2.3.3 注意事项
  • 3. 面向对象案例
    • 3.1 面向对象版tab栏切换
      • 3.1.1 案例准备
        • 3.1.1.1抽象对象:Tab对象
        • 3.1.1.2 创建(Tab)类
        • 3.1.1.3 获取元素并调用初始化函数绑定事件
      • 3.1.2 切换功能实现
      • 3.1.3 添加功能的实现
      • 3.1.4 删除功能模块
      • 3.1.5 编辑功能模块

1. 面向过程与面向对象

  • 面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用;
  • 面向对象:是把事务分解成为一个个对象,然后由对象之间分工与合作。

面向对象的思维特点

  1. 抽取(抽象)对象的共用的属性和行为组织(封闭)成一个(模板);
  2. 对类进行实例化,获取类的对象(不断产生新的对象)。

面向对象编程首先考虑的是有哪些对象,按照面向对象的思维特点,不断创建对象、使用对象、指挥对象做事情。

面向过程与面向对象对比:

面向过程 面向对象
优点 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点 不易维护、不易复用、不易扩展 性能比面向过程低

2.ES6 中的对象与类

2.1 对象

  • 现实生活中
    万物皆对象。对象是一个具体的事物,看得见、摸得着的实物;
  • 在JavaScript中
    对象是一组无序的相关属性和方法的集合,所有的事物都是对象,如字符串、数值、数组、函数等

对象:由属性和方法组成:是一个无序键值对的集合,指的是一个具体的事物。

  • 属性:即事物的特征,在对象中用属性来表示(常用名词);
  • 方法:即事物的行为,在对象中用方法来表示(常用动词);

以下代码是对对象的复习:

//字面量创建对象
var ldh = {
    name: '刘德华',
    age: 18
}
console.log(ldh);

//构造函数创建对象
  function Star(name, age) {
    this.name = name;
    this.age = age;
 }
var ldh = new Star('刘德华', 18)//实例化对象
console.log(ldh);

在这里插入图片描述

2.2 类

ES6 中新增加了类的概念,可使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象。

类 class:泛指某一大类;
对象:特指某一事物(通过类实例化产生的)。

2.2.1 创建类

2.2.1.1 语法

步骤1: 使用class关键字

class name {
  // class body
}  

步骤2:使用定义的类创建实例 ,new关键字

var xx = new name();  

2.2.1.2 实例

1.)创建类 class:创建一个明星类

 class Star {
   // 类的共有属性放到 constructor 里面
   constructor(name, age) {
   this.name = name;
   this.age = age;
   }
 }

2.) 利用类创建对象 new

   var ldh = new Star('刘德华', 18);
   console.log(ldh);

以上代码运行结果::
在这里插入图片描述
我们可以看出和使用构造函数的结果是一样。

2.2.2 类创建添加属性和方法

1). 创建类 class 创建一个类

class Star {
    // 类的共有属性放到constructor里面,constructor是构造器或者构造函数
    constructor(uname, age) {
      this.uname = uname;
      this.age = age;
    }//------------------------------------------->注意,方法与方法之间不需要添加逗号
    sing(song) {
      console.log(this.uname + '唱' + song);
    }
}

所有的 class 类里面不需要加 function

2). 利用类创建对象 new

var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨

在这里插入图片描述
注意:

  • 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
  • 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
  • constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
  • 多个函数方法之间不需要添加逗号分隔
  • 生成实例 new 不能省略

语法规范,

  • 创建类
    类名后面不要加小括号,而是直接跟一个{}
  • 生成实例
    类名后面加小括号, 构造函数不需要加function
    并且,多个函数和方法之间,不能添加标点符号做分隔。

3)在类中添加方法:直接把函数或者方法写到类里面

class Star {
    // 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数
    constructor(uname, age) {
      this.uname = uname;
      this.age = age;
    }//------------------------------------------->注意,方法与方法之间不需要添加逗号
    sing(song) {
      console.log(this.uname + '唱' + song);
    }
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨

2.2.3 类的继承

在程序中,子类可以继承父类的一些属性和方法

2.2.3.1 语法

// 父类
class Father{   
} 

// 子类继承父类
class  Son  extends Father {  
}      

2.2.3.2 实例

class Father {
      constructor(surname) {
        this.surname= surname;
      }
      say() {
        console.log('你的姓是' + this.surname);
       }
}
class Son extends Father{  // 这样子类就继承了父类的属性和方法
}
var damao= new Son('刘');
damao.say();      //结果为 你的姓是刘
  • 子类使用super关键字访问父类的方法
 //定义了父类
class Father {
   constructor(x, y) {
   this.x = x;
   this.y = y;
   }
   sum() {
   console.log(this.x + this.y);
	}
 }
//子元素继承父类
    class Son extends Father {
   		 constructor(x, y) {
    		super(x, y); //使用super调用了父类中的构造函数
    	}
    }
    var son = new Son(1, 2);
    son.sum(); //结果为3

继承的关键字extends
super:调用父类中的构造函数或普通函数,可以把constructor()里的数据传递给父类。

2.2.3.3 注意事项

注意:

  • 继承中,如果实例化子类输出一个方法,先看子类本身有没有这个方法,如果有就先执行子类的;

  • 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则);

  • 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用。

  • 时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.。

    • constructor中的this指向的是new出来的实例对象
    • 自定义的方法,一般也指向的new出来的实例对象
    • 绑定事件之后this指向的就是触发事件的事件源
  • 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象:

JavaScript从入门到放弃 - ES6中的对象和类_第1张图片
JavaScript从入门到放弃 - ES6中的对象和类_第2张图片

  • 类里面的共有的属性和方法一定要加this使用;
  • this 问题
    • constructor 里面的this,指向的是创建的实例对象;
    • 方法中的this指向不清楚,只有在调用的时候才清楚,this指向调用者。

3. 面向对象案例

3.1 面向对象版tab栏切换

功能需求:

  • 点击 tab栏,可以切换效果;
  • 点击 + 号, 可以添加 tab 项和内容项;
  • 点击 x 号, 可以删除当前的tab项和内容项;
  • 双击 tab项 文字或内容项文字,可以修改里面的文字内容。

JavaScript从入门到放弃 - ES6中的对象和类_第3张图片
面向对象编程思路:

  1. 首先,把公共的一些属性和方法抽取出来,写到类里面;
  2. 然后,根据这个类实例化对象,再创建各式各样的对象。

3.1.1 案例准备

3.1.1.1抽象对象:Tab对象

该对象具有:

  1. 切换功能;
  2. 添加功能;
  3. 删除功能;
  4. 修改功能。

3.1.1.2 创建(Tab)类

class Tab {
    constructor(){
    }
    // 1、切换功能
    toggle(){}
    // 2、添加功能
    addTab(){}
    // 3、删除功能
    removeTab(){}
    // 4、修改功能
    editTab(){}
};

3.1.1.3 获取元素并调用初始化函数绑定事件

class Tab {
    constructor(id){
        // 获取元素
        this.main=document.querySelector(id);
        this.lis=this.main.querySelectorAll('li');
        this.sections=this.main.querySelectorAll('section');
    }
    init(){
        // init 初始化,让页面加载时相关的元素绑定事件
        for (var i=0; i< this.lis.length;i++){
            this.lis[i].index=i;
            this.lis[i].onclick=function(){

            }
        }
    }
    // 1、切换功能
    toggleTab(){}
    // 2、添加功能
    addTab(){}
    // 3、删除功能
    removeTab(){}
    // 4、修改功能
    editTab(){}
};
new Tab('#tab');

3.1.2 切换功能实现

var that;
class Tab {
    constructor(id) {
        that = this;
        // 获取元素
        this.main = document.querySelector(id);
        this.lis = this.main.querySelectorAll('li');
        this.sections = this.main.querySelectorAll('section');
        this.init();
    }
    init() {
            // init 初始化,让页面加载时相关的元素绑定事件
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].index = i;
                this.lis[i].onclick = this.toggleTab;
            }
        }
        // 1、切换功能
    toggleTab() {
        // console.log(this.index);
        that.clearClass();
        this.className = 'liactive';
        that.sections[this.index].className = 'conactive';
    }
    clearClass() {
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].className = '';
                this.sections[i].className = '';
            }
        }
        // 2、添加功能
    addTab() {}
        // 3、删除功能
    removeTab() {}
        // 4、修改功能
    editTab() {}
};
new Tab('#tab');

声明了一个全局变量that,在constructor对其进行赋值,以解决this指向(lis)问题。
书写了一个利用循环清除样式的clearClass方法。

3.1.3 添加功能的实现

功能分析:

  1. 点击+,可以实现添加新的选项卡和内容;
  2. 第一步,创建新的选项卡li和新的内容section
  3. 第二步,把创建的两个元素追加到对应的父元素中

第一步:创建新的选项卡li和新的内容section

  • 传统的做法:动态创建元素createElement。但是元素里内容较多,需要innerHTML赋值,再appendChild追加到父元素;
  • 高级的做法:利用insertAdjacentHTML()可以直接把字符串格式元素添加到父元素中。

点击前往 :insertAdjacentHTML方法官方使用说明

appendChild不支持追加字符串的子元素;
insertAdjacentHTML支持追加字符串的元素。

     // 2、添加功能
    addTab() {
            that.clearClass();
            // 1、创建li元素和section元素;
            var random = Math.random();
            var li = '
  • 新标签页
  • '
    ; var section = '
    测试' + random + '
    '
    ; // 2、把这两个元素追加到对应的父元素里 that.ul.insertAdjacentHTML('beforeend', li); that.fsection.insertAdjacentHTML('beforeend', section); }

    注:后添加的lisection,不能在页面加载时获取 ,因此会出现后添加的lisection没有切换效果,此时就将获取li元素和section元素的放到函数updateNode里,在点击+号时,重新调用此函数获取元素并绑定onclick事件,代码如下:

    var that;
    class Tab {
        constructor(id) {
            that = this;
            // 获取元素
            this.main = document.querySelector(id);
            this.add = this.main.querySelector('.tabadd');
            // 获取 li 的父元素
            this.ul = this.main.querySelector('.fisrstnav ul:first-child');
            // 获取 section的父元素
            this.fsection = this.main.querySelector('.tabscon');
            this.init();
        }
        init() {
                this.updateNode();
                // init 初始化,让页面加载时相关的元素绑定事件
                this.add.onclick = this.addTab;
                for (var i = 0; i < this.lis.length; i++) {
                    this.lis[i].index = i;
                    this.lis[i].onclick = this.toggleTab;
                }
            }
            // 获取所有的小li 和 section
        updateNode() {
                this.lis = this.main.querySelectorAll('li');
                this.sections = this.main.querySelectorAll('section');
            }
            // 1、切换功能
        toggleTab() {
            // console.log(this.index);
            that.clearClass();
            this.className = 'liactive';
            that.sections[this.index].className = 'conactive';
        }
        clearClass() {
                for (var i = 0; i < this.lis.length; i++) {
                    this.lis[i].className = '';
                    this.sections[i].className = '';
                }
            }
            // 2、添加功能
        addTab() {
                that.clearClass();
                // 1、创建li元素和section元素;
                var random = Math.random();
                var li = '
  • 新标签页
  • '
    ; var section = '
    测试' + random + '
    '
    ; // 2、把这两个元素追加到对应的父元素里 that.ul.insertAdjacentHTML('beforeend', li); that.fsection.insertAdjacentHTML('beforeend', section); that.init(); } // 3、删除功能 removeTab() {} // 4、修改功能 editTab() {} }; new Tab('#tab');

    3.1.4 删除功能模块

    • 为元素的删除按钮x绑定点击事件;
    • 获取到点击的删除按钮的所在的父元素的index,删除对应的标题与内容。

    注意
    在tab类的删除方法中处理: 点击x获取对应的索引,注意获取的索引号是当前x号的父元素的索引,要读取parentNode.index
    因为tab标题本身有点击事件,x号的点击事件会向上传递,需要阻止事件冒泡。

    获取所有tab标题的x按钮并为每一个tab标题的x号绑定点击事件(一定要在更新的方法中获取,否则新添加的tab栏获取不到,代码会报错)

        // 3、删除功能
        removeTab(e) {
                e.stopPropagation(); // 阴止冒泡 防止触发li的切换点击事件
                var index = this.parentNode.index;
                // 根据索引号删除对应的li和section  remove() 方法可以直接删除指定的元素
                that.lis[index].remove();
                that.sections[index].remove();
                that.init();
                // 如果删除的不是选中状态的li,原来的选中状态li保持不变
                if (document.querySelector('.liactive')) return;
                // 如果删除了选中状态的列,需要设置它的前一个li为选中状态
                index--;
                // click 调用点击事件,不需要鼠标触发
                that.lis[index] && that.lis[index].click();
            }
    

    总结

    • 将记录下来的索引找到对应的tab标题和tab内容删除。remove() 方法可以直接删除指定的元素;
    • 重新初始化tab标题与tab内容的数量;
    • 点击删除选中状态的tab的标题后让前一个tab标题选中;
      • 将索引自减;
      • 手动调用click事件。

    1)将索引自减代码that.lis[index] && that.lis[index].click() 中的&& 符号,表示如果左边为真,才执行右边的代码。
    2)if (document.querySelector('.liactive')) :如果有处于选定状态的列,就return,下面的代码不再执行。

    3.1.5 编辑功能模块

    双击选项卡lisection里面的文字,可以实现修改操作。

    • 为元素(标题与内容)绑定双击事件ondblclick
    • 在双击事件处理文本选中状态,修改内部DOM节点,实现新旧value值的传递。

    核心思路
    双击文字时,在里面生成一个文本框;
    当失去焦点或是按下回车,就把文本框的值给原先元素即可。

    注:如果双击,会默认选定文字,此时需要双击禁止选中文字。
    双击禁止选中文字的代码:

    window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
    

    1)为元素(标题与内容)绑定双击事件

    this.spans[i].ondblclick = this.editTab;
    this.sections[i].ondblclick = this.editTab;
    

    文本框失去焦点时,将文本框里的值再赋给span

    input.onblur = function() {
        this.parentNode.innerHTML = this.value;
    }
    

    当敲下回车时,也能够把文本框的值给span

     input.onkeyup = function(e) {
                if (e.keyCode === 13) {
                    //手动调用失去焦点事件,不需鼠标离开操作
                    this.onblur();
                }
            }
    

    利用了键盘事件里的事件对象。

    修改模块的代码:

        // 4、修改功能
        editTab() {
            // 获取原先的内容
            var str = this.innerHTML;
            // 双击禁止选定文字
            window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
            this.innerHTML = '';
            // 获取文本框
            var input = this.children[0];
            input.value = str; //将原先span里的文字赋值给文本框
            // 让文本框里的文字处于选定状态
            input.select();
            //文本框失去焦点时,将文本框里的值再赋给span
            input.onblur = function() {
                    this.parentNode.innerHTML = this.value;
                }
                //敲下回车时,也能够把文本框的值给span
            input.onkeyup = function(e) {
                if (e.keyCode === 13) {
                    //手动调用失去焦点事件,不需鼠标离开操作
                    this.onblur();
                }
            }
        }
    

    至此,已完成Tab栏的切换、增、删、改功能

    你可能感兴趣的:(前端)