第三十二节: ES6 Class类

1. ES6 Class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

1.1 . 复习构造函数

类的功能是创建对象的,js之前实现类的功能需要利用构造函数模拟类的功能,通过new操作符实例化一个对象,对象上的原型[[Prototype]]上有个constructor方法指向构造函数

function person(name,age) {     //构造函数
  this.name=name;
  this.age=age  
}
person.prototype.sayName = function(){    //原型方法
  console.log(`我的名字叫${this.name}。`)
} 
person.prototype.sayAge = function(){    //原型方法
  console.log(`我今年${this.age}岁了`)
} 
person.run=function(){        //静态方法
  console.log('静态方法')
}
let student =new person('xiaoming',18)
console.log(student);
console.log(student.sayAge());
console.log(student.sayName());
console.log(person.run());   //静态方法需要使用构造函数调用

但是之前讲了一个合并对象的,那么我们可以将新增的方法放在一个对象里合并到原型上


  <script type='module'>
    let Person = class {    //类名为Person的ES6的类的声明
      //之前es5写的类功能分为构造函数,原型方法,静态方法,最终实例化,都是分开的。现在es6实现了整体的概念,将他们罗列到一个大括号容器里,其不是代码块,也不是对象,里面不加逗号。
      constructor(name, age) {  //构造函数   
        this.name = name;
        this.age = age;
        this.aa = function () {          //实例对象上的方法,生成到实例对象上的方法
          console.log(这是实例方法);
        }
      };
      //除了constructor方法、使用关键字static的方法,其他方法都是原型上的方法
      sayName() {    //原型方法
        console.log(`我的名字叫${this.name}。`)
      };
      sayAge() {    //原型方法
        console.log(`我今年${this.age}岁了`)
      };
      static run() {     //关键字static 写的方法是静态方法
        console.log(`这是静态方法`);
      }
    }
    let student = new Person('李四', 20);    //通过类实例化对象时本质上执行类内部的constructor函数
    console.log(student);
    student.sayAge()
    console.dir(Person)    //对象方式打印这个类,可以看到静态方法
    Person.run()    //在这种类里静态方法只能通过类名调用
    console.log(typeof Person);    //通过typeof检测类名结果是function,因此class声明类实际上是创建了一个具有构造函数方法行为的函数
    console.log(Person.prototype);
  </script>

<script type='module'>
    let Person = class {    //匿名类的表达式赋值给变量
     }
</script>
1.2 ES6 变形的类Class
// 这是类 不是json 所以里面不需要加逗号
class Person{
    // 这是一个构造方法(函数),只要new Person() 这个构造函数会自动执行
    constructor(name,age){ 
        console.log(`构造函数执行了,name:${name},age:${age}`) 
    }
    
    //以下是构造函数原型上的方法
    
    showName(){
        return `名字:${this.name}`;
    }    // 不需要加逗号,加逗号就报错
    
    showAge(){
        return `年龄:${this.age}`;
    }
}

// 这个Person也是要给函数,typeof Person  打印function
let p1 = new Person("wuwei",18);
console.log(p1.showName());
console.log(p1.showAge());

表达式的类(不推荐)

var Person = class {
    constructor(){
        this.name = "wuwei";
    }
    showName(){
        return `名字:${this.name}`
    }
}
2. constructor

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

class Fn {
}

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

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false
//constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

3. 类必须使用new调用

类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

class Foo {
  constructor() {
    return Object.create(null);
  }
}

Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'

4. 方法名字可以是变量

    var aaa = 'wu';
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
      [aaa]() {
        return `此时属性名是一个变量,就是wu`
      }
    }

    // console.log(typeof Person)
    let p1 = new Person("wuwei", 18);
    console.log(p1.wu());   // 此时不能通过aaa调用函数,aaa是变量,通过wu调用,可以通过[aaa]调用 p1[aaa]();
    console.log(p1[aaa]());

这里就可以发现属性名可以为表达式

var aa = 'wu';
var bb = 'wei';

var obj = {
    [aa + bb]: "无为"
}

5. ES6 class类没有提升和预解析的功能

也就是说如果写法会报错

var p1 = new Person()
console.log(p1);

class Person{
    constructor(){
        this.name = "aaa"
    }
}

但是以前通过ES5 构造函数模拟类是可以变量提升的,因为本身就是函数

// 这种写法构造函数会提升,正常打印一个对象
var p1 = new Person()
console.log(p1);
function Person(){
    this.name = "aaa"
}

6. get set

取值函数 get 存值函数 set

class Person{
    constructor(name,age){ 
        this.name = name;
        this.age = age;
    }
    get aa(){
        console.log(`成功获取a的值`)
        return this.age;
    }
    set aa(val){
        console.log(`成功设置a的值为${val}`);
        this.age = val;
    }
}

var p1 = new Person('wuwei',18)

7.静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

ES6 明确规定,Class 内部只有静态方法,没有静态属性。

class Person{
    constructor(name,age){ 
        this.name = name;
        this.age = age;
    }
    showName(){
        return `这是通过构造对象调用方法`;
    }
    static aaa(){
        return `这是静态方法,是通过类来调用的方法`;
    }
}

var p1 = new Person('wuwei',18)
console.log(p1.showName);
console.log(Person.aaa());

8. 继承

子类继承父类

8.1 之前的子类继承父类
// 父类
function Person(name){      //构造函数
    this.name = name;
}
Person.prototype.showName = function(){   //构造函数原型对象上的方法
    return `名字是:${this.name}`;
}

// 子类
function Student(name,skill){
    Person.call(this,name);   // 继承父类的属性 处理构造函数的继承,调用构造函数的方法,同时改变this指向
    this.skill = skill;   
}
Student.prototype = new Person; // 继承父类的方法,继承原型链
var stu = new Student("wuwei","逃课");
console.log(stu);
console.log(stu.name);
console.log(stu.showName())
8.2 ES6 类的继承 extends
class Person{   //基类
  constructor(name){
    this.name = name;
  }
}
class Student extends Person{  //派生类    es6的继承通过关键字extends
  // constructor(name){    //这种继承 没法添加自己扩展的属性和方法,添加就报错
  //   this.name = name;
  // }         
}
let stu = new Student('张三')
console.log(stu);

以上这种继承 没法添加自己扩展的属性和方法,添加就报错

通过super()扩展自己的属性和方法,super就是调用基类中的构造函数

super注意点:

1、super只可在派生类的构造函数中使用,如果尝试在非派生类(未使用extends的类)或函数中使用则会导致程序报错

2、在构造函数中访问this之前一定要调用super(),因为super()负责初始化this,如果在super之前访问this会导致报错

3、如果不想调用super(),则唯一的方法就是让类的构造函数显示返回一个对象

// 父类
class Person{
    constructor(name){
        this.name = name;
    }
    showName(){
        console.log('父类的showName方法');
        return `名字:${this.name}`;
    }
}

// 子类
class Student extends Person{
    constructor(name,skill){
        super(name); // 先执行父类的属性,继承后在设置自己的属性
        this.skill = skill;
    }
    // 继承父类的方法同时扩展内容
    showName(){
        super.showName(); // 先执行父类的showName方法
        // 在设置自己的showName方法
        console.log('执行了子类里面的showName');
    }

    // 设置自己的方法
    showSkill(){
        return `技能:${this.skill}`
    }
}

var stu = new Student("wuwei","逃课");
console.log(stu.name);
console.log(stu.showName())
  <script type='module'>
    class Person {
      constructor(name) {
        this.name = name;
      }
    }
    class Student extends Person {
      constructor(name, age) {
         super(name)  ;       //super继承基类中的中的构造函数,修正初始化this,不能在super之前使用this
        // this.name = name;         //这个this.name不是继承了父类的this.name,不写是undefined。应该把那么放到super中,相当于扩展,执行super相当于执行基类的构造函数
        this.age = age;
      //  return { name, age }
      }
    }
    let stu = new Student('张三', 18)
    console.log(stu);
  </script>

类方法的遮蔽,派生类中的方法总会覆盖基类中的同名方法

 //遮蔽
 let a =10;
   function aa(){
     let a =20;
     function bb(){
       let a =30;
       console.log(a);
     }
     bb()     //如果aa()   bb()不执行a是没有值的
   }
   aa()
//派生类遮蔽基类中的方法
class Person {
      constructor(name) {
        this.name = name;
      }
      sayName() {
        return (`'基类'中的${this.name}`);   //基类  原型对象的原型上的方法
      }
    }
    class Student extends Person {
      constructor(name, age) {
        super(name);
        this.age = age;
      }
      sayName() {
        return (`'派生类'中的${this.name}`);    //派生类   原型对象上的方法
      }
    }
    let stu = new Student('张三', 18)
    console.log(stu);    //控制台能看到原型上的方法  原型的原型sayName()方法
    console.log(stu.sayName());

如果想调用基类中的方法,则可以通过super在派生类同名方法中手动调用基类方法

    //派生类扩展基类中的方法(调用基类的方法)
    class Person {
      constructor(name) {
        this.name = name;
      }
      sayName() {
        console.log(`'基类'中的${this.name}`);  
      }
    }
    class Student extends Person {
      constructor(name, age) {
        super(name);
        this.age = age;
      }
      sayName() {
        console.log(super.sayName()); //super超级的意思,代表的就是基类。通过super取到基类中的sayName(),先执行基类中的方法,再执行派生类中的同名方法
        console.log(`'派生类'中的${this.name}`); 
      }
    }
    let stu = new Student('张三', 18)
    console.log(stu.sayName());

拖拽例子:

 <style>
    * {
      margin: 0;
      padding: 0;
    }

    #box {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
  </style>

</head>

<body>
  <!-- es5的拖拽 -->
  <div id="box"></div>
  <script>
    let dixTop = 0, dixLeft = 0;//定义两个信号量,记住top和left的值
    box.onmousedown = function (e) {// 有了值就可以绑定鼠标点击事件
      console.log(e);
      let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop;   //鼠标到元素左上角的距离= 鼠标距离页面的距离 - 元素距离页面左上角的距离
      console.log(dixL, dixT);
      box.onmousemove = function (e) { //鼠标移动事件
        dixLeft = e.clientX - dixL;
        dixTop = e.clientY - dixT;
        console.log(dixTop, dixLeft);
        this.style = `top:${dixTop}px;left:${dixLeft}px`
      }
      box.onmouseup = function () { //鼠标松开时还是跟着拖动,需要解绑自己和移动事件
        box.onmouseup = null;
        box.onmousemove = null
      }
    }
  </script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    #box {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
  </style>
</head>
<body>
  <!-- es5的拖拽 -->
  <div id="box"></div>
  <script>
    //拖动速度过快时,浏览器来不及绘制,鼠标离开了元素,无法触发拖动事件。把拖动事件绑到document   样式style的this为box,就算鼠标脱落了元素,元素也会一直跟着鼠标
    let dixTop = 0, dixLeft = 0;
    box.onmousedown = function (e) {
      console.log(e);
      let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop;
      console.log(dixL, dixT);
      document.onmousemove = function (e) {   //事件绑给document
        dixLeft = e.clientX - dixL;
        dixTop = e.clientY - dixT;
        console.log(dixTop, dixLeft);
        box.style = `top:${dixTop}px;left:${dixLeft}px`   //修改元素box的样式
      }
      document.onmouseup = function () {     //解绑也要给document  里面为this
        this.onmouseup = null;
        this.onmousemove = null
      }
    }
  </script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    #box {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <div id="box"></div>
  <script>
    //将box修改为this的写法,更优化了
    let dixTop = 0, dixLeft = 0;
    box.onmousedown = function (e) {
      let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop;
      console.log(dixL, dixT);
      document.onmousemove = function (e) {
        dixLeft = e.clientX - dixL;
        dixTop = e.clientY - dixT;
        console.log(dixTop, dixLeft);
        this.style = `top:${dixTop}px;left:${dixLeft}px`   //这里也修改为this,要让它的函数强制修改this
      }.bind(this)
      document.onmouseup = function () {
        this.onmouseup = null;
        this.onmousemove = null
      }
    }
  </script>
function Drag(className) {

    this.dom = document.createElement('div');
    this.dom.className = className;
    document.body.appendChild(this.dom)

    this.disX = 0;
    this.disY = 0;

    // 初始化
    this.init()

}

Object.assign(Drag.prototype, {
    // 初始化函数
    init() {
        this.dom.onmousedown = function (e) {
            this.down(e)

            document.onmousemove = this.move.bind(this)
            document.onmouseup = this.up
        }.bind(this)
    },

    down(e) {
        // console.log(e.clientX, e.clientY)
        this.disX = e.clientX - this.dom.offsetLeft;
        this.disY = e.clientY - this.dom.offsetTop;
        // console.log(this.disX, this.disY)
    },

    move(e) {
        // console.log(this)
        this.dom.style.left = e.clientX - this.disX + 'px';
        this.dom.style.top = e.clientY - this.disY + 'px';
    },

    up(e) {
        // console.log(this)
        document.onmousemove = null;
        document.onmouseup = null;
    }
})
let left = new Drag('left')

ES6 的拖拽(ES5拖拽不太适合给多个元素绑定拖拽,会很乱)

  <style>
    * {
      margin: 0;
      padding: 0;
    }

    .abox,
    .bbox {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
    .bbox {
      background-color: skyblue;
    }
  </style>
</head>

<body>
  <script>
    class Drag {       //类
      constructor(className) {     //构造函数
        this.dom = document.createElement('div');     //创建div节点
        this.dom.className = className;    //节点上添加属性lassName,也就是参数
        document.body.appendChild(this.dom)    //放到页面中body元素中
        this.disX = 0;     //信号量用的是属性,变量不适合多个元素的拖拽,会发生覆盖
        this.disY = 0;
        // 初始化代码
        this.init()
      }
      // 初始化函数
      init() {
        this.dom.onmousedown = function (e) {
          this.down(e)   //差值
          document.onmousemove = this.move.bind(this)   //鼠标移动事件。不强制绑定this就是document
          document.onmouseup = this.up    //鼠标抬起事件
        }.bind(this)    //强制绑定使内部的this不再指向内部的document,需要指向外部的this
      }
      down(e) {   //计算差值
        // console.log(e.clientX, e.clientY)
        this.disX = e.clientX - this.dom.offsetLeft;
        this.disY = e.clientY - this.dom.offsetTop;
        // console.log(this.disX, this.disY)
      }
      move(e) {
        // console.log(this)
        this.dom.style.left = e.clientX - this.disX + 'px';     //元素属性值+px
        this.dom.style.top = e.clientY - this.disY + 'px';
      }
      up(e) {
        // console.log(this)
        document.onmousemove = null;
        document.onmouseup = null;
      }
    }
    let abox = new Drag('abox')     //对象,所有的属性都是对象自己控制
    let bbox = new Drag('bbox')
    // bbox{
    //   dom:<div class="bbox"></div>,
    //   disX:0,
    //   dixY:0
    // }
  </script>

通过继承创建有边界的拖拽,拖拽控制在边界内部

    <style>
    * {
      margin: 0;
      padding: 0;
    }
    .abox,
    .bbox,
    .right {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
    .bbox {
      background-color: skyblue;
    }

    .right {
      background-color: tomato;
    }
  </style>
</head>

<body>
  <script>
    class Drag {
      constructor(className) {
        this.dom = document.createElement('div');
        this.dom.className = className;
        document.body.appendChild(this.dom)
        this.disX = 0;
        this.disY = 0;
        this.init()
      }
      init() {
        this.dom.onmousedown = function (e) {
          this.down(e)
          document.onmousemove = this.move.bind(this)
          document.onmouseup = this.up
        }.bind(this)
      }
      down(e) {
        this.disX = e.clientX - this.dom.offsetLeft;
        this.disY = e.clientY - this.dom.offsetTop;
      }
      move(e) {
        this.dom.style.left = e.clientX - this.disX + 'px';
        this.dom.style.top = e.clientY - this.disY + 'px';
      }
      up(e) {
        document.onmousemove = null;
        document.onmouseup = null;
      }
    }
    let abox = new Drag('abox')
    let bbox = new Drag('bbox')
    
    
    class LimitDrag extends Drag {   //扩展父类的拖拽
      move(e) {
        super.move(e);   //继承父类
        this.dom.style.left = Math.max(0, this.dom.offsetLeft) + 'px';  //扩展自己的边界限定,不能超出边界
        this.dom.style.left = Math.min(window.innerWidth - this.dom.offsetWidth, this.dom.offsetLeft) + 'px';

        this.dom.style.top = Math.max(0, this.dom.offsetTop) + 'px';
        this.dom.style.top = Math.min(window.innerHeight - this.dom.offsetHeight, this.dom.offsetTop) + 'px';
      }
    }
    let right = new LimitDrag('right')   //扩展的拖拽
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容