掌握JavaScript面向对象编程核心密码:深入解析JavaScript面向对象机制对象基础、原型模式与继承策略全面指南,高效创建高质量、可维护代码

在这里插入图片描述

ECMAScript(简称ES,是JavaScript的标准规范)支持面向对象编程,通过构造函数模拟类,原型链实现继承,以及ES6引入的class语法糖简化面向对象开发。对象可通过构造函数创建,使用原型链共享方法和属性,实现继承、封装和多态等面向对象特性。

ECMAScript的面向对象技术极大提升了JavaScript的代码组织度和复用性,通过类和继承机制促进了模块化编程,增强了代码结构的清晰度与可维护性。封装、继承、多态等特性支持复杂应用开发,是构建可扩展、易管理的大型项目的基础,对提升开发效率和代码质量至关重要。

本文详细介绍了JavaScript 对象概念原型模式创建对象的过程(Object 创建对象、构造函数 创建对象、原型模式 创建对象)以及机制策略(__proto__和 prototype、constructor),继承机制(原型链继承、构造函数继承、组合继承、寄生式继承、寄生组合式继承、ES6 Class继承)等内容。

一、JavaScript 对象

ECMAScript(JavaScript)中的对象是一种数据结构,用于存储键值对(property-value pairs),其中键(property)通常是字符串类型,值(value)可以是任意数据类型,包括其他对象。对象是JavaScript中最基本的数据结构,也是语言的核心特性之一。以下是对ECMAScript对象概念的几个关键点总结:

  • 动态性:JavaScript对象是动态的,可以在运行时添加、修改或删除其属性。这为编程提供了极大的灵活性。

  • 原型继承:JavaScript采用原型链继承机制。每个对象都有一个内部的[[Prototype]]属性(可通__proto__访问,尽管不推荐直接操作),指向它的原型对象。当试图访问一个对象的属性或方法时,如果对象本身没有,JavaScript引擎会向上查找其原型链,直至找到或链结束。

  • 构造函数:构造函数是一种特殊的函数,用于初始化新创建的对象。使用new关键字调用构造函数时,会创建一个空对象,并将其[[Prototype]]链接到构造函数的prototype属性所指向的原型对象,然后执行构造函数体内的代码以初始化该对象。

  • 字面量表示法:可以直接使用对象字面量 {} 来创建并初始化一个对象,这是定义简单对象的便捷方式。

  • 属性访问:可以通过.[]操作符访问对象的属性,如 obj.propertyobj['property']

  • 方法:对象的属性可以是函数,这样的属性通常称为方法。方法允许对象封装行为。

  • this关键字:在对象的方法中,this关键字指向调用该方法时的对象上下文。在全局作用域或非严格模式下未明确绑定的函数调用中,this默认指向全局对象(浏览器中是window,Node.js中是globalglobalThis)。

  • 原型对象:每个函数都有一个prototype属性,它指向一个对象,这个对象就是将来新建的对象的原型对象。原型对象上的属性和方法可以被其所有实例共享。

理解JavaScript的对象概念是掌握其面向对象编程的基础,对于编写高效、可维护的代码至关重要。

二、Object 创建对象

创建一个对象,然后给这个对象新建属性和方法。

var car = new Object(); //创建一个Object 对象
car.name = 'AIPHD'; //创建一个name 属性并赋值
car.color = '4AD3FF'; //创建一个color 属性并赋值
car.run = function () { //创建一个run()方法并返回值
return this.name + this.color + '运行中...';
};
alert(car.run()); //输出属性和方法的值

上述代码实例化了一个对象并定义了属性及方法,其中run()方法内部的this关键字即指向car对象自身。但不足之处在于,若需生成多个相似对象,会导致代码重复且冗长。

为了解决多个相似对象实例化的冗余问题,可以采用“工厂模式”,该模式设计初衷正是为了有效减少重复代码,优化实例化过程中的资源消耗。

function createObject(name, age) { //集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = createObject('AIPHD', 100); //第一个实例
var box2 = createObject('AIMANT', 200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立

尽管工厂模式缓解了重复实例化的问题,但它亦非完美之策。它引发的新挑战包括:为每个对象重复构建相同的属性和方法,导致内存使用低效;以及难以通过类型直接辨别对象函数识别问题等等。

三、构造函数 创建对象

构造函数的使用遵循一套既定的规范,旨在确保代码的高效与可维护性。这些规范主要包括:

  • 命名规范:构造函数名应采用大驼峰命名法,如PersonCar,以便于区分普通函数和构造函数。

  • 首字母大写:遵循惯例,构造函数的名称首字母通常大写,区别于其他小写命名的普通函数。

  • 使用new关键字:调用构造函数时,必须通过new操作符来创建新实例。这会自动执行构造函数内的代码,并为新对象分配内存空间。

  • this关键字:在构造函数内部,this指代新创建的实例对象。可以使用它来给实例添加属性和方法。

  • 无需显式返回值:构造函数默认返回新创建的对象实例,除非使用return语句明确返回一个对象,此时返回的对象将替代默认实例。

  • 原型链继承:利用构造函数的.prototype属性,可以实现方法的共享,避免每个实例都拥有相同方法的副本,从而节省内存。

遵循这些规范,可以编写出结构清晰、易于理解且性能良好的JavaScript代码。

function Car(name, color) { //构造函数模式
this.name = name;
this.color = color;
this.run = function () {
return this.name + this.color + '运行中...';
};
}
var car1 = new Car('AIPHD', '4AD3FF'); //new Car()即可
var car2 = new Car('AIMANT', 'FFF800');
alert(car1.run());
alert(car instanceof Car); //很清晰的识别他从属于Car

构造函数创建对象的过程大致分为以下几个步骤:

  • 调用构造函数:当使用new关键字调用一个构造函数时,JavaScript引擎首先会在内存中创建一个新的空对象。

  • 绑定this:接着,这个新创建的空对象会被绑定到构造函数内部的this关键字上。这意味着通过this可以给新对象添加属性和方法。

  • 执行构造函数体:构造函数的代码体开始执行。在这个阶段,可以通过this给新对象添加属性和方法,比如this.name = 'Alice'

  • 初始化原型:如果构造函数中有对原型(prototype)的修改或扩展(例如,添加共享方法),这些更改会影响之后由该构造函数创建的所有实例。

  • 返回对象:构造函数执行完毕后,如果未显式返回一个对象(或者返回nullundefined),则new操作符会自动返回最初创建的那个对象实例。如果构造函数显式返回一个对象,则返回该对象,而不是默认创建的实例。

通过这一系列步骤,构造函数不仅创建了一个新的对象实例,还为其添加了特定的属性和方法,完成了对象的初始化和配置。

注:

  • 构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。

  • this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

这种方法解决了函数识别问题,但内存消耗问题仍旧存在,且引入了新的困扰:this关键字的上下文不确定性。具体而言,在全局环境中,若作为对象方法调用,this正确指向Box实例;但若以普通函数形式调用,this则默认绑定至全局对象(在浏览器中即window),导致this作用域的混乱。

四、原型模式 创建对象

每个函数自带一个名为prototype的属性,它实质上是一个对象,承载着旨在被该函数构建的所有实例共同继承的属性与方法。从逻辑角度阐释,每当通过某个构造函数生成实例时,该实例会自动指向该构造函数prototype属性所定义的对象,作为其原型。采纳原型模式的意义,在于实现这些共有属性和方法的一次定义、多处复用,无需在每个构造实例时重复定义相同数据,而仅需将这些共享特征附加至原型上即可。

function Car() {} //声明一个构造函数
Car.prototype.name = 'AIPHD'; //在原型里添加属性
Car.prototype.color = '4AD3FF';
Car.prototype.run = function () { //在原型里添加方法
return this.name + this.color + '运行中...';
};

原型设计巧妙地缓解了内存消耗问题,同时也有效应对了this作用域相关挑战。

在创建对象时,我们通常的做法是:将那些在生成每个新实例时需要独立初始化的属性值,放置在构造函数内部定义;而对于那些所有实例共用的方法,则倾向于将其添加到原型(prototype)上。这种方法结合了构造函数与原型的优点,即所谓的“构造函数+原型”混合模式来构建对象。

function Task(id){  this.id = id; // 属性(构造函数)
}  Task.prototype.status = "STOPPED";  
Task.prototype.execute = function(args){  // 共用方法(原型)return "execute task_"+this.id+"["+this.status+"]:"+args;  
}  var task1 = new Task(1);  
var task2 = new Task(2);  task1.status = "ACTIVE";  
task2.status = "STARTING";  print(task1.execute("task1"));  
print(task2.execute("task2"));

结果:

execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2

构造器会自动为task1,task2两个对象设置原型对象Task.prototype,这个对象被Task(在此最为构造器)的prototype属性引用,参看下图中的箭头指向。

在这里插入图片描述

Task作为函数实体,其隐含的proto链结点为Function.prototype。同时,系统内置的函数原型(Function.prototype)其proto指向基底的Object.prototype。层层递进,至顶层Object.prototypeproto属性为空(null),标示原型链的顶层终点。

1. 原型对象

在JavaScript中,每个对象均关联一个原型对象,该对象在不同 JavaScript 引擎中的具体实现细节可能有所差异。以Firefox为例,每个对象内部含有一私有属性__proto__,它作为一个指针,指向该对象的原型对象。

2. 原型链

原型链是JavaScript实现继承的核心机制。当试图访问一个对象的属性或方法时,如果该对象本身没有,则JavaScript引擎会向上搜索其原型对象(即__proto__指向的对象)是否有该属性或方法。如果原型对象也没有,引擎会继续在原型的原型对象中寻找,如此层层向上,直到找到该属性或方法,或抵达原型链的末端(通常是Object.prototype的__proto__为null,标志着原型链的结束。这意味着不再有更上一层的原型可供查询,即达到了原型链的顶层)。这一连串由对象及其原型对象逐级连接形成的链式结构,即称为原型链。

2.1. __proto__和 prototype

__proto__是每个JavaScript对象(除了null)都有的一个内部属性,它指向该对象的原型对象(prototype object)。这个属性不是标准的一部分,但在大多数现代浏览器和Node.js环境中都得到了非正式的支持。正式的标准推荐使用Object.getPrototypeOf()和Object.setPrototypeOf()方法来访问和修改对象的原型。

function Animal(name) {this.name = name; // 实例属性
}// 给Animal构造函数的原型添加一个方法
Animal.prototype.speak = function() {console.log("My name is " + this.name);
};// 创建Animal的实例
let cat = new Animal("Tom");// 访问实例的方法
cat.speak(); // 输出: My name is Tom// 查看cat的原型链
console.log(cat.__proto__); // 输出: Animal的原型对象,其中包含speak方法
console.log(cat.__proto__ === Animal.prototype); // 输出: true,表明cat的__proto__指向Animal的prototype
console.log(Animal.prototype.__proto__ === Object.prototype) // 输出: true,表明Animal.prototype的__proto__指向创建它的函数对象(Object)的prototype
console.log(Object.prototype.__proto__) // 输出: null,Object.prototype对象也有__proto__属性,但它比较特殊,为null// 查看Animal构造函数的prototype属性
console.log(Animal.prototype); // 显示包含speak方法的原型对象

我们把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。

2.2. constructor

在JavaScript中,每个构造函数的prototype对象上都有一个默认的属性constructor,这个属性指向构造函数自身。constructor属性的作用主要是为了方便地识别某个实例是通过哪个构造函数创建的,同时也便于从原型对象重新获取构造函数的引用。

  • 自我标识:在复杂的继承结构或者原型链修改后,constructor可以帮助明确对象的构造函数来源,即使原型被覆盖或修改也能追溯到原始构造函数。

  • 便于复用和构造:当需要根据对象类型动态创建新实例时,可以直接通过原型链上的constructor调用构造函数。

function Animal(name) {this.name = name;
}console.log(Animal.prototype.constructor === Animal); // 输出: true,表明constructor指向Animal函数let cat = new Animal("Kitty");
console.log(cat.constructor === Animal); // 间接通过原型链,同样输出: true// 即使修改了原型,constructor仍指向正确的构造函数
Animal.prototype = {speak: function() {console.log("My name is " + this.name);}
}; // 注意:这样的直接赋值会丢失原有的constructor属性!// 修复constructor属性
Animal.prototype.constructor = Animal;let dog = new Animal("Rex");
console.log(dog.constructor === Animal); // 确保修复后仍为true
Function.prototype.constructor === Function //true
Object.prototype.constructor === Object //true

五、继承机制

ECMAScript(JavaScript)提供了多种继承策略,每种策略都有其特点和适用场景。以下是几种主要的继承方式的详解与示例:

1. 原型链继承

原理:通过让子类型的原型对象等于父类型的实例,使得子类型能够访问到父类型上的属性和方法。

示例:

function SuperType() {this.superProperty = true;
}SuperType.prototype.getSuperValue = function() {return this.superProperty;
};function SubType() {this.subProperty = false;
}// 继承SuperType
SubType.prototype = new SuperType();// 修复构造函数引用
SubType.prototype.constructor = SubType;SubType.prototype.getSubValue = function() {return this.subProperty;
};var instance = new SubType();
console.log(instance.getSuperValue()); // 输出: true

2. 构造函数继承(借用构造函数)

原理:在子类型构造函数内部通过callapply方法调用父类型构造函数,为子类型实例添加属性。

示例:

function SuperType(name) {this.name = name;
}function SubType(name, age) {SuperType.call(this, name); // 借用构造函数this.age = age;
}var instance = new SubType("Tom", 25);
console.log(instance.name); // 输出: Tom
console.log(instance.age); // 输出: 25

3. 组合继承(原型链+构造函数继承)

原理:结合原型链继承和构造函数继承的优点,既可以在子类型中继承父类型的属性和方法,又能保持每个实例的唯一性。

示例:

function SuperType(name) {this.name = name;
}SuperType.prototype.sayName = function() {console.log(this.name);
};function SubType(name, age) {SuperType.call(this, name); // 继承属性this.age = age;
}// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; // 修复构造函数引用SubType.prototype.sayAge = function() {console.log(this.age);
};var instance = new SubType("Tom", 25);
instance.sayName(); // 输出: Tom
instance.sayAge(); // 输出: 25

4. 寄生式继承

原理:创建一个对象作为父类型的实例,然后为其添加额外的属性和方法,最后返回这个对象。

示例:

function createAnother(original) {var clone = Object.create(original); // 或者使用 Object.assign({}, original) 进行浅拷贝clone.extraMethod = function() {console.log("Extra method");};return clone;
}var original = { value: 1 };
var another = createAnother(original);another.extraMethod(); // 输出: Extra method

5. 寄生组合式继承

原理:结合了寄生式继承和组合继承的特点,优化了组合继承中重复调用父构造函数的问题。

示例:

function inheritPrototype(subType, superType) {var prototype = Object.create(superType.prototype); // 创建父类型的原型副本prototype.constructor = subType; // 修正构造函数的指向subType.prototype = prototype; // 将子类型的原型指向新创建的原型副本
}function SuperType(name) {this.name = name;
}SuperType.prototype.sayName = function() {console.log(this.name);
};function SubType(name, age) {SuperType.call(this, name); // 继承属性this.age = age;
}inheritPrototype(SubType, SuperType); // 实现继承SubType.prototype.sayAge = function() {console.log(this.age);
};var instance = new SubType("Tom", 25);
instance.sayName(); // 输出: Tom
instance.sayAge(); // 输出: 25

6. ES6 Class继承

原理:ES6引入了class关键字,使得继承更加简洁明了,背后仍然是基于原型继承机制。

示例:

class SuperType {constructor(name) {this.name = name;}sayName() {console.log(this.name);}
}class SubType extends SuperType {constructor(name, age) {super(name); // 调用父类构造函数this.age = age;}sayAge() {console.log(this.age);}
}let instance = new SubType("Tom", 25);
instance.sayName(); // 输出: Tom
instance.sayAge(); // 输出: 25

每种继承策略各有千秋,开发者应根据实际需求选择最适合的继承方式。ES6的class继承因其简洁易读性,逐渐成为主流选择。

在ECMAScript中运用面向对象技术,关键在于合理设计类与接口,利用ES6的class语法简化继承和封装过程。采用组合而非深度继承提高灵活性,利用 Mixins 引入多重继承特性。重视模块化,合理划分职责,利用闭包和模块模式增强封装性。适时采用原型链继承与构造函数继承,结合实际情况灵活选择,确保代码既高效又易于理解维护。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/5397.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

max各种相机导出到ue4匹配镜头的工具集

总览 rollout export_UE4Cam_v2 "导出UE4Cam_v2:半自动" width:200 height:120(HyperLink explain "在打开的max文件中使用" pos:[25,12] width:200 height:15 color:(color 255 155 0) GroupBox grp1 "要导出的相机名" pos:[5,28] width:179 …

一个单例模式中使用std::unique_ptr引起的莫名其妙的COFF损坏的问题(未解决)

使用static std::unique_ptr和static std::shared_ptr都不行struct IElementAgendaEvents {//! Called to allow listeners to modify the agenda by adding/removing entries before applying tool operation. Return true if entries added or invalidated.virtual bool …

如何使用Scrapy和Python 3爬取网页

简介 网络爬虫,通常称为网络爬行或网络蜘蛛,是以编程方式浏览一系列网页并提取数据的行为,是处理网络数据的强大工具。 通过使用网络爬虫,您可以挖掘有关一组产品的数据,获取大量文本或定量数据以进行分析&#xff0…

1、FreeCAD概述与架构

FreeCAD介绍 FreeCAD的诞生:2002年10月29日,由Jrgen Riegel上传了版本0.0.1的初始上传。FreeCAD的维基百科页面显示,FreeCAD基本上是由不同强大的库组成的集合,其中最重要的是openCascade,用于管理和构建几何体&#x…

【Vue 2.x】学习vue之一基础部分

文章目录 Vue 一基础部分第一章1、git两个分支主分支子分支 使用方法方式1:采用命令的方式操作分支方式2:在idea中使用git的分支 向git远程仓库提交时忽略文件使用git时的一些冲突注意事项 2、Vue问题1:什么是Vue?问题2&#xff1…

泰勒创造力达到顶峰?(下)

上文说了一半,回顾看文: https://blog.csdn.net/weixin_41953346/article/details/138336524 继续看下文 “Like I lost my twin /Fuck it if I cant have him,"she sings in “Down Bad". 在《Down Bad》这首歌中,她唱道&#xff…

TiDB系列之:使用TiUP部署TiDB集群最新版本,同时部署TiCDC的详细步骤

TiDB系列之:使用TiUP部署TiDB集群最新版本,同时部署TiCDC的详细步骤 一、部署TiDB集群二、准备环境三、安装 TiUP四、安装TiUP cluster组件五、初始化包含TiCDC的TiDB集群拓扑文件六、检查和修复集群存在的潜在风险七、查看可以安装的tidb版本八、部署 TiDB 集群:九、查看集…

Activiti7 开发快速入门【2024版】

记录开发最核心的部分,理论结合业务实操减少废话,从未接触工作流快速带入开发。假设你是后端的同学学过JAVA和流程图,则可以继续向后看,否则先把基础课程书准备好先翻翻。 为什么要工作流 比起直接使用状态字段,工作…

工业互联网常用开源库

libopen62541 opc-ua开源库 libmodbus modbus开源库 libsocketcan can 开源库 canutils:ubuntu 中socket can 与can通道绑定命令ifconfig -a 查看当前can设备名如can0ip link set down can0ip link set can0 type can bitrate 5000ip link set up can0cansend ca…

【C++之多态的知识】

C学习笔记---018 C之多态的知识1、C多态的简单介绍1.1、多态的分类1.2、多态的构成条件 2、虚函数2.1、虚函数的重写(覆盖) 3、虚函数重写的两个例外3.1、协变:(基类与派生类虚函数返回值类型不同)3.2、析构函数的重写(基类与派生类析构函数的名字不同) 4、两个关键…

redis运维篇下篇

最近在学redis,由于笔者是学运维的,所以推荐学习运维的小伙伴参考,希望对大家有帮助! redis运维篇上篇:http://t.csdnimg.cn/MfPud 附加redis多用户管理:http://t.csdnimg.cn/DY3yx 目录 十.redis慢日志 十一.redis的key的有效…

dvwa kali SQL注入

high: 1.txt的来源 1.txt的内容 手动添加: id1&SubmitSubmit 执行: sqlmap -r /root/1.txt -p id --second-url "http://192.168.159.128:20000/vulnerabilities/sqli_blind/" --batch medium: 换链接,换cook…

HDFS存取策略联系

书上关于这部分分了三个点: 1.数据存放 2.数据读取 3.数据复制 但数据存放和数据复制都是数据写操作过程中的,“存放”体现一种思想,“复制”体现过程,整个数据写操作过程如下: 1.分块:当客户端写入一个…

【JS篇之】异常

前言:在代码编写过程中,最常遇到的就是程序异常。其实异常并非坏事,它可以让开发人员及时发现、定位到错误,提醒我们做正确的事情,甚至在某些时候,我们还会手动抛出异常。 1.异常的分类 在JS中&#xff0…

2021 OWASP Top 10-零基础案例学习

文章目录 A01:2021 – 权限控制失效情境 #1: SQL 注入攻击风险风险与后果解决方案情境 #2: 未经授权的访问控制漏洞风险与后果解决方案 A02:2021 – 加密机制失效情境 #1: 自动解密的信用卡卡号与SQL注入情境 #2: 弱SSL/TLS使用与会话劫持情境 #3: 不安全的密码存储与彩虹表攻击…

http实现post请求时本地没问题,线上报413错误、nginx配置免费https、nginx反向代理

MENU 错误原因解决其他方式关于nginx的文章 错误原因 前端发送请求以后后端没有收到请求 而客户端却报了413错误 是请求实体过大的异常 如果请求夹带着文件就可能造成请求实体过大 那这里是什么原因造成的呢 在基础的后端开发中 都会用到nginx反向代理 默认大小为1M 超过1M都会…

LinkedList与链表

文章目录 ArrayList的缺陷链表链表的概念及结构链表的实现 LinkedList的使用什么是LinkedListLinkedList具体使用 ArrayList和LinkedList的区别 ArrayList的缺陷 通过源码知道,ArrayList底层使用数组来存储元素 由于其底层是一段连续空间,当在ArrayList任…

Windows 11 系统安装时如何跳过联网和逃避微软账号登录

问题描述 Windows 11 是从 22H2 版本之后开始强制联网何登录微软账号的。 这就带来两个问题: 1、如果我的电脑没有网络或者网卡驱动有问题,那就无法继续安装系统了。 2、如果我有强怕症,就是不想登录微软账号,害怕个人信息泄露…

SpringEL表达式编译模式SpelCompilerMode详解

https://docs.spring.io/spring-framework/reference/core/expressions.html 在构建SpringEL表达式解析器时候,发现可以传递个SpelCompilerMode参数,这个值不传的话默认是OFF // SpelParserConfiguration config new SpelParserConfiguration(); Spel…

uniApp+Vue3+vite+Element UI或者Element Plus开发学习,使用vite构建管理项目,HBuilderX做为开发者工具

我们通常给小程序或者app开发后台时,不可避免的要用到可视化的数据管理后台,而vue和Element是我们目前比较主流的开发管理后台的主流搭配。所以今天石头哥就带大家来一起学习下vue3和Element plus的开发。 准备工作 1,下载HBuilderX 开发者…