ES6 面向对象 最最最全 ' 类 ' 的讲解 (超详细篇) (精华帖)

ES6 面向对象简介

面向对象:面向对象不是一种编程语言,他是一种编程思想,跟具体的语言无关

对比面向过程:

  • 面向过程:思考的切入点是功能的步骤
  • 面向对象:思考的切入点是对象的划分

面向对象不是说,我写的程序里面有对象就是面向过程,比如说C语言,他也能模拟出一个类似于对象的结构体,并不是说他不能有对象而是说他思考的切入点是步骤而是从这个角度来思考程序的。

我们来拿把大象装进冰箱来举栗子

当我们面向过程时需要拆分他的步骤

我们在这里用伪代码来给大家开展示一下


	function 第一步打开冰箱门() {我们应该怎么做}
	function 第二步把大象放进去() {我们应该怎么做}
	function 第三步关上冰箱门() {我们应该怎么做}

在这里我们将整件事去拆分成一个一个步骤,以每一步去干什么,来作为整件事件的基准线。

当我们用面向对象的方式来写时


	function 大象() {这是个大象}
	function 冰箱() {这是个冰箱}
	
	大象.proptype.他能走 = function () {怎么走}
	大象.proptype.他能被放进去 = function () {怎么放进去}
	大象.proptype.他能被拿出来 = function () {怎么拿出来}
	
	冰箱.proptype.他能开门 = function () {怎么开门}
	冰箱.proptype.他能关门 = function () {怎么关门}
	冰箱.proptype.他能制冷 = function () {怎么制冷}

由此可见面向对象的方式不是去寻求步骤了,也不是不能有步骤,而是将对象按功能组件划分,不去找步骤,而是我需要什么去完成整个事件,用什么来完成。

在这里不是说面向对像好,面向过程不好,他们各有各的优点,在一些小型项目中面向过程,更加方便、便捷,比面向对象要好很多,而在大型项目中用面向对象的方法来编写会更加方便,这样更加条理分明,分工明确,此时面向过程的思路就有点捉襟见肘欧了。

类:构造函数语法糖

传统构造函数的问题

  1. 属性和原型方法定义分离,降低了可读性。
    那么我们以前是怎么写的呢
	
	//首先我们创建一个构造函数,构造器
	function demo (type, name) {
		this.type = type,
		this.name = name
	}
	
	//再在函数的原型上加上方法(定义实例方法,原型方法)
	demo.prototype.log = function () {
		coansole.log('属性: ${this.type}');
		coansole.log('名字: ${this.name}');
	} 

  1. 原型成员可以被枚举
    并且当对其进行for in循环时他不光能把自己的属性输出,并且还可以吧原型上的属性输出,而实际上绝大部分时候,我们在遍历一个成员时我们是不希望遍历其原型上的属性的,我们只希望遍历他啊本身的东西,那能不能控制呢,能,但是稍显麻烦。

  2. 默认情况下,构造函数仍然可以被当做普通函数使用

他从逻辑上应该是一个整体,但是我们现在是把它分开的,那么如果说它们之间有1000行代码,那样的话
写着写着就将它们分开了,但是从逻辑上来讲它们是一个整体的,所以在面向对象中ES6对其做了非常大的改进,我们将一个对象的所有成员的定义,统称为类。

类的特点

在以前ES5里面还没有类这个概念,我们都是用构造函数来创建对象的,成员和原型方法是分开来写的
书写方法:

  • 首先写一个class
  • 然后写上类的名称,相当于构造函数的名称
  • 然后写上{}大括号
  • 然后写上关键字constructor是类的构造器,相当于用constructor来定义他的函数
  • 然后写上属性和方法
  • 写原型方法时不需要写functionprototype有点相当于方法速写,直接写上方法名字后面是参数列表

	class demo {
		constructor (type, name) {
			this.type = type,
			this.name = name
		}
	}
	
	log () {
		coansole.log('属性: ${this.type}');
		coansole.log('名字: ${this.name}');
	}

这样代码看上去就更加像一个整体了,这样的代码它是不是可以实现一样的功能,并且这样的逻辑更加合理,并且它本身是一个整体。

并且你写的这些类上的实例方法他会把它们全部放到原型上面去,而且他会把原型成员自动设置为不能枚举的,for in循环时只会循环到他自己的属性,会自动能够把原型上的属性和方法给屏蔽掉,而且他并不能被当做正常函数使用,所以说在ES6中它有更加严格的语法环境,所以说在使用类的时候就必须按照类的面向对象的方式来使用。

特点:

  1. 类声明不会被提升,与letconst一样,存在暂时性死区
  2. 类中的所有代码,均在严格模式下执行
  3. 类的所有方法都是不可枚举的
  4. 类的所有方法内部都无法被当作构造函数使用
  5. 类的构造器必须使用new来使用

类的其他书写方式

  1. 可计算的成员名
    可能我并不知道这个class里的方法叫什么名字,可能他来自另一个变量

现在我们假设有一个变量它的属性值我需要将这个变量的属性值当做这个变量的名字只需要在方法名上加一个中括号[]放上去就可以了,这样在调用时就可以直接使用变量名了。


	const test = 'demo';
	[test]() {
		coansole.log('属性: ${this.type}');
		coansole.log('名字: ${this.name}');
	}
	
  1. Objectsetter

在ES5里面Object.defineProperty可以定义某个对象成员属性和设置,在以前的时候要略显麻烦一些。我们来模拟一下,做成一个类似的效果。


	//当我们需要将age进行操作时就可以这样
	class Demo {
		constructor (type, name, age) {
			this.type = type,
			this.name = name,
			this.age = age
		}
		
		//创建一个age属性,并给他加上getter,读取该函数时时,会运行该函数
		get age() {
			return this._age + "岁";
		}
		//创建一个age属性,并给他加上setter给该属性赋值时,会运行该函数
		set age(age) {//当其年龄大于1000小于0时的情况对其赋值
			if (typeof age !== "number") {
				throw new TypeError("age proper must be number")
			}
			if (age < 0) {
				age = 0;
			}else if (age > 1000) {
				age = 1000;
			}
			this._age = age;
		}
	}

使用gettersetter控制属性,不在原型上。
这是我们在类中可以创建的这种写法更加舒服也更方便

  1. 静态成员
    函数的本身也是对象,我们希望给函数本身加上一些成员

	class Demo {}
	Demo.abc = 123;

构造函数本身的成员就是静态成员,对象里面的成员叫做实例成员,原型上的成员也叫实例成员

在这里插入图片描述

因为都是通过 对象. 来访问的,所以new__proto__之后的都是实例对象,直接放到函数里面的就叫做静态成员,它是不能通过对象来访问的,不管你有没有对象,我都能通直接通过构造对象来访问,这叫静态成员。


	class Demo {
		constructor(name) {
			this.name = name;
			this.width = 50;
			this.height= 50;
		}
	}
	
	const demo1 = new Demo('1');
	const demo2 = new Demo('2');
	const demo3 = new Demo('3');
	//当我们这样创建一个class时这样写在里面直接赋值时是没问题的
	//但是当我们需要访问宽高时就会有一个问题
	//因为所有的宽高都是完全一样的我们直接获取就行,但是我获取时就需要创建一个对象出来才能获取宽高
	const Demo = new Demo('1')
	//这是不是就很怪异,既然所有的宽高都是一样的那么我为什么还要在创建一个呢
	//而且在我们创建完之后每一个里面都会有一个宽高,它不仅仅是浪费内存空间么简单
	//有时候我们都会有一些困惑,当我们需要获取一个对象的宽度时我们还要创建一个对象
	//因此像这种属性我不应该作为实例属性,我们不应该在定义下来时有宽高,而是在没有定义下来时就有宽高
	//静态属性就应该放到这
	Demo.width = 50;
	Demo.height = 50;
	//那么这样的情况下我们就不需要它存在所有的宽高都是一样的
	//那么这就是静态属性,他不依赖具体的对象,他是跟类相关的跟函数本身相关的。
	

那么这是之前的写法,这种之前的写法不好,为什么呢?
因为这种写法就又将它们分开了,他们本身是一个整体。ES6里面说,我们也要将静态方法写在类里边。

使用static关键字定义的成员即静态成员


	class Demo {
		constructor(name) {
			this.name = name;
		}
		static width = 50;
		static height= 50;
	}

用这种方法跟刚才那种效果是完全一样的,这就是静态成员的速写方式,当然,这不仅是属性,方法也是可以的。


	class Demo {
		constructor(name) {
			this.name = name;
		}
		static test() {}
	}
	//这样调用
	Demo.test()

  1. 字段初始化 (ES7)

	class test {
		a = 1;
		b = 2;
		c = 3;
	}

在ES7里面可以用字段初始化的方法将直接赋值的参数,直接放到类里。构造函数可以不用写了。当我们new之后他就会有一些实例成员,因为我们没有加static就变成了他的实例成员了。他不是在原型上面的,实在对象里面的,他就相当于将这些放到constructor这就是字段初始化器。

注意:

  • 使用static的字段初始化器,添加的是静态成员
  • 没有使用staict的字段初始化器,添加的成员位于对象上
  • 箭头函数在字段初始化器位置上指向当前对象
  1. 类表达式
	
	const A = class{//匿名类,表达式
	//里面的代码都是一样的
	}

6.装饰器(ES7)(Decorator

这里的是一些扩展的知识,还并未纳入到现在的正式标准,所以点到即止,因为这里在以后可能会发生一些比较大的改动。

在当一些代码中有些方法过时了,我们有更新的方法之后,怎么办。我们不能吧这个代码删掉因为,有可能在一些旧的系统中它还在使用,如果删了的话它就可能会报错,那我们怎么办呢,而当我们有一天万一这个方法又能用了,又不过时了,我们如果通过直接改动函数体的方式来执行,就太low了,太麻烦了,并且改着改着就有可能出问题了。

这是一个典型的横切关注点的问题

那么对于横切关注点来说最合适的就是使用装饰器它的语法是:


	 	class Test {
			@Obsolete
			print() {
				console.log("123")
			}
		}

		function Obsolete(target, mnethodName, descriptor){
			//这个函数有三个参数
			//第一个是类名就是类本身第二个是成员的名字第三个参数是放大的descriptor
			//这三个参数分别输出
			//function Text
			//print
			//{ value : function print (){}, ... }
			//	console.log(target, mnethodName, descriptor);
			//这样我就可以用他来做,这里的value不是可以接收什么参数吗
			//我不管你接受什么参数,因为我也不一定是来修饰他的
			//我可以用来修饰任何方法把它修饰为过期
			//我们先把他原来的value保存一下
			const oldFunc = descriptor.value
			descriptor.value = fuction (...args){
				//一开始先输出一个
				console.warn( ${methodName}方法已过时 );
				//然后过时了不是不能用,只是提示你一下过时了,然后我们再去调用这个函数
				oldFunc.apply(this, args);
			}
		}
		

装饰器的本质是一个函数

因为这现在还是一个并未成为正式标准的,所以浏览器并不能让你看到的他会报错的,显示语法错误。

类的继承

如果两个类A和B,如果可以描述为: B 是 A, 则 A 和 B 形成继承关系,就是 A 里面完全包括了 A 。

如果 B 是 A ,则:

  • B 继承自 A
  • A 派生 B
  • B 是 A 的子类
  • A 是 B 的父类

如果 A 是 B 的父类,则 B 会自动拥有 A 中的所有实例成员。

而关于继承,在ES6中出现了一个新的继承方法extrnds关键字super

  • extends:继承,用于类的定义
  • super
    • 直接当做函数调用,表示父类构造函数
    • 如果当做对象使用,则表示父类的原型
	
	//test继承自Demo
	class test extends Demo {
		constructor(a, b, c) {
			super(a, b, c);
			//子类特有的属性
			this.abc = 123;
		}
	}

注意:

ES6要求,如果定义了constructor,并且该类是子类必须在constructor的第一个行手动调用父类的构造函数,如果子类不写constructor,则会有默认的构造器需要的参数和父类一致,并且自动调用父类构造器

冷知识:

  • js制作抽象类
    • 抽象类:一般是父类,不能通过该类创建对象
  • 正常情况下,this的指向,this始终指向具体类的对象

我们想啊当我们创建对象时比如说创建个人new时我们从逻辑上来说不能创建个人,因为这个世界上没有人,只有你我他,或者说你告诉我谁叫动物它可以是猫狗猪,但是谁叫动物,所以说我们在new时就应该不能让他们创建对象,需要创建的是他们下边的子类。

由此可引出下面的代码

	
	class Animal {
	    constructor(type, name, age, sex) {
	    //我们可以通过new.target来判断其是不是指向的本身
	        if (new.target === Animal) {
	        	//当他是new创建的时扔出一个错误
	            throw new TypeError("你不能直接创建Animal的对象,应该通过子类创建")
	        }
	        this.type = type;
	        this.name = name;
	        this.age = age;
	        this.sex = sex;
	    }
	}

好了,以上就是我所理解的关于面向对象 和类的全部相关用法了,希望对你们有所帮助。
如果有什么问题欢迎在下面评论~~~

发布了27 篇原创文章 · 获赞 5 · 访问量 2712

猜你喜欢

转载自blog.csdn.net/function_zzc/article/details/104604583
今日推荐