面向对象的基础概念
类和对象
- 类:具有相同属性和行为事物的统称。比如人、动物、鸟
- 对象:类的具体的表现。比如老王对于人这个类来说是一个具体的表现。老王家的奔驰对于汽车来说是一个具体的表现。
关系
- 类是对象的模板,对象是类的一个实例(具体表现)
属性和行为
- 属性:指某个事物上的特征。比如对于人这个类来说,它的对象都有姓名、年龄、性别、籍贯、肤色、发色等属性。
- 行为:指事物的动作的描述或具有的功能。比如对于人来说,有吃饭、睡觉、上厕所、唱、跳、rap等。
面向对象和面向过程
面向过程
- 面向过程就是分析出解决问题的步骤,然后用方法实现每个步骤,按照流程依次调用方法,最后完成程序
面向对象
- 是一种编程的思想。指以类和对象为核心来将程序拆分成多个模块,然后模块间相互调用来完成程序的一整套设计理念,用于替代面向过程
面向对象和面向过程的比较
- 面向对象是多个模块进行处理,如果中间某个模块需要更改,其他模块可以直接进行复用。而面向过程如果中间的步骤需要更换,可能会整个流程需要重写书写
- 低耦合:程序的一段代码更改之后不会影响到其他代码。
- 维护性更高: 需要更新,面向对象直接加一个新的模块即可。但面向过程需要重新书写整个流程。
优劣对比
- 面向过程:
- 优点 :性能比面向对象高,因为类调用时要实例化,比较消耗资源。
- 缺点:没有面向对象易维护,易复用,易扩展
- 面向对象:
- 优点:易维护,易复用,易扩展,面向对象具有封装,继承,多态的特性,可以设计出低耦合的系统,使系统更加灵活,更易于维护;
- 缺点:性能比面向过程低
ES6面向对象
- 类在程序中是以一种特殊的复合型的数据结构存在。类中包含了各种各样的属性和函数(行为).通过类可以创建对应的对象
- 类是对象的抽象,对象是类的具体表现
- ES6的面向对象的语法本质上相当于ES5面向对象的语法糖
- ES6面向对象代码编译为ES5代码。默认生成一个IIFE,目的为了隔离作用域
- ES5没有模块化编程,作用域分为全局作用域和函数作用域,模块作用域使用IIFE实现
创建类和对象
创建类(class)
class 类名{ //构造器: constructor(){ //添加属性 this.属性名 = 属性值 } // t添加行为 函数名(){ } }
根据某个类创建对象
let 变量名= new 类名(); 例子: let p1 = new People();
属性和行为
class People{
constructor(name){
this.name=name;
//构造函数内行为
this.job=function(){
console.log(this.name);
}
}
//构造函数外行为
show(){
console.log(this.name);
}
}
- 方法可以在构造器中定义,也可以在类中定义
- 类中定义的方法,默认放在原型对象身上
构造器constructor()
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
作用
- 完成对象属性的初始化
class 类名{
constructor(参数1,参数2,参数n){
this.属性名1= 参数1;
this.属性名2= 参数2;
this.属性名n= 参数n;
}
}
let 变量名 = new 类名(实际参数1,实际参数2,实际参数n);
特点
- 构造器只会在创建对象时自行调用一次。不可手动调用
new
负责分配内存空间并生成原生对象的基本架构。构造器则负责该对象属性的初始化
this
- 概念:
this
在面向对象中指的是当前创建出来对象本身。即当前对象 - 用处
- 能够在构造器中通过
this.属性名
来修改属性的值 - 在行为中通过该
this.属性名
能够访问当前对象的某个属性
- 能够在构造器中通过
类属性(静态属性)
静态属性指的是Class本身的属性,通过类名来调用
所有对象都可以使用,即可以认为类属性是
所有对象共享
的。
语法
class 类名{ static 变量名= 值; constructor(){} } //使用和修改 类名.属性名 类名.属性名=新数据
类属性和一般属性
- 语法:类属性需要
static
. 一般的属性在constructor
里通过this.属性名
来使用 - 调用:类属性是类本身来调用。一般属性是通过
对象名.属性名
或在类里通过this.属性名
来调用 - 应用:类属性作为对象的共享属性来使用,一般属性作为对象的私有属性来使用。
类行为(静态方法)
在函数前添加
static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用包含this关键字,指向类
- 实例对象不能调用静态方法
- 父类的静态方法,可以被子类继承
应用场景
- 作为工具函数进行使用
- 定义一个
Random
类,用于实现各种各样的随机数 - 定义一个
RegUtil
类,用于定义常用的正则表达式即检测类方法
- 定义一个
类行为(静态方法)和一般行为
- 语法:类行为需要
static
. 一般的行为直接写函数名(){}即可 - 调用:类行为是类本身来调用。一般行为通过对象来调用
- 应用:类行为一般是作为工具方法来使用。一般行为是作为对象的一般函数来使用(偏向于业务)
面向对象特性
封装
封装是将内部的实现细节封装起来,向外暴露调用的接口例如:功能函数
- 目的是将信息隐藏
划分
- 数据封装
js
当中只能依靠变量的作用域来实现封装的特性,并且只能模拟出public(公有)
和private(私有)
两种特性。- 利用创建函数的作用域达到数据封装
- 封装实现
- 对象内部的变化对外界是透明的,不可见。这种做法使对象之间低耦合,便于维护升级,团队协作开发。
优点
降低耦合率 可重复调用类中的属性 提高安全性
继承
继承是子对象可以继承父对象的属性和行为,即父对象拥有的属性和行为,其子对象也就拥有了这些属性和行为
本质还是基于原型链的继承
语法
class Person { constructor(name) { this.name = name; } } //子类 class Student extends Person { constructor(name) { super(name); } }
特点
- extends关键字用于表示是哪一个类继承了父类,子类会拥有父类的行为。
- super()是用于将父类里的属性赋给子类,调用父类的构造器,
- 子类不会继承父类的
类属性和类行为
- 如果子类的属性都是来自于父类,那么子类的
构造函数
可以省略不写 - 减少类的定义代码
super关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用,ES6
要求子类的构造函数必须执行一次super
函数。
super
作为函数调用时,代表父类的构造函数
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
注意,使用super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
子类扩展
- 子类除了可以使用父类的属性和行为之外,本身可以写自己的属性和行为。
- 如果子类有自己的属性的话,那么需要自己书写
构造函数
重写
- 子类可以重新编写父类已经有的行为同名函数。这样程序在调用行为时,会优先调用子类已经重写的行为,如果没有重写,直接调用父类写的行为。
- 重写是针对父类有的行为。子类自己扩展的行为不算重写。
多态
一种定义,多种实现,即在JavaScript中,一个行为传入不同类的对象,该行为会有不同的效果。天生具备多态
- 实现多态的三个必要条件:
- 继承
- 重写
通俗来讲就是同一个函数,因为传递的参数列表不同,可以实现的不同的功能
面向对象面试题
// 函数名字首字母大写,看成构造函数
function Cat(){
let showName = function(){
console.log(1);
}
console.log(this);
return this
}
Cat.showName = function(){ console.log(2) }
Cat.prototype.showName = function(){ console.log(3) }
var showName = function(){
console.log(4);
}
function showName(){
console.log(5);
}
// 请说出下面代码的结果
Cat.showName() //调用的是类属性,结果为2
showName() //4
Cat().showName() //4
new Cat.showName() //2
new Cat().showName() //3
new new Cat().showName() //3
- 若没有通过关键字定义变量,则默认添加为window的属性
- 原型对象new之后,才会创建原型对象
- this 指向问题
- 变量函数提升(预解析) 优先级 函数>变量