概念 在 javaScript 中我们我们有两大编程思想,面向过程(POP)和 面向对象(OOP);
面向对象的特征
继承 -> 子类继承了父类的属性方法
封装 -> 将实现同样的代码段,放到一个函数中,可以重复使用,不需要关系代码实现的细节,实现代码的高内聚,低耦合
多态
类和对象的关系
类是对象的抽象,对象是类的具体
类是对象的模板,对象是类的产品
如何创建类 使用 class 关键字,class 后面跟上类名,类名的命名使用帕斯卡命名法 (首字母大写)
如何实例化一个类 实例化一个类就是创建一个实例 (对象),使用关键字 new,后面跟上类名和实参列表,返回值是一个实例 (对象)
构造器
构造器就是类中自带的一个方法,可以理解为前缀方法
当我们创建一个实例 (对象) 的同时,他会被自动调用,即出现 new 的时候,构造器自动执行;
每个类都有构造器,即使我们不定义构造器也存在,为空
构造器的方法是固定的,为 constructor
因为 class 类没有传形参的地方,所以需要 constructor () 来传递形参,来实现动态类;
简单 demo
1 2 3 4 5 6 7 class Person { constructor ( ){ console .log ("构造器执行了" ) } } var person1 = new Person ();
创建一个动态类
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { constructor (name,age ){ this .name =name; this .age =age; } say ( ){ console .log (`name is ${this .name} ,age is ${this .age} ` ) } } var person1 = new Person ("张三" ,20 );person1.say ();
继承
一个类中存在另一个类的属性或方法,就是继承;
继承使用关键字 extends;
继承的类称为子类,也叫派生类;
被继承的类称为父类,也叫基类;
demo1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Worker { constructor (name, money ) { this .name = name; this .money = money; } kq ( ) { console .log (this .name + "的考勤" ) }; jx ( ) { console .log (this .name + "的绩效" + this .money ) } } class Boss extends Worker { skq ( ) { console .log (this .name + "正在审核考勤" ) } } var worker = new Worker ("张三" , 10000 );worker.kq (); worker.jx (); var boss = new Boss ("李四" , 15000 );boss.kq (); boss.jx (); boss.skq ()
demo2:
有时候父类定义的方法不一定能满足子类的需求,可以通过在子类中重写该方法,会直接覆盖父类的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Worker { constructor (name, money ) { this .name = name; this .money = money; } kq ( ) { console .log (this .name + "的考勤" ) }; } class Boss extends Worker { kq ( ) { console .log (this .name + "的考勤跟你不一样,俺的是年度的~~~" ) } } var worker = new Worker ("张三" , 10000 );worker.kq (); var boss = new Boss ("李四" , 15000 );boss.kq ();
继承构造器
我们可以通过刚才的方法来重写一个方法,但如果父类的构造器无法满足子类的需求,得需要继承构造器;
关键字 super;
我们需要在子类中重新定义一个构造器,并继承父类所有参数,然后在子类的构造器中,使用 super 继承父类的构造器;
其实就是子类用到的动态变量,父类没有,然后需要在子类中传形参;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Worker { constructor (name, money ) { this .name = name; this .money = money; } kq ( ) { console .log (this .name + "的工资为" + this .money ) }; } class Boss extends Worker { constructor (name, money, sex ) { super (name, money); this .sex = sex; } kq ( ) { console .log (this .name + "的工资为" + this .money +"性别为" +this .sex ) }; } var worker = new Worker ("张三" , 10000 );worker.kq (); var boss = new Boss ("李四" , 15000 ,1 );boss.kq ();
创建对象的方式 字面量方式
优点:可以对命名空间进行划分,防止属性或方法冲突 (多个 name 也能区分开,因为属于对象私有属性或方法)
缺点:属于手工作业模式,不能实现批量生产 (相同功能的代码冗余)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var obj1 = { name : "哈哈" , fn : function ( ) { console .log ("obj1" ); } }; var obj2 = { name : "呵呵" , fn : function ( ) { console .log ("obj2" ); } }; console .log (obj1.name , obj2.name ); obj1.fn (); obj2.fn ();
工厂函数模式 我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function createPerson (name, age, height ) { const person = {}; person.name = name; person.age = age; person.height = height; person.eat = function ( ) { console .log (this .name + "is eating" ); } return person; } const p1 = createPerson ("张三" , 18 , 188 );const p2 = createPerson ("李四" , 20 , 177 );const p3 = createPerson ("王五" , 24 , 178 );
构造函数模式
在 ES6
之前,我们都是通过 function
来声明一个构造函数 (类) 的,之后通过 new
关键字来对其进行调用;(如果这么一个普通的函数被使用 new
操作符来调用了,那么这个函数就称之为是一个构造函数;其实为了区别开,声明的时候首字母建议大写 );
在 ES6
之后,JavaScript 可以像别的语言一样,通过 class
来声明一个类;
优点:可以实现命名空间的划分,防止属性或方法冲突,可以实现批量生产,可以实例识别
缺点:不能实现公共的属性或方法公有,都是自己私有的
new 的五步骤:
其实这个结合工厂函数(普通函数共同属性封装)
更好理解。构造函数
调用封装的函数,他既没有声明一个新对象
,也没将函数内部的变量 return
出去,而且构造函数
内部使用的是 this
与对象的属性产生关联;所以实例化对象
的时候,按道理是报错的,所以构造函数创建新对象,返回新对象,内部使用的 this 与创建的新对象关系绑定等操作,需要一个东西来完成,这个东西就是在调用构造函数前,使用的 new
关键字来完成的。
创建一个新的空对象 (空实例)(obj)
将构造函数的显式原型赋值给这个新对象,作为新对象的隐式原型
构造函数内部的 this,会指向创建出来的新对象 (this 指向第一步的空对象)
执行函数体的代码块
返回对象
简单实现一下 new
1 2 3 4 5 6 7 8 9 10 function myNew (fn,...args ){ const obj={}; obj.__proto__ =fn.prototype ; fn.apply (obj,args); return obj; }
instance of: 检测当前这个实例 (对象) 是否属于某个类
1 2 3 4 var arr = [1 ,2 ,3 ];console .log (arr instanceof Array ); console .log (arr instanceof String );
简单案例:
1 2 3 4 5 6 7 8 9 function Person ( ){ console .log (this ); }; var person1 = new Person (); var person2 = new Person ();
案例二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Person (name, age ) { this .name = name; this .age = age; this .say = function ( ) { console .log (`名字:${this .name} ,年龄:${this .age} ` ) } }; var person1 = new Person ("张三" , 19 );var person2 = new Person ("李四" , 20 );console .log (person1); console .log (person2); console .log (person1.say === person2.say );
原型模式
优点:可以实现命名空间的划分,防止属性或方法冲突,可以实现批量生产,可以实例识别,可以实现公共属性或方法公有;
原型模式的基于构造函数模式的;
原型模式将当前实例 (对象) 公有的属性或方法写到 prototype 上;
prototype 是当前函数 (类) 的属性;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Person (name, age ) { this .name = name; this .age = age; } Person .prototype .say = function ( ) { console .log (`name:${this .name} ,age:${this .age} ` ) } Person .prototype .test = "我是测试用的~" ;var person1 = new Person ("zhangsan" , 20 );var person2 = new Person ("lisi" , 24 );console .log (person1.say === person2.say );
详情可参考:
原型与原型链