面向对象的三大特点

142 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

封装、继承、多态

一、封装

  • 封装就是创建一个对象,集中保存现实中一个事物的属性和功能
  • 将零散的数据封装进对象结构中,及其便于大量数据的管理维护
  • 只要是使用面向对象的思想开发时,第一步都是封装各种各样的对象结构备用

封装方式【一】:直接量 {}

var 对象名 = { // new Object() 的简写
  属性名: 属性值,
  方法名:function(){}
}
var user = {
  name: "hayes",
  age: 18,
  info: function() {
    console.log(`我是 ${name}, ${age} 岁`)
  }
}
user.info()

这里如果运行的话,会报错,找不到 info 中的 nameage,需要通过 this 来访问

为什么在对象自己的方法内,想使用当前对象自己的另一个属性名,非要加上 this 呢?

我们来看一下这段程序的执行过程:

  • var user = {:大括号,创建对象
  • 创建对象之后,对象的这些属性就会进入到对象之中(nameageinfo
  • info: function(){}:小写的 function 在底层代表的是 new Function。所以这里会创建一个新函数
    • 这个新函数虽然嵌套的很深,但是它能使用的作用域只有:自己和全局
    • 为什么没有对象呢,因为对象的 {} 只是一个 new Object 简写,它不代表任何的作用域(在JS中只有函数才会创建作用域)
  • 那么现在位于 info 函数中是无法获取到对象的 nameage 属性的,但是我们可以通过如下的方式获得
    • ${user.name}${user.age}:但是这里属于紧耦合
    • 另一个方法,我们可以加上 this 关键字
      • 每个函数内自带的
      • 专门指向正在调用当前函数的 .前的对象 的关键字
    • this 为什么会指向 user
    • 我们可以看到这段代码:user.info()info 函数在调用的时候,它前面有个 .. 的前面是 user,那么自然函数内的 this 就指向 user

对象的方法中,只要想使用当前对象自己的属性或其他方法时,都要加 this

封装方式【二】:new Object

var 对象名 = new Object(); // 先创建空对象{}
// 然后强行对空对象中添加新属性和新方法
对象名.属性 = 属性值
对象名.方法 = function() {
  ...
  this.属性名
  ...
}

为什么有了 {} 大括号模式,还需要 new Object() 模式呢?

其实这揭示了 js语言底层最核心的原理:js 中所有对象底层都是关联数组

注:只有js的对象可以在创建完之后再随意的向里面添加属性和方法

封装方式【三】:构造函数

以上两种方式的问题:

一次只能创建一个对象。如果想创建多个相同结构的对象时,代码就会很多重复------及其不便于将来的维护。

var user1 = {
  name: "hay1",
  age: 18
}
var user2 = {
  name: "hay2",
  age: 20
}

可以看出,上面创建的两个对象,属性名是一模一样的,只是值不同。

解决:想反复创建多个相同结构,只是内容不同的对象时,都用构造函数

构造函数:描述同一类型的所有对象的统一结构的函数。优点是代码重用

1、定义构造函数

function 类型名(形参1,形参2) {
  // 将来要加入到新对象中的属性
  this.属性名 = 形参1
  this.xxx =xxx
  this.方法名 = function(){}
}

2、使用构造函数

构造函数需要使用 new 来调用

var 对象名 = new 类型名(实参值1,实参值2)

问题:new 一共干了几件事?

至少做了2件事:

  • 创建指定类型的一个新对象
  • 同时把实参值传给构造函数的形参变量

3、原理

实参传给形参,形参传给属性,属性通过 this 交给新对象

总结

this 的两种情况

  • obj.fun()fun 中的 this. 前的 obj 对象
  • new Fun()Fun 中的 thisnew 创建的新对象

new 的4步

  1. 创建一个新的空对象
  2. 让子对象继承构造函数的原型对象
  3. 调用构造函数,将 this 替换为新对象,通过强行赋值方式为新对象添加规定的属性
  4. 返回新对象地址

二、继承

只要将方法定义放在构造函数中,那么每次 new 时都会执行 function。这样会反复创建相同函数的多个副本,浪费内存。

function Student(name, age) {
  this.name = name
  this.age = age
  this.info = function() {}
}
var hay1 = new Student("hayes1", 18); // 这里hay1.info 的地址(假设):0x123
var hay2 = new Student("hayes2", 20); // 这里hay2.info 的地址(假设):0x345

上述代码在使用构造函数的时候,每次调用都会创建一个新的 info 副本

  • 不应该把方法放在构造函数中,否则会重复创建,浪费内存

解决方法:继承

  • 如果将来发现多个子对象都要使用相同的功能和属性值时,可以使用继承来解决

什么是继承:父对象中的成员,子对象无需重复创建,就可以直接使用!就像使用自己的成员一样

实现

js 中的继承都是通过原型对象实现的

原型对象:替所有子对象集中保存共有属性值和方法的父对象

只要发现多个子对象都需要使用相同的功能和属性值时,都可将相同的功能和属性值集中定义在原型对象中。

强行赋值

构造函数.prototype.属性名 = 属性值
构造函数.prototype.方法名 = function(){}
function Student(name, age) {
  this.name = name
  this.age = age
}
Student.prototype.info = function() {}

这样的话,之后使用 Student 作为构造函数,生成的对象都自带 info 函数了

三、多态

同一个函数,在不同情况下表现出不同的状态

  • 重载:同一个函数,输入不同的参数,执行不同的逻辑
  • 重写:覆盖

重写:

  • 在子对象中定义一个和父对象中成员同名的自有成员
  • 从父对象继承来的个别成员不好用时,就可以在子对象中定义同名成员,来覆盖父对象中的同名成员

四、总结

简单谈谈你对面向对象的理解:

  • 封装:创建对象
    • 如果只创建一个对象,则使用 {}
    • 如果反复创建多个相同结构的对象,则使用构造函数
  • 继承:所有子对象共用的属性值和方法,都要放在构造函数的原型对象中
  • 多态:重写:只要觉得从父对象继承来的成员不好用,就可以在子对象中重写同名成员