主要内容:

面对过程 vs 面向对象 理解

js 对象及类(ES6) *

案例:面向对象版 tab 栏切换

面型过程 vs 面向对象

问题:

  1. 从宏观上看,编程思想有哪两种?
  2. 面向过程专注于什么?面向对象呢?
  3. 面向对象编程有哪些核心特性?
  4. 思考:面向对象和面向过程之间是完全无关的么?

回答:

  1. 面向过程及面向对象
  2. 面向过程专注于解决问题的步骤,面向对象专注于问题中涉及到的对象
  3. 封装 继承 多态
  4. 面向对象和面向过程是彼此关联的,面向对象中对象方法实现某些目标仍然需要基于具体的步骤

JS 对象及类(ES6)

问题:

  1. 对象和类之间是什么关系?

回答:

  1. 类是多个具备相同属性的对象的模板,对象是类的实例

ES6 类的定义及实例化

问题:

  1. ES6 如何定义类?如何实例化?
  2. 回忆 js 对象创建有几种方法?ES5 用什么充当类?

回答:

  1. // ES6 定义类格式 类名首字母要求大小
  2. class 类名{
  3. constructor(参数1,参数2){
  4. this.属性名1 = 参数1
  5. }
  6. 方法1(){...}
  7. 方法2(){...}
  8. }
  9. class Stu{
  10. constructor(id,name){
  11. this.id = id;
  12. this.name = name;
  13. }
  14. study(){
  15. alert('学习')
  16. }
  17. }
  18. var stu1 = new Stu('001','jim') // 必须有new 没有new时会报错
  19. console.log(stu1.name)
  20. stu1.study()

补充:

  1. ES6 类的本质 就是个函数
  2. 类的另一种定义方式 var C = class {…}
  3. 静态方法
 class Father {
     constructor(x, y) {
         this.x = x;
         this.y = y;
     }
     sum() {
         console.log(this.x + this.y);
     }
     // 通过static关键字定义静态方法,通过类名直接调用,类似Array.isArray() Date.now()
     static test(){alert('test')}
 }
Father.test()

ES6 类的继承

问题:

  1. ES6 如何实现继承?
  2. 思考:继承有什么好处?

答:

  1. extends
  2. 方便代码复用
 class Father {
     constructor(x, y) {
         this.x = x;
         this.y = y;
     }
     say(){
         console.log('father')
     }
     sum() {
         console.log(this.x + this.y);
     }
     // 通过static关键字定义静态方法,通过类名直接调用,类似Array.isArray() Date.now()
     static test(){alert('test')}
 }
class Son extends Father{  
}
var s1 = new Son(1,2)
s1.sum()//s1自动继承父类的所有方法

super 关键字及使用注意事项

问题:

  1. 用一句话描述 super 关键字的功能。
  2. super 关键字有几种用法?
  3. 在构造函数中使用 super 方法时要注意什么?

答:

  1. 调用父类方法
  2. a super()调用父类的构造函数 b,super.方法名() 调用父类的普通方法
  3. 要放在this的前面,若子类定义了自己的constructor则在该constructor 中必须调用super,若未定义自己的constructor时则不需调用
 class Father {
     constructor(x, y) {
         this.x = x;
         this.y = y;
     }
     say(){
         console.log('father')
     }
     sum() {
         console.log(this.x + this.y);
     }
     // 通过static关键字定义静态方法,通过类名直接调用,类似Array.isArray() Date.now()
     static test(){alert('test')}
 }
class Son extends Father{ 
    constructor(x,y,xxx){
        super(x,y)
        this.xxx = xxx;
    }

    calc(){
        this.sum()  //子类中可通过this调用父类方法
    }
    say(){
        super.say()  //子类中通过super调用父类同名方法
        console.log('son')
    }

}
var s1 = new Son(1,2,3)
s1.sum()//s1自动继承父类的所有方法
s1.say()

ES6 类使用时注意事项

问题:

  1. ES6 使用类时有哪些注意事项?

答:

a 必须先定义类再实例化

b 类中所有的实例属性及方法在使用时都必须通过this.属性名/方法名() 的方式

c 类中this的指向仍然遵循”谁调用指向谁”

d 不能重复定义

案例(面向对象 tab 栏切换)

需求:

1 点击li实现tab栏切换效果

2 点击每个li中的删除按钮,删除tab及对应content

3 点击 加号 动态添加li及对应的content且对应的li及其删除按钮具备功能

4 双击li中文字实现编辑

思路(面向对象):

1 封装类 可以方便地 new出多个选项卡

2 在构造函数中设置相关的各种属性(属性值为各种元素)并为各种元素绑定事件

3 设置切换、删除、新增、编辑等四个方法实现对应功能

注意:this指向的变化

var that;
class Tab {
  constructor(selector) {
    // 将选项卡的核心部件定义为公共属性
    that = this;
    this.main = document.querySelector(selector);
    // getElementsByTagName获取到的伪数组会自动根据dom的变化而变化
    this.tabs = this.main.getElementsByTagName('li');
    this.contents = this.main.getElementsByTagName('section');
    //获取关闭按钮
    this.rmBtns = this.main.getElementsByClassName('icon-guanbi');
    // 获取tab文字span
    this.spans = this.main.getElementsByClassName('text');
    this.addBtn = this.main.querySelector('.tabadd');
    // li的父级
    this.tabsParent = this.main.querySelector('ul');
    // section的父级
    this.contentsParent = this.main.querySelector('.tabscon');
    // 初始化,绑定事件
    this.init();
  }
  init() {
    this.addBtn.onclick = this.add;
    for (var i = 0; i < this.tabs.length; i++) {
      this.tabs[i].index = i;
      this.tabs[i].onclick = this.toggle;
      //关闭按钮
      this.rmBtns[i].onclick = this.remove;
      //tab文字
      this.spans[i].ondblclick = this.edit;
      //编辑contents
      this.contents[i].ondblclick = this.edit;
    }
  }
  // 切换
  toggle() {
    // 此处this指被click的li
    console.log(this.index);
    // 清除所有li及section的类
    that.clearClass();
    // 为当前li及对应的section加上相关 类
    this.className = 'liactive';
    that.contents[this.index].className = 'conactive';
  }
  // 添加
  add() {
    // 新增li及section并使其处于选中状态
    // 取消其他li及section的选中状态
    that.clearClass();
    // 新增
    var li = `<li class="liactive">
    <span class="text">新增的${Math.random().toFixed(
      3
    )}</span><span class="iconfont icon-guanbi"></span>
  </li>`;
    that.tabsParent.insertAdjacentHTML('beforeend', li);
    var section = `<section class="conactive">测试1${Math.random()}</section>`;
    that.contentsParent.insertAdjacentHTML('beforeend', section);
    that.init();
  }
  // 删除
  remove(e) {
    e.stopPropagation();
    console.log(this.parentNode.index);
    var index = this.parentNode.index;
    // 删除 index对应的li及section
    // that.lis[index].remove()
    this.parentNode.remove();
    that.contents[index].remove();
    // 重新初始化
    that.init();
    if (that.main.querySelector('.liactive')) return;
    //删除的是被选中的li 让上一个 索引为index-1 处于被选中状态
    if (index == 0 && that.tabs.length > 0) {
      that.tabs[0].click();
    } else {
      index--;
      that.tabs[index] && that.tabs[index].click();
    }
  }
  // 编辑
  edit() {
    // 阻止默认选中文字
    window.getSelection
      ? window.getSelection().removeAllRanges()
      : document.selection.empty();
    // 将内容替换为input框
    var txt = this.innerHTML;
    this.innerHTML = `<input type="text" value="${txt}" />`;
    var inp = this.children[0];
    // select方法选中文本框中文字
    inp.select();
    // inp失去焦点时,将其value赋值给span的innerHTML
    inp.onblur = function () {
      if (this.value !== '') {
        this.parentNode.innerHTML = this.value;
      } else {
        this.parentNode.innerHTML = txt;
      }
    };
    // 若编辑后按下enter键同样有上述blur效果
    inp.onkeyup = function (e) {
      if (e.keyCode === 13) {
        this.blur();
      }
    };
  }
  // 清空所有类
  clearClass() {
    for (var i = 0; i < that.tabs.length; i++) {
      that.tabs[i].className = '';
      that.contents[i].className = '';
    }
  }
}

var t = new Tab('#tab');