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面向对象编程的理解,希望对大家有点帮助吧!

相关推荐
科技探秘人6 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人7 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR12 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香14 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969317 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai22 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc27 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_91531 分钟前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
待磨的钝刨3 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json