深入理解 JavaScript 中的 class

深入理解 JavaScript 中的 class(类)

学习内容:

1、 掌握ES6中创建类和对象的方式 2、 掌握ES6中类的继承 3、 掌握ES6中静态方法的使用和调用 4、 理解ES6中实例化对象的原理

一、ES5:function

1、构造函数

​ 封装、继承、多态是大多OOP语言都支持的特性,而JavaScript在ES5中没有提出真正意义上的类、继承的概念。它通过函数首字母大写的方式告知开发者这是一个构造函数或者类(有些人会将它理解为类),但从严格意义上来讲,它是构造函数。这对于初学者来说,看起来是非常脑溢血的。比如下方code:

// 构造函数
        function Person(name, age) {
            // 实例属性
            this.name = name;
            this.age = age;
            // 实例方法
            this.speak = function() {
                console.log(`你好,我的名字叫${this.name},今年${this.age}岁了。`)
            }
        }
        // 实例化对象,并传入参数
        let father = new Person("张三", 20);
        // 调用实例属性
        console.log(father.name);
        // 调用实例方法
        father.speak();

​ 如同上方code所示,我们通过function关键字+首字母大写的方式告知开发者,这不是普通的函数,而是一个特殊的函数—构造函数。如果首字母小写则将变成一个普通函数,意义就立马改变了。并且它们的调用方式也不同,普通函数就是以函数的方式调用函数名();,而构造函数可以以这样的方式调用,同时也可以使用new关键字来实例化对象。

2、静态方法

​ 在大多数OOP语言中,静态基本上都是为了共享数据,以减少实例对象所造成的内存消耗。但是ES5中并没有提出相对明确的静态方法。但是,我们可以通过原型对象来实现静态方法。比如下方code:

// 构造函数
        function Father(name) {
            // 实例属性
            this.name = name;
            // 实例方法
            this.run = function() {
                console.log(`我叫${this.name},我会跑!`)
            }
        }

		// 静态属性
		Father.age = 20;

        // 给Father构造函数的原型中添加方法
        Father.prototype = {
            // 重新指向构造函数
            constructor: Father,
            // 原型方法
            speak: function() {
                console.log("我会说话!");
            },
            run: function() {
                console.log("我会跑!");
            }
        };
        // 实例化对象
        var father = new Father("张三");
		//调用静态属性
		consloe.log(Father.age);
        // 调用原型方法
        father.speak();

​ 在上述code中,我们通过给构造函数的原型以对象的赋值,并将指针重新指向构造函数的方式来给构造函数添加静态方法,而静态属性我们使用构造函数名.属性名=属性值;的方式实现,调用的时候使用构造函数名.属性名;的方式调用。

3、继承

​ 在大多数OOP语言中,继承基本上都是为了减少code冗余,提高code的可读性和灵活性以及可维护性而存在的。但是JavaScript在ES5中并没有提出相对明确的继承概念。但是在ES5中,我们可以通过原型链和一些方法来实现真正意义上的继承。比如下方code:

 // 基类构造函数
        function Father(name, age) {
            // 实例属性定义在构造函数内部
            this.name = name;
            this.age = age;
            // 判断原型中的该属性是否是函数类型的,也就是判断原型中是否有该方法
            if ((typeof Father.prototype.speak) !== "function") {
                // 方法定义在原型中
                Father.prototype.speak = function() {
                    console.log(`我叫${this.name},今年${this.age}岁了`)
                }
            }
        }

        // 派生类构造函数
        function Son(name, age, sex) {
            // 调用基类的构造函数,相当于把基类中的属性添加到未来的派生来实例对象中
            // 将Father基类中的this指向改变为son的实例,并将基类所需要的参数传递
            Father.call(this, name, age);
            // 实例对象属性
            this.sex = sex;
        }

        // 修改派生类的原型,这样就可以继承基类原型中的方法
        Son.prototype = new Father();
        // 实例化son,并将基类所需要的参数一并传递
        var son = new Son("小明", 20, "男");
        console.log(son.name);
        console.log(son.age)
        son.speak();

​ 在上述code中,我们可以看出派生类通过Son.prototype = new Father();更改派生类原型为基类实例的方式来继承基类中的属性和方法,而通过Father.call(this, name, age);修改this指向来调用基类的实例属性和方法,将其变成派生类实例的属性和方法,假如实例对象中有与原型同名的属性和方法则默认替换。在ES5中它是以这样的原理来实现继承的。

​ 但是在实际开发中,以这样的方式实现静态、继承确实是有些麻烦,于是乎JavaScript在ES6中正式提出了class(类)、constructor(构造函数)、extends(继承)、static(静态)的概念。

二、ES6:class

​ 2015年6月JavaScript正式发布ES6版本,并且最重要的特点之一就是,引入了class的概念,使全球js开发者收益,使得js开发者终于告别了,直接使用原型对象模仿面向对象中的类和类继承的时代。

​ 但是js中并没有一个真正的class原始类型,class仅仅只是对原型对象运用的语法糖。所以,只有理解如何使用原型对象实现类和类继承,才能真正地用好class。那么先上code让大家体验一下:

class Person {
            // 构造函数
            constructor(age) {
                this.age = age;
            };
            // 静态方法
            static speak = function(age) {
                console.log(`我的名字叫${Person.uName},今年${age}岁了`)
            };
        }
        // 静态属性
        Person.uName = "你好";
        // 实例化对象
        var father = new Person(20);
        // 调用属性和方法
        console.log(Person.uName);
        Person.speak(father.age);
        // 试图修改静态属性,但是不可以
        Person.name = "小明";
        console.log(Person.uName);

​ 我们看到出现了一个个新的关键字,关键字class用于声明一个类,关键字constructor用于声明构造函数,关键字static用于声明静态方法,而静态属性则于ES5中一样,同样只能写在类的外边。并且静态属性和方法不能被实例对象所调用,只能被类所调用。

​ ES6的语法很简单,并且也很容易理解。但是在实例化的背后,究竟是什么在起作用呢?

三、class实例化背后的原理

​ 使用class 的语法,让开发者告别了使用prototype原型对象模仿面向对象的时代。但是,class并不是ES6引入的全新概念,它的原理依旧是原型对象和原型链。

​ 我们前边不是学习了测试数据类型的知识嘛,那么我们是不是就可以使用typeof来检测类的类型,请看下方code:

// 声明一个类 
class Person {
            // 构造函数
            constructor(age) {
                this.age = age;
            };
            // 静态方法
            static speak = function(age) {
                console.log(`我的名字叫${Person.uName},今年${age}岁了`)
            };
        }
        // 静态属性
        Person.uName = "你好";
        // 实例化对象
        var father = new Person(20);
 		// 测试Person的类型
        console.log(typeof Person);

在这里插入图片描述

​ 通过typeof数据类型测试,我们惊奇的发现,class并非什么全新的数据类型,它实际上只是function函数类型。

​ 为了能够更加直观的了解Person类,我们可以将它的实例对象在控制台上输出,结果如下:

在这里插入图片描述

​ 我们发现Person实例对象的属性并不多,除去内置属性外,大部分属性根据名字都能明白它的作用。其中需要我们关注的两个属性是prototype原型对象__proto__实例对象的原型。相信大家肯定会问:不是ES5中的原型和原型链嘛?没错,这就是原型对象。也就是说使用class创建对象的方式就是隐式的使用原型对象和原型链。

分析:

1.	ES6中`声明类和构造函数`其实就等于ES5中使用`function`声明的构造函数
2.	而静态方法和静态属性其实就是在原型对象中设置的属性和方法
3.	在实例化对象的时候,浏览器自动创建了一个对象(原型对象),并且将该对象所属类的构造函数与原型对象绑定,或者说原型对象记录了构造函数的引用并指向了构造函数,而构造函数的`prototype`属性指向了原型对象
4.	当通过实例属性调用静态属性和方法的时候,实例属性的`__proto__`属性又指向了原型对象
5.	那么这无疑是ES5中构造函数、实例对象与原型对象的三角关系

在这里插入图片描述

​ **温馨提示:**如果有看不懂的朋友,请看博主上两篇博文,详细讲解了原型对象和基与原型对象的继承。

四、继承

​ 在传统面向对象中(ES5面向对象),类是可以继承类的,这样派生类就可以使用基类中的属性和方法,来达到code复用的目的。

​ 当然,ES6中也可以实现继承。ES6中明确提出了类的继承语法,即使用extends关键字,并使用super()方法调用基类的构造函数。其实super()类似于call()中的借调继承。请看下方code:

     // 基类
        class Anima {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }
            say() {
                console.log(`我的名字叫${this.name},今年${this.age}岁了`);
            }
        }
        // 派生类继承基类
        class Dog extends Anima {
            constructor(name, age, sex) {
                // 调用基类的构造
                super(name, age)

                // 实例成员
                this.sex = sex;
                // 静态成员
                Dog.color = "红色";
                // 静态方法
                Dog.run = function() {
                    console.log("我想睡觉");
                }
            }

            // 方法重写
            say() {
                console.log(`我的名字叫${this.name},今年${this.age}岁了,颜色是${Dog.color}`);
            }
        }
        var dog = new Dog("大黄", 18, "男");
        dog.nm = "哈哈"
        dog.say()
        Dog.run();
        console.log(dog);
        console.log(Dog);

​ 实际上ES6中的继承与ES5中的继承原理是一样的,其本质都是基与原型对象和原型链的继承。

五、对象常用方法

  1. Object.key(对象):获取当前对象中的属性名,并返回一个包含所有属性名的数组。
  2. Object.defineProperty():该方法用于设置、修改、添加对象中的属性
Object.defineProperty(对象,修改或新增的属性名,{
		value:修改或新增的属性的值,
		writable:true/false,//如果值为false 不允许修改这个属性值
		enumerable: false,//enumerable 如果值为false 则不允许遍历
        configurable: false  //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})	

体验:

 		// 基类
        class Anima {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }
            say() {
                console.log(`我的名字叫${this.name},今年${this.age}岁了`);
            }
        }

        // 派生类继承基类
        class Dog extends Anima {
            constructor(name, age, sex) {
                // 调用基类的构造
                super(name, age);
                // 实例成员
                this.sex = sex;
                // 静态成员
                Dog.color = "红色";
                // 静态方法
                Dog.run = function() {
                    console.log("我想睡觉");
                }
            }

            // 方法重写
            say() {
                console.log(`我的名字叫${this.name},今年${this.age}岁了,颜色是${Dog.color}`);
            }
        }
		// 实例化dog对象
        var dog = new Dog("大黄", 18, "男");
		// 设置dog对象中的sex属性
        Object.defineProperty(dog, "sex", {
            value: "小明",
            writable: false,
            enumerable: false,
            configurable: false
        });
        // 试图修改,但是并不能修改
        dog.sex = "女";
		// 打印的还是 小明
        console.log(dog.sex);
		// 以数组的形式获取对象中所有对象的属性
        // 注意:由于我们上边设置sex属性不可遍历,所以无法通过key()方法获取到sex属性,因为key()方法的原理是通过遍历查找的
        console.log(Object.keys(dog));

六、总结

​ 虽然ES5中,class(类)的继承和静态方法要比ES5中的语法简单,好理解。但是js中并没有一个真正的class原始类型,class仅仅只是对原型对象运用的语法糖。

​ 简单来说,js中不管是ES5还是ES6中的继承和静态方法的实现都是基与原型对象和原型链实现的。所以,只有理解ES5中如何使用原型对象实现类和类继承,才能真正地用好ES6中的class。希望大家不要因为ES6的语法简单而忽略ES5中的知识。

不管是ES6也好,ES7也罢,基本上都是基与ES5来封装实现的,所以希望各位博友能够先学好ES5。同时也希望大家能够广集意见,如果博主有说的不对的地方还请给位博友纠正。当然如果还有不懂的地方,博主也会给大家详细解释。最后感谢各位博友的支持,以及感谢CSDN能够提高这样好的一个平台,谢谢!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

御弟謌謌

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值