web前端 - 精简系列四:JS面向对象和Es6

149 阅读8分钟

面向对象编程介绍

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');
    console.log(this.ul);
    // 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;
      this.remove[i].onclick = this.removeTab;
      this.spans[i].ondblclick = this.editTab;
      this.sections[i].ondblclick = this.editTab;
    }
  }
  // 因为我们动态添加元素 需要从新获取对应的元素
  updateNode() {
    this.lis = this.main.querySelectorAll('li');
    this.sections = this.main.querySelectorAll('section');
    this.remove = this.main.querySelectorAll('.icon-guanbi');
    this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
  }
  // 1. 切换功能
  toggleTab() {
    // console.log(this.index);
    that.clearClass();
    this.className = 'liactive';
    that.sections[this.index].className = 'conactive';
  }
  // 清除所有li 和section 的类
  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 =
      '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
    var section = '<section class="conactive">测试 ' + random + '</section>';
    // (2) 把这两个元素追加到对应的父元素里面
    that.ul.insertAdjacentHTML('beforeend', li);
    that.fsection.insertAdjacentHTML('beforeend', section);
    that.init();
  }
  // 3. 删除功能
  removeTab(e) {
    e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件
    var index = this.parentNode.index;
    console.log(index);
    // 根据索引号删除对应的li 和section   remove()方法可以直接删除指定的元素
    that.lis[index].remove();
    that.sections[index].remove();
    that.init();
    // 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
    if (document.querySelector('.liactive')) return;
    // 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
    index--;
    // 手动调用我们的点击事件  不需要鼠标触发
    that.lis[index] && that.lis[index].click();
  }
  // 4. 修改功能
  editTab() {
    var str = this.innerHTML;
    // 双击禁止选定文字
    window.getSelection
      ? window.getSelection().removeAllRanges()
      : document.selection.empty();
    // alert(11);
    this.innerHTML = '<input type="text" />';
    var input = this.children[0];
    input.value = str;
    input.select(); // 文本框里面的文字处于选定状态
    // 当我们离开文本框就把文本框里面的值给span
    input.onblur = function () {
      this.parentNode.innerHTML = this.value;
    };
    // 按下回车也可以把文本框里面的值给span
    input.onkeyup = function (e) {
      if (e.keyCode === 13) {
        // 手动调用表单失去焦点事件  不需要鼠标离开操作
        this.blur();
      }
    };
  }
}
new Tab('#tab');

类和对象

      // 1:对象
      // 现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人
      // 可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、
      // 函数等。
      // 对象是由属性和方法组成的:
      // 属性:事物的特征,在对象中用属性来表示(常用名词)
      // 方法:事物的行为,在对象中用方法来表示(常用动词)

      // 类 class
      // 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
      // 类抽象了对象的公共部分,它泛指某一大类(class)
      // 对象特指某一个,通过类实例化一个具体的对象
      // 面向对象的思维特点:
      // 1. 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
      // 2. 对类进行实例化, 获取类的对象

      // 创建类和生成实例:
      class Star {
        constructor(uname, age) {
          this.uname = uname;
          this.age = age;
        }
      }
      var mayun = new Star('马云', 66); //Object { uname: "马云", age: 66 }
      var yyx = new Star('尤雨溪', 34); //Object { uname: "尤雨溪", age: 34 }
      console.log(mayun);
      console.log(yyx);

      // 类中添加共有方法:
      class Boss {
        constructor(name, money) {
          this.name = name;
          this.money = money;
        }
        // 共有方法
        sing(song) {
          console.log(song);
        }
      }
      var mayun = new Boss('马云', '1500亿身家');
      console.log(mayun); // Object { name: "马云", money: "1500亿身家" }
      mayun.sing('假如云知道'); // 假如云知道

      // 类继承extends和super关键字:
      class Boss {
        constructor(name, money) {
          this.name = name;
          this.money = money;
        }
        sing(song) {
          console.log(song);
        }
      }
      class Fuerdai extends Boss {}
      var wsc = new Fuerdai('王思聪', '好多money');
      console.log(wsc);
      wsc.sing('KTV是我家'); // KTV是我家

      // super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
      class Father {
        constructor(name, money) {
          this.name = name;
          this.money = money;
        }
        work(msg) {
          console.log(msg);
        }
      }
      class Son extends Father {
        constructor(name, money) {
          super(name, money);
        }
      }
      var xiaoming = new Son('小明', '兜里9块9');
      xiaoming.work('放牛');

      // 类里面this指向问题:指向调用者
      class Work {
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
        happy() {
          console.log(this); //Object { name: "打工人", age: "16未成年" }
        }
      }
      var wxb = new Work('打工人', '16未成年');
      console.log(wxb); //Object { name: "打工人", age: "16未成年" }
      wxb.happy();

构造函数原型 prototype

      // 原型constructor构造函数:
      // constructor 构造函数
      // 对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称
      // 为构造函数,因为它指回构造函数本身。
      // constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
      // 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋
      // 值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
      // 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

      function Boss(name) {
        this.name = name;
      }
      Boss.prototype = {
        constructor: Boss,
        work: function (money) {
          console.log(money);
        },
      };
      var mayun = new Boss('马云');
      mayun.work('1500亿身家'); //1500亿身家
      // console.log(Boss.prototype === mayun.__proto__); // true
      // console.log(Boss.prototype.constructor === mayun.__proto__.constructor); // true
      console.log(Boss);
      console.log(mayun);

forEach、some、filter方法

     // 1:数组方法
      // 迭代(遍历)方法:forEach()
      array.forEach(function(currentValue, index, arr))
      var arr = [1, 2, 3, 4];
      var sum = 0;
      arr.forEach(function (val, index, arr) {
        // console.log(val); // 1  2  3  4
        // console.log(index); // 0  1  2  3
        // console.log(arr); // Array(4) [ 1, 2, 3, 4 ]
        sum += val; //10
      });
      console.log(sum);

      // array.filter(function(currentValue, index, arr))
      // filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,
      // 主要用于筛选数组,注意它直接返回一个新数组
      //  用filter返回数组中能被2整除的每一项
      var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      var newArr = arr.filter(function (val, index) {
        return val % 2 === 0;
      });
      console.log(newArr); //Array(5) [ 2, 4, 6, 8, 10 ]

      // some()
      // array.some(function(currentValue, index, arr))
      // some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
      // 注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
      // 如果找到第一个满足条件的元素,则终止循环. 不在继续查找.

      // 使用some查找数组中,是否有可以被2整除的数据,有返回布尔值true
      var arr = [1, 2, 3, 4, 5];
      var flag = arr.some(function (val, i) {
        // console.log(val); // 1 2 3 4 5
        // console.log(i); // 0  1  2  3  4
        return val % 2 === 0;
      });
			console.log(flag); // true
			
// forEach和filter性能开销大,some性能开销小些 
// 在forEach中,即使return true,它也会将数组中的每一项循环完,性能开销大。
// filter,filter即使return,程序也不会打断,也会继续向后执行完,和forEach差不多。

// some,return 在此处发挥了作用,到了这里就return退出程序了,后续代码就不在执行了。

call、apply、bind的使用

      // call、apply、bind的使用
      // call方法及其应用
      // call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。

      // -------------------------  call ------------------------
      // 普通函数
      var obj = {
        name: 'zs',
      };
      function fn1(a, b) {
        console.log(this);
        console.log(a + b);
      }
      fn1.call(obj, 1, 5); // 函数使用call将this指向了obj对象,

      // 构造函数
      function Boss(name) {
        this.name = name;
      }
      function Fuerdai(name) {
        Boss.call(this, name); //将this指向了父构造函数
        console.log(this); //Object { name: "王思聪" }
      }
      var wsc = new Fuerdai('王思聪');

      // bind方法基本使用
      // 1. 不会调用原来的函数   可以改变原来函数内部的this 指向
      // 2. 返回的是原函数改变this之后产生的新函数
      // 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
      // bind的感觉和call的很像
      // -------------------------  bind ------------------------
      var obj = {
        name: 'zs',
      };
      function fn2(a, b) {
        console.log(this);
        console.log(a + b);
      }
      var fn = fn2.bind(obj, 2, 3); // callhi直接调用函数,apply需要用新变量接收
      fn();

      // 点击让按钮禁用,2秒以后让按钮恢复回来,通过bind将this指回了btn当前点击的按钮
      var btn = document.querySelector('.btn');
      btn.onclick = function () {
        this.disabled = true;
        setTimeout(
          function () {
            this.disabled = false;
          }.bind(this), //定时器的this指向的是window,使用bind将this指回了按钮
          2000
        );
      };

      // -------------------------  apply ------------------------
      // apply方法及其应用
      // 感觉没有call好用,虽然指向了obj
      var obj = {
        name: 'zs',
      };
      function fn2(arr) {
        console.log(this); //Object { name: "zs" }
        console.log(arr); //red
      }
      fn2.apply(obj, ['red']); // 函数使用apply将this指向了obj对象,

      // 借助Math.max求数组中最大和最小值
      var arr = [1, 3, 7, 9, 23, 8, 5];
      var max = Math.max.apply(Math, arr);
      var min = Math.min.apply(Math, arr);
      console.log(max); //23
      console.log(min); //1

      // call和apply以及bind总结
      // 相同点:
      // 都可以改变函数内部的this指向.

      // 区别点:
      // 1. call 和 apply 会调用函数, 并且改变函数内部this指向.
      // 2. call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
      // 3. bind 不会调用函数, 可以改变函数内部this指向.

      // 主要应用场景:
      // 1. call 经常做继承.
      // 2. apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
      // 3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

闭包-递归及应用

      // 闭包(closure)指有权访问另一个函数作用域中变量的函数。
      // 一个作用域可以访问另外一个函数的局部变量
      // 我们fn 外面的作用域可以访问fn 内部的局部变量
      // 闭包的主要作用: 延伸了变量的作用范围

      // 闭包应用-点击li打印当前索引号
      var lis = document.querySelectorAll('li');
      for (var i = 0; i < lis.length; i++) {
        (function (i) {
          lis[i].onclick = function () {
            console.log(i); // 0  1   2
          };
        })(i); // 使用自调用函数,将外层循环的i,通过形参的方式传递到内部的点击事件中,此时产生了闭包
      }

      // 闭包应用-3秒钟之后,打印所有li元素的内容
      var lis = document.querySelectorAll('li');
      for (var i = 0; i < lis.length; i++) {
        (function (i) {
          setTimeout(function () {
            console.log(lis[i].innerHTML); // 第1个li  第2个li  第3个li
          }, 3000);
        })(i); // 循环之中,同上一致,使用自调用函数,将i当做形参传给内部的函数使用,产生了闭包
      }

      // 闭包应用-计算打车价格
      // 用对象的方式计算打车价格,道路顺畅就是price按照正常计算,拥堵就在原有的total上加上10元

      var car = (function () {
        // 使用自调用函数,在return中将2个价格计算并返回
        var start = 13; // 起步价格
        var total = 0; // 总价
        return {
          price: function (n) {
            // 正常打表价格
            if (n <= 3) {
              total = start;
            } else {
              total = start + (n - 3) * 5;
            }
            return total;
          },
          yd: function (flag) {
            //正常价格上,加上拥堵费用
            return flag ? total + 10 : total;
          },
        };
      })();
      console.log(car.price(5)); // 23
      console.log(car.yd(true)); // 33

      // 什么是递归函数
      // 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
      // 简单理解:函数内部自己调用自己, 这个函数就是递归函数
      // 递归函数的作用和循环效果一样
      // 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
      var num = 1;
      function fn1() {
        console.log('我要打印6次'); // 我要打印6次 * 6
        if (num === 6) {
          return; // 满足条件后,一定要避免死循环,要加上return退出。
        }
        num++;
        fn1(); // 1 函数自己调用自己
      }
      fn1();

浅拷贝 - 深拷贝

      // 1:Object.assign(target, ...soureces)
      // ------------------- Object.assign -------------------
      // 参数:target:目标对象。   sources:任意多个源对象。  返回值:目标对象会被返回。
      // Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,
      // 然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
      var obj1 = {
        test1: {
          test2: 'hello',
        },
      };
      var obj2 = Object.assign({}, obj1);
      obj2.test1.test2 = 'nihao'; //修改b的属性,会影响a的属性
      console.log(obj2); //test1: Object { test2: "nihao" }
      console.log(obj1); //test1: Object { test2: "nihao" }

      // 2:jquery 有提供一个$.extend可以用来做 Deep Copy。
      // ------------------- $.extend -------------------
      var obj1 = {
        test1: {
          test2: 'hello',
        },
      };
      var obj2 = $.extend(true, {}, obj1);
      obj2.test1.test2 = 'nihao'; // //修改b的属性,不会影响a的属性
      console.log(obj2); //test1: Object { test2: "nihao" }
      console.log(obj1); //test1: Object { test2: "hello" }

      // 还有一些其它的第三方函数库有深拷贝function,如lodash。

      // -------------------  JS数据类型  -------------------
      // 基本数据类型主要是:undefined,boolean,number,string,null。
      // 基本类型就是值类型, 存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配
      // 引用类型 Object Array
      // 引用类型, 存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置

      // ------------------- 浅拷贝 -------------------
      // 概念: 对于字符串类型,浅拷贝是对值的复制,对于对象来说,浅拷贝是对对象地址的复制, 也就是拷贝的结果是两个对象指向同一个地址

      // ------------------- 深拷贝 -------------------
      // 概念: 深拷贝开辟一个新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
      // 深拷贝是对对象以及对象的所有子对象进行拷贝。

      // ------------------- 总结: -------------------
      // 其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。
      // 浅拷贝是复制,两个对象指向同一个地址
      // 深拷贝是栈中新开地址,两个对象指向不同的地址

解构赋值

      // ------------------- 数组解构赋值 -------------------
      // 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
      // 如果是数组解构,声明变量时,也需要使用数组的形式声明来接收,多出来的变量,值是undefiend
      let arr = [1, 2, 3];
      let [a, b, c] = arr;
      console.log(a); // 1
      console.log(b); // 2
      console.log(c); // 3
      console.log(d); // undefined

      // -------------------  对象解构  -------------------
      // 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功 将对象属性的值赋值给变量
      //因为解构的是对象类型的,所以声明变量的时候,也是用对象方式声明的
      let obj = { name: 'wsc', age: 33 };
      let { name, age } = obj;
      console.log(name); // wsc
      console.log(age); // 33

      // 解构后重命名, 和{res:data}是一样的
      // -------------------  解构后重命名  -------------------
      let { name: uname } = obj;
      console.log(uname); // wsc

箭头函数 =>

      // 箭头函数是用来简化函数定义语法的
      // ()括号就代替function, =>就代表这是箭头函数
      // 1:传统函数
      const fn = function () {
        console.log('fn1');
      };
			fn();
			
      // 2:箭头函数
      const fn = () => {
        console.log('fn2');
      };
      fn;

      // 在箭头函数中 如果函数体中只有一句代码 并且代码的执行结果就是函数的返回值 函数体{}大括号可以省略
      const sum = (a, b) => a + b;
      console.log(sum(1, 3)); //4

      // 箭头函数不绑定this 箭头函数没有自己的this关键字 如果在箭头函数中使用this
      // this关键字将指向箭头函数定义位置中的this
      const fn = () => {
        console.log(this); // window
      };
      fn();

      // fn.call(obj), 函数使用call将this指向了obj对象
      const obj = { name: 'zs' };
      function fn() {
        console.log(this); //Object { name: "zs" }
        return () => {
          console.log(this); //Object { name: "zs" }
        };
      }
      const objFn = fn.call(obj);
      objFn();

剩余参数和拓展运算符 (展开运算符)

      // 剩余参数和拓展运算符 (展开运算符)
      // --------------------------- 剩余参数 --------------------------
      // 求任意个数组的和
      const sum = (...arr) => {
        let total = 0;
        arr.forEach((item) => {
          total += item;
        });
        return total;
      };
      console.log(sum(1, 2, 3));

      // --------------------------- 接收数组剩下的参数 --------------------------
      let arr = ['apple', 'orange', 'banana'];
      let [a, ...b] = arr;
      console.log(a); //apple
      console.log(b); //Array [ "orange", "banana" ]

      // --------------------------- 数组合并的方式一 --------------------------
      let arr1 = [1, 2, 3];
      let arr2 = [4, 5, 6];
      let arr3 = [...arr1, ...arr2];
      console.log(arr3); // Array(6) [ 1, 2, 3, 4, 5, 6 ]
      // --------------------------- 数组合并的方式二 --------------------------
      let arr1 = [1, 2, 3];
      let arr2 = [4, 5, 6];
      let arr3 = arr1.concat(...arr2);
      console.log(arr3); //Array(6) [ 1, 2, 3, 4, 5, 6 ]

Array实例方法

    //  ------------------------- Array.from方法  -------------------------
      // 使用Array.from将对象转换为数组
      var obj = {
        // 注意对象中的属性的格式
        0: 'zs',
        1: 18,
        length: 2,
      };
      let arr = Array.from(obj);
      console.log(arr); //Array [ "zs", 18 ]

      //  ------------------------- Array实例方法:find  -------------------------
      //  find,查找出id为2的对象
      var arr = [
        { id: 1, name: 'zs' },
        { id: 2, name: 'ww' },
      ];
      let obj = arr.find((item) => item.id === 2);
      console.log(obj); //Object { id: 2, name: "ww" }

      // Array实例方法:findIndex
      // 数组.findIndex查找数组中的项,返回的是该项的索引
      let arr = [1, 3, 5, 7, 9];
      let res = arr.findIndex((item) => item > 3);
      console.log(res); // 2

      // -------------------------  Array实例方法:includes  -------------------------
      //  数组.includes,查询数组是否包含该项,返回的是布尔值
      let arr = ['a', 'b', 'c'];
      let res = arr.includes('c');
      console.log(res); // true

ES6模板字符串和 set数据结构

      // `模板语法` 可以直接将变量名渲染`${变量名}`,也可以将对象里面的属性名给渲染`${对象.属性名}`,
      // 还可以直接调用函数`{fn()}`
      // 可以直接将变量名渲染`${变量名}
      // --------------------------------- ${变量名} --------------------------------
      let name = 'zs';
      let myName = `hello my name is ${name}`;
      console.log(myName); // hello my name is zs

      // // ----------------------------- ${fn()} -----------------------------
      const fn = (a, b) => a + b;
      let res = `函数的结果是 ${fn(1, 3)}`;
      console.log(res); //函数的结果是 4

      // // 也可以将对象里面的属性名给渲染`${对象.属性名}
      // // ---------------------------- ${对象.属性名} -----------------------------
      let obj = {
        name: 'zs',
        age: 16,
      };
      let objName = `she name is ${obj.name}`;
      console.log(objName); //she name is zs

      // ---------------------------- add()往数组中添加项 ----------------------------
      const set = new Set();
      set.add('a').add('b');
      console.log(set);

      // ------------------------ adelete删除数组中的项, 返回布尔值 ------------------------
      console.log(set.delete('b')); // true
      console.log(set); //Set [ "a" ]

      // ------------------------ has查询数组中是否存在该项,返回布尔值 -----------------------
      console.log(set.has('a')); // true

      // -------------------------------- 清空数组 ---------------------------------------
      set.clear();
      console.log(set); //Set []

      // ------------------------------- forEach遍历数组 --------------------------------
      var arr = ['a', 'b', 'c'];
      let arr1 = arr.forEach((item) => {
        console.log(item); // a  b  c
      });

下一篇: web前端 - 精简系列五:Ajax数据请求和接口测试