Class类

1.构造函数(constructor)

1.1理解构造函数

构造函数是一个对象,可以包含属性和方法,但是每一次实例化后得到的都是不一样的实例对象(方法不能共享)简单来说就是一家四口人,要买四个冰箱(冰箱是同一个构造函数,但是冰箱的操作[也就是构造函数里的方法]是独有的)才能满足生活所需,因为每个人用的冰箱的操作是不一样的,是独有的,而非共享,所以母亲的冰箱,只能母亲去操作,而其他人都不会操作,所以会导致资源的浪费

2.原型链

2.1prototype原型属性

每一个构造函数都有一个prototype原型属性指向一个原型对象,定义在这个原型对象上的方法可以被共享,也就是说我们可以利用这个方式去解决一家四口遇到的困难,让只需要一个冰箱就能解决家庭所需
    function Myfn(name){
      this.name = name;
    }
    // 注意:
    Myfn.prototype.sing = function(){ // 将需要被共享的属性放在prototype上,就可以解决家庭困难,让大家都能统一的操作冰箱
      console.log('你好')
    }
    // 每调用一个构造函数,就会产生一个不一样的对象
    let father = new Myfn('father');
    let mother = new Myfn('mother');
    let daughter = new Myfn('daughter');
    let son = new Myfn('son')
    console.log(mother.sing === father.sing); // true
    console.log(mother.sing === daughter.sing); // true
    console.log(mother.sing === son.sing); // true

2.2__proto__原型链节

每一个原型对象(构造函数的prototype)以及实例对象都有 __proto__ 原型链节(有两个下划线)
    function Myfn(name){
      this.name = name;
    }
    Myfn.prototype.sing = function(){
      console.log('你好')
    }
    // 每调用一个构造函数,就会产生一个不一样的对象
    let father = new Myfn('father');
    let mother = new Myfn('mother');
    let daughter = new Myfn('daughter');
    let son = new Myfn('son')
    console.log(mother.sing === father.sing); // true
    console.log(mother.sing === daughter.sing); // true
    console.log(mother.sing === son.sing); // true
    console.log(Myfn.prototype.__proto__) // Object对象
    console.log(Myfn.prototype.__proto__.__proto__) // null
    console.log(mother.__proto__ === Myfn.prototype) // true
    console.log(Myfn.prototype.constructor) // f Myfn(name){} 构造函数

2.3原型链继承图

例如实例对象上使用方法,会先检查自己有没有这个方法,没有的话沿着__proto__原型链节往上找,如果找到Object对象还找不到就会返回错误,如果能找到就会执行方法,这就是原型链的继承,也就是说父亲的技能,会被你继承,你也就可以相应的使用这个技能(对应的方法),这也是为什么挂载在prototype上面的方法可以被共享的说明
    function Myfn(name){
      this.name = name;
    }
    let myfn = new Myfn('ldh')
    console.log(myfn.sing()); // 这里会报错,因为没有定义 sing 方法
    // 演示继承 (ES5的继承方式)
    function Myfn(name){
      this.name = name;
    }
    Object.prototype.sing = function(){ // Object原型对象上定义sing方法
      console.log('nihao');
    }
    let myfn = new Myfn('ldh')
    console.log(Myfn.prototype.sing()); // 正常输出,输出过程:查找自己有没有sing方法,没有就 
                                        // 往Object对象上查
    console.log(myfn.sing());  // 正常输出,输出过程:查找自己有没有sing方法,没有就往 
                               // Myfn.prototype原型对象上查,还是没有,继续往上查,查找Object 
                               // 对象上是否有sing方法

3.class类

3.1理解class类的背景

当接触了构造函数和原型链之后,我们就可以很好的介绍Class类的来源了,我们都知道构造函数的好处就是可以创建自己的属性和方法,但是不足之处在于不能共享方法,造成不必要的资源浪费,这个时候出现了原型链解决了这个问题,但是原型链面对几个方法还好,但是几十个几百个方法呢?那就会出现代码冗余的情况,这个时候出现了Class类,解决了构造函数和原型链的所有问题
    // 原型链解决共享问题出现代码冗余
    function Myfn(name){
      this.name = name;
    }
    // 下面五个方法,我们就要挂载五次,Myfn.prototype 是重复的,导致如果有几十个或者几百个方法就会造成大量代码冗余
    Myfn.prototype.sing = function(){
      console.log('你好');
    }
    Myfn.prototype.song = function(){
      console.log('你好');
    }
    Myfn.prototype.weak = function(){
      console.log('你好');
    }
    Myfn.prototype.running = function(){
      console.log('你好');
    }
    Myfn.prototype.pushing = function(){
      console.log('你好');
    } 

3.2创建class类

1.class(小写)标识这是一个类,紧接着就是跟上类名,随后是大括号{},这就完成了类的定义
2.类里面有一个constructor方法,即构造方法;this关键字代表实例对象
3.对于普通方法,不需要加上function关键字,直接把函数定义放进去即可
4.方法之间不需要逗号分隔,加了会报错
5.使用的时候,直接对类使用new命令,跟构造函数的用法完全一致,并且类必须用new调用,否则会报错(构造函数不用new不会报错)
      // 创建 class Start{} 类
      class Start{
      constructor(name,age){ // 这里面就是一个构造函数
        this.name = name;
        this.age = age;
      }
      sing(){ // 方法不需要加上function关键字
        console.log('我会唱歌');
      } // 方法之间不需要用逗号隔开
      running(){
        console.log('我会跑步')
      }
    }
    // 调用方式   
    let start = new Start('ldh',23);
    start.sing();
    start.running();
    console.log(start.sing === start1.sing); // true,证明了方法共享
    // 以上代码等同于以下代码
    function Start(name,age){
      this.name = name;
      this.age = age;
    }
    Start.prototype.sing = function(){
      console.log('我会唱歌');
    }
    Start.prototype.running = function(){
      console.log('我会跑步');
    }   
    // 调用方式
    let start = new Start('ldh',23);
    start.sing();
    start.running();  

3.3class类中的方法分类

1.类方法内部含有this关键字的,称为“实例方法”
2.类相当于实例的原型,类中定义的方法都会被实例继承,如果在一个方法前加上static关键字,表示该方法不会被实例继承,而是通过类来调用,这就称为‘静态方法’,除了“静态方法”,其余的方法都被实例对像调用
    class Start{
      constructor(name,age){
        this.name = name; 
        this.age = age;
      } 
      static sing(){  // 静态成员
        console.log('我会唱歌')
      }
      running(){  // 实例成员
        console.log('我会跑步')
      }
    }
    let start = new Start('ldh',23);
    let start1 = new Start('hzh',23);
    start.sing(); // 报错,因为此时的sing方法被static定义,我们只能通过类来调用
    Start.sing(); // 成功输出 '我会唱歌'
    start.running();

3.4class类里面的注意点

1.类内部的方法都是不可枚举的,构造函数内的方法可枚举(也就是说不能使用for...in去遍历)(js基本包装类型的原型属性是不能被遍历因为是不可枚举的)
// 1.不可枚举,也可以简单理解属性挂载在了原型属性上,所以不能枚举,从而不能被遍历
    class Myfn{
      constructor(name){  // 构造函数里面的属性和方法可以被枚举,也自然可以被遍历   
        this.name = name;  
        this.song = function(){
          console.log('你好');
        }
      }
      sing(){ // 类里面的方法是被挂载在prototype原型属性上的,所以不能被枚举,也就是不能被遍历
        console.log('你好');
      }
    }
    let myfn = new Myfn('ldh');
    console.log(Object.keys(myfn)); // [name,song]
    console.log(Object.getOwnPropertyNames(myfn)); // [name,song]
    let my = Object.getOwnPropertyNames(myfn); 
    for(var item in my){
      console.log(my[item]); // name song
    }
2.类里面必须有一个constructor方法,如果没有显示指定,将自动添加一个空的constructor方法,通过new命令生成对象实例时,自动调用该方法,默认返回实例对象this
// 2.默认的constructor构造函数
    class Myfn{
      sing(){
        console.log('你好');
      }
    }
    let myfn = new Myfn('ldh');
    console.log(myfn.__proto__); // 默认含有constructor
    console.log(Object.keys(myfn)); // [],表明默认含有constructor
    console.log(Object.getOwnPropertyNames(myfn)); // [],表名默认含有constructor
3.实例属性除非显式定义在本身(即定义在this对象上),否则都定义在原型上,所有实例共享原型对象。
4.与ES5一样,可以使用get和set关键字,对某个属性设置存值函数与取值函数,拦截该属性的存取行为
// 4.使用get和set关键字,对某一个属性进行存值或者取值,拦截该属性的存取行为
    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: ' + value);
      }
    }
    let inst = new MyClass();
    inst.prop = 123; // 调用 set 设置值 setter: 123
    console.log(inst.prop)// 调用 get 获取值 getter 
5.类和模块内部默认是严格模式,不需要使用use strict指定运行模式。
6.我认为class类是有变量提升的,只不过它也存在暂时性死区(欢迎指正)
// 6.个人认为类存在变量提升,只不过也存在暂时性死区这个说法(欢迎指正)
    new Foo(); // ReferenceError:Cannot access 'Foo' before initialization 
    class Foo {}

3.5class类的继承

1.extends关键字,通过extends关键字实现继承,比ES5的通过原型链实现继承清晰和方便的多
// 1.extends关键字实现继承
    class Animal{
      constructor(name,age){
        this.name = name;
        this.age = age;
      }
      running(){
        return '跑'
      }
      myname(){
        return this.name
      }
    }
    class Dog extends Animal{

    }
    let dog = new Dog('大黄');
    console.log(dog.running()); // 跑
    console.log(dog.myname()); // 大黄
2.super关键字,super关键字既可以用来当做函数使用,也可以当做对象来使用,子类必须
(1)super作为函数: 代表父类构造函数,ES6要求子类构造函数必须执行一次super函数。作为函数时只能用在子类的构造函数中,用在其他地方会报错!
注意:super虽然代表了父类Animal的构造函数,但是返回的子类Dog的实例,即super内部的this指的是Dog的实例,因此super()相当于Animal.prototype.constructor.call(this)
// 2.1 super作为函数,参数为父类构造函数的参数,有多少写多少
    class Animal{ // 父类
      constructor(name,age){
        this.name = name;
        this.age = age;
        this.sing = function(){
          return '你好'
        }
      }
      running(){
        return '跑'
      }
      myname(){
        return this.name
      }
    }
    class Dog extends Animal{ // 子类继承
      constructor(name,age,sex){
        super(name,age) // 子类的constructor必须有super关键字,super这里作为函数,参数为父类 
                        // 构造函数的参数,想承接哪些属性,就写哪些属性,没有承接的属性,会在 
                        // 子类的实例对象中显示,但对应的值为undefined
        this.sex = sex
      }
      run(){
        return '继续跑'
      }
    }
    let animal = new Animal('大黄',32,'公的'); // 不会有 sex ,因为父类不能使用子类的属性
    let dog = new Dog('小黄',23,'公的');
    console.log(animal,dog)
    console.log(animal.run()); // 会报错,父类不能用子类属性之外,也不能用子类的方法,
                               // 但是子类能用父类的任何方法或通过super承接的属性
(2)super作为对象: 只能调用父类的实例成员,对于父类实例对象上的方法和属性,无法通过super调用;在实例成员中,super调用父类的实例成员,this指向子类(Dog),而在静态成员(static标识的方法)中,this指向子类继承(class Dog extends Animal{...})
// 2.2 super作为对象
    class Animal{
      constructor(name,age){
        this.name = name;
        this.age = age;
        this.x = 1;
        this.sing = function(){
          console.log(this.x); // 2
        }
      }
      running(){
        console.log(this.x); // 2
        return 'x';
      }
      static myname(){  
        console.log(this); // 静态成员,则this指向 class Dog extends Animal{...}
      }
    }
    class Dog extends Animal{
      constructor(name,age,sex){
        super(name)
        this.sex = sex
        this.x = 2
      }
      run(){
        super.song(); // super不能用于调用父类实例上的方法,所以会报错
        super.running(); // 2,说明调用父类原型上的方法时,this是指向子类的
      }
      static myrun(){
        super.myname();
      }  
    }
    let animal = new Animal();
    let dog = new Dog();
    animal.song = function(){
      console.log('我会唱歌')
    }
    console.log(dog.sing()); // 2
    console.log(dog.run()); // 2
    console.log(dog.myname()); // 报错,原因是,静态成员只能由类来调用,而不是类实例来调用
    console.log(Dog.myname()); // 静态成员,则this指向 class Dog extends Animal{...}
    console.log(Dog.myrun()); // 静态成员,则this指向 class Dog extends Animal{...}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执迷原理

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值