class

  1. 入门的时候都是这样的。先有一个动物类,这是一个父类,然后在延伸出小猫小狗之类的小动物,这样就是一个子类。我们既然用到类了,那一般都会用到父类和子类,因为这是一种继承关系。
  2. es5中的做法
    1. es5中是怎么声明一个类的
      1. es5中是用函数来描写类的,对于一个类来说,肯定要有构造函数,构造函数解决什么问题呢,第一个是传参数,第二个是实例化,也就是
        初始化。
      2. 我们要想区分这个动物是什么类别,不能大家都长的一样吧,小猫小狗都是一个动物类别那肯定不可以的。这个时候要定义一个属性

        this.type = type
      3. 那这样一个类的声明基本上就做完了。
      4. 但是,特别要强调的点,这里只有属性,如果我想定义一个方法怎么办,如果你直接向设置属性一样设置一个方法,就会有一个问题,你
        每一个实例它都有这个方法,你可能会问每个对象下面不就应该type属性和eat方法吗,为什么这样是弊端呢。你想想,type也好
        eat也好,它是这个类应该共有的属性吗。就是说小猫小狗这个实例对象上它应该复用一些东西,比如说吃,动物这个类上就应该有这个方
        法,而不是让每个实例上都挂载这样一个方法。
        let Animal = function (type) { this.type = type this.eat = function () {} }
    2. es5中生成一个实例对象
      let dog = new Animal('dog') let monkey = new Animal('monkey') console.log(dog) // Animal {type: 'dog', eat: f} console.log(monkey) // Animal {type: 'monkey', eat: f}
      dog实例对象也好monkey实例对象也好,这两个实例对象它有区别,比如说它们的type不一样,但是它们的eat一样吗,对于它俩来说
      是一样的,但是从这两个打印出来的对象来讲,它们各自有各自的eat,这个显然不对。既然它俩都应该继承一个父类,如果这样设计的
      话,那就是每个子类对象上都有父类的所有东西,这个不是很合理,至少eat这种它俩都共同有的东西你不应该再各自生成一份。打个比
      方,它俩eat应该是一样的是吧,因为都继承于父类,如果我把一个eat改掉,另一个的eat改了吗,是没改的,因为它实例化之后这是
      两个对象了,你改任何一个都不会影响另一个,这样的话就违背了继承的原则,什么叫继承,大家都继承于父类的eat,如果父类的eat
      被修改之后,那所有的实例对象都应该跟着改变,这才叫继承,如果做不到那就不对,一百个方法,你会发现每个对象下面都有一堆这玩意
      。显然这是不合理的。
    3. 那我们遇到上面的问题应该怎么写呢
      把它们共有的方法写在这个类的原型上去,函数都有一个prototype这个对象,你可以往这个上面去写,也就是类继承的工作原理,它
      会沿着原型链一直往上找。你会发现eat没有了,它就是把eat挂在了这个原型链上去,它不在这个实例本身,而在它的原型链上
      let Animal = function (type) {
        this.type = type
      }
      Animal.prototype.eat = function () {}
      let dog = new Animal('dog')
      let monkey = new Animal('monkey')
      console.log(dog) // Animal {type: 'dog'}
      console.log(monkey) // Animal {type: 'monkey'}
    此时,你这样修改eat就不会违背继承的原则,一个实例的公共方法修改,另一个实例也会跟着修改
    dog.constructor.prototype.eat = function () {console.log(111)} console.log(dog.eat()) // 111 console.log(dog.monkey()) // 111
    这样的话,我想改变公共方法的时候,每个实例都可以去改,因为它们是继承于同一个地方,这是跟刚才我们讲的本质的区别。这个
    地方就是用原型链,但是刚才那个做法你是做不到的,因为它实例化的时候每个对象相当于是做了一个备份,备份之间又不相互通,
    所以你改了自己这个公共方法的时候,你发现通过继承的时候你改变不了别人,你只能改变自己。这个就违背了继承的原则,而利用
    这种方式就可以了,我去修改我们共同的方法,那这样大家都跟着变了,你有多少实例也都跟着变,这才是继承的本质。
    1. 总结
      1. es5中在做类的定义的时候,如果你把所有共用的东西都写在函数体内部,这个是不合理的。正确的做法是,所有实例对象想共有的
        东西你要放在prototype上去。
      2. 为什么这样做还有第二个原因,你要把这个function当成一个构造函数去用,构造函数内部哪些是这个实例对象想自己独一份的
        东西你往里面放,大家通用的东西你放到原型链上去。
  3. es6中的做法
    es6已经有了专用的语法。
    1. 定义类
      class Animal {}
    2. 类定义完了,接着还是刚才那两个步骤,构造函数初始化传参,在es6中它的构造函数是这样的。
      构造函数就是为了传参的
      class Animal {
      // 构造函数
      constructor (type) {
      this.type = type
      }
      // 公用方法,挂载在原型上的方法
      eat () {

      }
      }
    3. class只是es5用原型链声明类的一个语法糖,语法不一样,但最后的本质是一样的,这就是语法糖。
  4. 类的属性
    前面我们讲了类的定义以及实例化,接下来我们讲一下属性
    1. 如果你想保存一个私有属性private
    2. 如果我想让我这个属性是只读的
      通过这两个点,我们引出es6中的getter和setter,也就是我们属性的读与写的操作,es6是如何做到属性的保护以及只读。
      class Animal {
        constructor (type) { 
          this.type = type
        }
        get age () {
          return 4
        }
        set age (val) {
          this.realAge = val // 这里不能再给age赋值了,不然就变成死循环了
        }
      }
      let dog = new Animal()
      console.log(dog.age) // 4
      dog.age = 5
      console.log(dog.age) // 4
      console.log(dog.realAge) // 5 
    我们想做这个对象的属性是只读的,在es6定义的类比es5的function那种做法要灵活了很多,而且它也扩展了一些功能,它不
    要求你所有的属性都必须写在consturctor也就是构造函数里面,它允许你把这个属性放在类的最顶层,但是你放在顶层的时候
    ,前边一定要加get或者是set,这样就对应着getter和setter,就是读和写,也就是说你如果这个age看上去是一个函数,前
    面如果是get或者set它就变成了属性,这个是之前你绝对没见过的,就是我们特别要注意的点。
    get你可以理解为一个对外的出口,set age里面的赋值就不能是age本身了,你可以是别的。上面get里返回的4,我们只是表示
    它的年龄,但是4和age本身它俩不是一个等量的关系,你可以返回任何值,age只是让你这个实例能访问一个对象访问一个数值访
    问一个入口,你只是用age这个出入口,你可以理解为age是你一个出入口的名称,而不是你这个实例对象的一个属性的名字,它
    增加这个功能的含义就是为了让你控制你的属性的只读或者说是有条件的写入,如果没有setter和getter的话你做不到这样,它
    改变这个属性,你赋成什么,它就改成什么了,但是有了setter和getter后,你会发现,你在set和get这个函数体里面做你任
    何想做的逻辑,这个是之前属性之间的简单的赋值和读取所做不到的。setter和getter它让你做到了你在读写的时候可以灵活改
    变返回值,但是这个返回值跟你这个出入口的名字不要一样。
    let _age = 4 class Animal { constructor (type) { this.type = type } get age () { return _age } set age (val) { if (val > 4 && val < 7) { _age = val } } } let dog = new Animal('dog') console.log(doa.age) dog.age = 5 console.log(dog.age)
    通过这个setter的操作,也就说你可以把setter作为一个拦截,你想给这个age做赋值的时候,它会拦截你,你不满足这个条件
    我就给你拦到外面,满足这个条件我会让你赋值给这个。这样我们做到了它既访问不到了这个真实的_age这个变量,这不就是私有
    属性吗,这样就能够实现私有属性的保护,age是暴露给你这个实例对象操作的出入口,但真实的存储这个年龄的变量它不是age,
    这个age只是给谁来读取这个对象的时候,你给它看的,但并不是它真正存放的地方。这就是set和get的价值所在。
    总结:es5中是怎么读写属性的,就是this.属性,你想拦截,只读,有条件的读和写都做不到。es6通过getter和setter的方式
    能让你在读写属性上面有更大的操作权。甚至可以做一些个性化的定制。
  5. 操作方法
    这个方法指两个概念,一个是对象实例的方法;一个是类的静态方法,静态方法它不属于实例对象的,而是属于这个类的。也就是说
    你直接从对象实例*问这个方法它是没有的,但是你通过这个类来访问这个方法,它是存在的。这就是静态方法和实例对象方法的
    区别。
    1. es5
      let Animal = function (type) {
        this.type = type
        this.eat = function () {}
      }
      Animal.prototype.eat = function () {
    
      }
    实例对象的方法它是挂载在prototype上或者挂载在this上的。
    es5的静态方法该怎么写
    Animal.walk = function () {}
    可以看出来,静态方法是挂载在Animal这个类上的,而不是挂在prototype上,这个是非常要注意的点。
    1. es6
      class Animal {
        constructor (type) { 
          this.type = type
        }
        // 公用方法,挂载在原型上的实例对象的方法
        eat () { 
          Animal.walk()
        }
        // 定义类的静态方法
        static walk () {
    
        }
      }
    1. es5, es6的静态方法只是在定义上有区别,在引用上大家都是一样的,因为静态方法就是属于类的,你调用的时候要用类去
      引用这个方法。
    2. 在现实的业务开发中,什么时候用实例对象的方法,什么时候用类的静态方法。这个是要特别注意的。
      思考方式
      如果说你的这个方法依赖于对象的某些属性或者是方法,那这个方法就必须定义为类的实例对象方法。如果说我这个方法里面
      不会涉及到实例对象的内容,你就要用类的静态方法,类的静态方法拿不到当前的实例对象。如果你把这个方法定义为一个静
      态方法,那这个方法跟这个实例对象都绝缘了。要根据业务场景和代码设计去决定。它们本身之间没有优点和缺点。只是哪个
      更适合,哪个不适合。
      静态方法一般就是当作一个功能函数来用就好了。
  6. 类的继承
    1. es5
      let Animal = function (type) {
        this.type = type
      }
      Animal.prototype.eat = function () {
    
      }
    现在我们有一个Animal的类,它要做为一个动物类的最顶层的父类。现在我们想再声明一个它的子类叫狗dog,因为狗又分
    好多种,因为狗它是一个群体,你可以用一个类。但是这个狗它属于动物,也就是说dog类应该继承Animal类,这样dog的
    实例就对应着不同属性的小狗,这是一个继承的概念。面向对象之所以强大,就是因为有继承,什么叫继承呢,就是爹有的
    东西可以继承给儿子,你爹有的东西,儿子只要继承他爹,那他的所有的财产所有的东西,即使儿子什么都不动,他都有。换
    句话说,我们这有一个Dog的类,还有一个Animal的类,Animal上声明的所有的属性和方法,Dog即使什么都不干,它会
    继承Animal类的所有属性和方法,这就是继承的概念。
    1.es5如何实现继承的
    • es5中继承有很多种实现方法
      let Dog = function () {
        Animal.call(this, 'dog')
      }
     Animal是父类,Dog是子类,Dog类要继承Animal类,你要做的第一件事情就是在Dog的构造函数中要执行Animal的构造
     函数,并且把实例对象的指针指向当前的Dog类。
     Dog.prototype = Animal.prototype
    1. es6
      // 子类不声明构造函数,此时es6给你写了一个默认的constructor
      class Dog extends Animal {
    
      }
      // 子类声明构造函数
      class Dog extends Animal {
        constructor (type) {
          // super调用必须放到构造函数第一行,这个super就是父类的构造函数
          super(type)
        }
      }
  7. 类的总结

    我们关于类这块的内容一共讲述了几个知识点。
    1. 类的声明
    2. 类的属性,getter和setter如何去做类的属性的读和写,功能上比es5强大了很多
    3. 方法
    4. 继承
上一篇:HTML之jQuery


下一篇:匿名内部类