JS 面向对象编程

前言

我觉得,JS的面向对象编程属于它最难的部分,一直无法真正理解它的精髓,直到最近才弄懂了,本文是我对这块知识的理解。

先说一下面向对象编程在JS中的地位

在前端环境下,面向对象思想作用其实并不大,因为在前端:组合优于继承

组合是指将一个对象的实例作为另一个对象的属性来使用。这种方式可以让我们在不改变原有代码的情况下,灵活地扩展对象的功能,从而更好地分离关注点,使代码更加模块化。比如,我们可以定义一个Car对象,然后将Engine对象作为它的属性来使用,这样我们就可以在不改变Car对象的情况下,灵活地更换不同的引擎。

而继承则将一个类的属性和方法直接继承到子类中,这样子类就可以直接使用父类的属性和方法。但是,继承往往会导致子类和父类之间的紧耦合,使得代码更加难以维护和扩展。

综上所述,虽然继承在某些情况下也是有用的,但是在大多数情况下,组合更加灵活和可维护。

对比一下前后端的开发环境,可以发现这样的规律: 后端程序只有"数据+行为 ",关注点不多而且容易预测,继承将会很好用。而前端是"数据+行为+展现+交互 ",多出来的"展现+交互"决定了前端的关注点多且无从预测 ,也就无法准确的定义"模板",使用继承,并不是好的选择。

当然,有些环境下,也适合继承,比如网页游戏开发,元素的对象化是很重要的。

总之,之所以JS面向对象被提及这么多,我猜还是因为很多JS技术人员是受C++/JAVA等面向对象思维的影响,同时,JS本身就是一枚面向对象的语言,理解原型继承等知识是非常必要的

再说一下JS继承机制的设计思想

原型和原型链的设计机制很令人费解,prototype对象是如何产生的Function和Object对象为何会如此特殊?真相其实很简单!

  1. 1994年,工程师Brendan Eich负责开发一种网页脚本语言,时间很紧,他觉得没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。
  2. 1994年正是面向对象编程最兴盛的时期,Brendan Eich也受到了影响,JS里面所有的数据类型都是对象,这与Java非常相似。
  3. 但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢?如果真的是一种简易的脚本语言,其实不需要有"继承"机制,但是,JS里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
  4. 但是,他不打算引入"类"(class)的概念,因为一旦有了"类",JS就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
  5. 他想到C++和Java使用new命令时,都会调用"类"的构造函数。他就做了一个简化的设计,在JS语言中,new命令后面跟的不是类,而是构造函数,所以JS中构造函数充当了"模板"的作用。
  6. 这时,用构造函数生成实例对象,会有一个缺点,那就是无法共享属性和方法,这是极大的资源浪费。考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性,这个属性包含一个对象,即prototype对象或原型对象。
  7. 所有实例对象需要共享的属性和方法,都放在原型对象里,那些不需要共享的属性和方法,就放在构造函数里。
  8. 实例对象一旦创建,将自动引用其构造函数原型对象的属性和方法,对象中指向其构造函数的原型对象的属性是_proto_。
  9. 有了"原型"、"继承"机制后,就可以构造JS中互联的对象系统。
  10. JS最先产生出两个凭空出来的对象,Function和Object.prototype,其背后是用C++语言实现。前者的作用是构造其它对象,后者的作用是使得原型对象、构造函数对象、实例对象能联系起来。
  11. Function创建了Object、Date、包括Function本身等构造函数对象。
  12. Object创建了Function.prototype等原型对象,但Object自己的原型对象不是由Object创建的,它是凭空产生的,所以其它原型对象的_proto_属性指向Object.prototype,而Object.prototype的_proto_指向null。

其实都在下面这张图里面:品,细品!

详解JS面向对象核心概念

理解对象

对象是什么?

从内容上看,对象是实物的抽象;从形式上看,对象是一个容器,封装了属性和方法。

在Js中,除了基本类型外,所有值都是对象,但即使是基本类型,也可以当作是对象一样使用("装箱"操作),所以在JS里面,万物皆对象

装箱 :在JS中,基本类型(如字符串、数字、布尔值、null和undefined)不是对象,它们是原始值,虽然这些值不是对象,但是JS提供了一种方式来访问它们的属性和方法,当你尝试访问基本类型的属性或方法时,JS会自动将基本类型转换为对象,这个过程称为"装箱"。例如,当你使用"hello".toUpperCase()时,JS会自动将字符串"hello"转换为一个String对象,以便你可以调用toUpperCase()方法,但是,一旦调用完成,JS就会将String对象转换回原始字符串值。因此,虽然基本类型可以像对象一样使用,但它们并不是真正的对象。

对象的创建

JS中创建对象的四种方式:(1)字面量(2)构造函数(3)class(4)Object.create()

虽然表面上看,JS对象有四种来源,但本质上,除了Function和Object.prototype,JS中所有的对象都是通过构造函数创建出来的。通过字面量创建的对象,或者原型对象,本质是通过 Object 构造函数创建的对象,所以其_proto_会指向Object.prototype。构造函数创建对象的底层实现是通过Object.create(),而Object.create()创建对象的底层实现是通过Function(),本文对此不做拓展了!

对象的性质

js使用属性描述符 来描述对象的每个成员,它可以用于控制对象属性的访问与修改权限、枚举和删除行为

属性描述符也是一个对象,可以通过 Object.getOwnPropertyDescriptor() 方法来获取对象属性的描述符,也可以通过 Object.defineProperty() 方法来定义或修改对象属性的特性。

  • value:属性的值
  • configurable:是否可以重新定义
  • enumerable:是否允许被遍历,会影响for-in循环
  • writable:是否允许被修改
  • get():读取属性时,得到的是该方法的返回值
  • set(val):设置属性时,会把值传入val,调用该方法

理解构造函数

JS中,它充当了"模板"角色,用于创建其它对象。

在JS中创建构造函数的方式有三种:(1)function关键字函数声明(不是函数表达式)(2)通过class声明(3)通过Function创建

理解类

JS中的类,本质上是"特殊的函数",它在使用上主要跟四个关键字有关:class、constructor()、extends、super。

class

用于声明创建一个基于原型继承的新类。

constructor()

它是类中的构造函数,一个类中只能有一个名为"constructor"的构造函数,如果不显式指定一个构造函数,则会添加默认的构造函数。

基类的默认构造函数是:

js 复制代码
constructor() {} 

派生类的默认构造函数是:

js 复制代码
constructor(...args) {
  super(...args);
}

extends

用来创建一个普通类或者内建对象的子类。

super

用于调用对象的父对象上的函数,包括构造函数,比如:

js 复制代码
class Cat {
     constructor(name) {
        this.name = name;
       }

      speak() {
   	 console.log(this.name + " makes a noise.");
       }
}

class Lion extends Cat {
      constructor(name){
        super(name);// 调用父对象的构造函数
        }
      speak() {
    	super.speak(); // 调用父对象的函数
    	console.log(this.name + " roars.");
        }
}

理解原型

所有对象本质上都是构造函数创建的(除了Object.prototype),所以所有对象都有_proto_属性,其指向其构造函数的原型对象。

所有对象都有原型,但只有构造函数对象才具有prototype属性,它指向其原型对象,同时原型对象上有一个constructor属性,指向此原型对象对应的构造函数(并不是生成该原型对象的构造函数Object)。

理解原型链

通过_proto_属性连接起来的原型对象,组成了原型链,原型链的终点是Object.prototype,它的_proto_属性指向null。

关于封装

JS把数据和方法封装在对象里,通过原型的方式解决了数据不能共享、浪费内存、效率低的问题,封装性具体体现在私有字段、公有字段和静态字段。

私有字段

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。

实现方式是在命名上加以区别,可以使用增加前缀 # 的方法来定义私有类字段, # 是名称本身的一部分,访问时也需要加上。

公有字段

公有方法和公有属性,是在类的外部也能访问的方法和属性,类的方法和属性在默认情况下是公有的

静态字段

加上static关键字定义静态字段,表示该方法或属性不会被实例继承,而是直接通过类来调用Class 本身的方法或属性。(这与java中的静态字段有很大不同)

关于继承

Brendan Eich原本只是想设计一个比较简单的继承机制,直接使用构造函数充当"模板",再让一个构造函数继承另一个构造函数,但由于JS中的构造函数和原型的特殊性,最后出现了很多种实现继承的方式,总共有下面这些:

  1. 构造函数绑定
  2. 原型模式
  3. 直接继承原型对象
  4. 利用空对象作为中介
  5. 拷贝继承
  6. 类方式

重点讲一下原型模式和类方式的继承!

原型继承

js 复制代码
SubClass.prototype = new SuperClass();
SubClass.prototype.constructor = SubClass;

将子类的原型设置为父类的实例,再将子类原型对象的constructor属性指向它本身。

第一句让子类能访问父类的原型对象,子类原型对象的_proto_属性(原型的原型)指向父类的原型对象,实现了继承。

第二句,是因为当执行完第一句后,SubClass.prototype.constructor变成了父类,这会导致继承链的紊乱,所以需要人为把这个指向纠正过来。

类继承

js 复制代码
class Animal {
   constructor(name) {
    	this.name = name;
     }

   speak() {}
}

class Dog extends Animal {
   constructor(name) {
    	super(name); 
     }

   speak() {}
}

类继承的本质是原型继承,通过extends实现,写法上与java接近。

关于多态

多态是同一个行为具有多个不同表现形式或形态的能力,由于JS本身是动态的,天生就支持多态

总结

以上就是我关于JS面向对象编程的理解,希望对大家有点帮助吧!

相关推荐
Манго нектар27 分钟前
JavaScript for循环语句
开发语言·前端·javascript
蒲公英100134 分钟前
vue3学习:axios输入城市名称查询该城市天气
前端·vue.js·学习
天涯学馆1 小时前
Deno与Secure TypeScript:安全的后端开发
前端·typescript·deno
以对_1 小时前
uview表单校验不生效问题
前端·uni-app
Zheng1132 小时前
【可视化大屏】将柱状图引入到html页面中
javascript·ajax·html
程序猿小D2 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
john_hjy2 小时前
【无标题】
javascript
奔跑吧邓邓子2 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
软件开发技术深度爱好者3 小时前
用HTML5+CSS+JavaScript庆祝国庆
javascript·css·html5
前端李易安3 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp