面向对象:面向对象不是一种编程语言,他是一种编程思想,跟具体的语言无关
对比面向过程:
面向对象不是说,我写的程序里面有对象就是面向过程,比如说C
语言,他也能模拟出一个类似于对象的结构体,并不是说他不能有对象而是说他思考的切入点是步骤而是从这个角度来思考程序的。
我们来拿把大象装进冰箱来举栗子
当我们面向过程时需要拆分他的步骤
我们在这里用伪代码来给大家开展示一下
function 第一步打开冰箱门() {我们应该怎么做}
function 第二步把大象放进去() {我们应该怎么做}
function 第三步关上冰箱门() {我们应该怎么做}
在这里我们将整件事去拆分成一个一个步骤,以每一步去干什么,来作为整件事件的基准线。
当我们用面向对象的方式来写时
function 大象() {这是个大象}
function 冰箱() {这是个冰箱}
大象.proptype.他能走 = function () {怎么走}
大象.proptype.他能被放进去 = function () {怎么放进去}
大象.proptype.他能被拿出来 = function () {怎么拿出来}
冰箱.proptype.他能开门 = function () {怎么开门}
冰箱.proptype.他能关门 = function () {怎么关门}
冰箱.proptype.他能制冷 = function () {怎么制冷}
由此可见面向对象的方式不是去寻求步骤了,也不是不能有步骤,而是将对象按功能组件划分,不去找步骤,而是我需要什么去完成整个事件,用什么来完成。
在这里不是说面向对像好,面向过程不好,他们各有各的优点,在一些小型项目中面向过程,更加方便、便捷,比面向对象要好很多,而在大型项目中用面向对象的方法来编写会更加方便,这样更加条理分明,分工明确,此时面向过程的思路就有点捉襟见肘欧了。
//首先我们创建一个构造函数,构造器
function demo (type, name) {
this.type = type,
this.name = name
}
//再在函数的原型上加上方法(定义实例方法,原型方法)
demo.prototype.log = function () {
coansole.log('属性: ${this.type}');
coansole.log('名字: ${this.name}');
}
原型成员可以被枚举
并且当对其进行for in
循环时他不光能把自己的属性输出,并且还可以吧原型上的属性输出,而实际上绝大部分时候,我们在遍历一个成员时我们是不希望遍历其原型上的属性的,我们只希望遍历他啊本身的东西,那能不能控制呢,能,但是稍显麻烦。
默认情况下,构造函数仍然可以被当做普通函数使用
他从逻辑上应该是一个整体,但是我们现在是把它分开的,那么如果说它们之间有1000行代码,那样的话
写着写着就将它们分开了,但是从逻辑上来讲它们是一个整体的,所以在面向对象中ES6对其做了非常大的改进,我们将一个对象的所有成员的定义,统称为类。
在以前ES5里面还没有类这个概念,我们都是用构造函数来创建对象的,成员和原型方法是分开来写的
书写方法:
class
类{}
大括号constructor
是类的构造器,相当于用constructor
来定义他的函数function
和prototype
有点相当于方法速写,直接写上方法名字后面是参数列表
class demo {
constructor (type, name) {
this.type = type,
this.name = name
}
}
log () {
coansole.log('属性: ${this.type}');
coansole.log('名字: ${this.name}');
}
这样代码看上去就更加像一个整体了,这样的代码它是不是可以实现一样的功能,并且这样的逻辑更加合理,并且它本身是一个整体。
并且你写的这些类上的实例方法他会把它们全部放到原型上面去,而且他会把原型成员自动设置为不能枚举的,for in
循环时只会循环到他自己的属性,会自动能够把原型上的属性和方法给屏蔽掉,而且他并不能被当做正常函数使用,所以说在ES6中它有更加严格的语法环境,所以说在使用类的时候就必须按照类的面向对象的方式来使用。
特点:
let
和const
一样,存在暂时性死区new
来使用class
里的方法叫什么名字,可能他来自另一个变量现在我们假设有一个变量它的属性值我需要将这个变量的属性值当做这个变量的名字只需要在方法名上加一个中括号[]
放上去就可以了,这样在调用时就可以直接使用变量名了。
const test = 'demo';
[test]() {
coansole.log('属性: ${this.type}');
coansole.log('名字: ${this.name}');
}
Object
和setter
在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;
}
}
使用getter
和setter
控制属性,不在原型上。
这是我们在类中可以创建的这种写法更加舒服也更方便
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()
class test {
a = 1;
b = 2;
c = 3;
}
在ES7里面可以用字段初始化的方法将直接赋值的参数,直接放到类里。构造函数可以不用写了。当我们new
之后他就会有一些实例成员,因为我们没有加static
就变成了他的实例成员了。他不是在原型上面的,实在对象里面的,他就相当于将这些放到constructor
这就是字段初始化器。
注意:
static
的字段初始化器,添加的是静态成员staict
的字段初始化器,添加的成员位于对象上
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 ,则:
如果 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
制作抽象类
我们想啊当我们创建对象时比如说创建个人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;
}
}
好了,以上就是我所理解的关于面向对象 和类的全部相关用法了,希望对你们有所帮助。
如果有什么问题欢迎在下面评论~~~