掌握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.rhkb.cn/news/319240.html

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

相关文章

关于MS-DOS时代的回忆

目录 一、MS-DOS是什么? 二、MS-DOS的主要功能有哪些? 三、MS-DOS的怎么运行的? 四、微软开源MS-DOS源代码 五、高手与漂亮女同学 一、MS-DOS是什么? MS-DOS(Microsoft Disk Operating System)是微软公…

第五篇:通信脉络:探索计算机外设与总线体系的精髓

通信脉络:探索计算机外设与总线体系的精髓 1 引言 在这个技术日新月异的时代,理解计算机系统的基本构成要素 —— 总线和外设 —— 对于每个从事技术工作的人来说都是至关重要的。这些组件不仅是计算机通信的基石,也直接影响着系统的性能、效…

Stable Diffusion webUI 配置指南

Stable Diffusion webUI 配置指南 本博客主要介绍部署Stable Diffusion到本地,生成想要的风格图片。 文章目录 Stable Diffusion webUI 配置指南1、配置环境(1)pip环境[可选](2)conda环境[可选] 2、配置Stable Diffu…

STM32 F103C8T6学习笔记17:类IIC通信(SMBus协议)—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识:1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

图像处理ASIC设计方法 笔记21 标记ASIC的顶层状态机

目录 (一)标记ASIC的工作流程1 ASIC首先从控制寄存器内读出待标记图像的基本参数2若写入了有效的启动命令,则进入下面一帧图像的标记过程。3 ASIC通过接口模块从FIFO1中读取待标记的图像4一帧图像初步标记完成后进行等价表的整理压缩5从临时标记存储器中读取临时标记送入标记…

快速构建vscode pytest 开发测试环境

如果不想用 heavy 的pycharm vscode 也是1个很好的选择 安装python SDK pacman -S python [gatemanmanjaro-x13 tmp]$ pacman -Q python python 3.11.8-1安装Vscode 很多中方法 yay -S visual-studio-code-bin [gatemanmanjaro-x13 tmp]$ pacman -Q | grep -i visual visua…

【UE5】动态播放媒体

最近项目中有一个需求,需要将场景中的42块屏幕都显示媒体内容,想着如果每一块屏幕都创建一个MediaPlayer资产、一个MediaSource资产、一个MediaTexture资产及创建对应的Material,就是4*42168个资产需要维护了,所以想着就全部采用动…

DDD:根据maven的脚手架archetype生成ddd多模块项目目录结构

随着领域驱动的兴起,很多人都想学习如何进行ddd的项目开发,那ddd的项目结构是怎么样的?又是如何结合SpringBoot呢?那么针对这个问题,笔者使用maven的archetype封装一个相对通用的ddd的项目目录,方便一键生成…

【Github】将github仓库作为图床使用

创建github仓库 首先创建一个github仓库专门用于存储图片,具体步骤如下: 1.点击新的仓库按钮 2.初始配置:随便填写一个仓库名;这里的仓库状态一定要是public公开的,不然后面访问不了图片 下载PicGo PicGo官网 在A…

微信小程序 uniapp家庭食谱菜谱食材网上商城系统小程序ko137

随着生活节奏的不断加快,越来越多的人因为工作忙而没有时间自己出去订购喜欢的菜品。随着Internet的飞速发展,网络已经成为我们日常生活中必不可少的部分,越来越多的人也接受了电子商务这种快捷、方便的交易方式。网上订餐其独有的便捷性和直…

Ubuntu20安装torch1.13和pytorch_geometric2.3.0(对应cuda11.6)

在torch下载页面搜索1.13https://pytorch.org/get-started/previous-versions/,wheel安装方式(激活conda虚拟环境) pip install torch1.13.0cu116 torchvision0.14.0cu116 torchaudio0.13.0 --extra-index-url https://download.pytorch.org…

WPF之XmlDataProvider使用

1,WPF XAML支持数据提供(DataProvider),但其提供的数据只供查看不可进行修改,删除,添加等。 数据提供者都继承自System.Windows.DataSourceProvider类,目前,WPF只提供两个数据提供者…

Docker入门篇来啦~

文章目录 1虚拟化技术1.1 硬件级虚拟化1.2 操作系统级虚拟化 2 Docker是什么2.1 Docker介绍2.2 容器和虚拟机的区别2.3 为什么使用Docker 3 Docker运行环境部署3.1 Docker安装3.2 Docker服务启动 4 Docker核心组件4.1 镜像4.1.1 镜像的基本概念4.1.2 镜像的组成结构4.1.3 镜像的…

【Hadoop】--基于hadoop和hive实现聊天数据统计分析,构建聊天数据分析报表[17]

目录 一、需求分析 1、背景介绍 2、目标 3、需求 4、数据内容 5、建库建表 二、ETL数据清洗 1、数据问题 2、需求 3、实现 4、扩展概念:ETL 三、指标计算 1、指标1:统计今日消息总量 2、指标2:统计每小时消息量、发送量和接收用…

Unity UGUI Image 点击事件忽略空白像素区域

我们会遇到图片不是方形的不规则图片。这个时候我们希望只有点击到图像内容本身才算点击,点击空白区域则不算点击。而UGUI对图片的处理是整个图片都会算作点击区域,这样不能满足于我们的使用需求了。 首先我们需要把图片本身的Read/Write 选项打开 然后…

CSS精灵图、字体图标、HTML5新增属性、界面样式和网站 favicon 图标

精灵图 为什么要使用精灵图 一个网页中往往会应用很多小的背景图像作为修饰,当网页中的图像过多时,服务器就会频繁地接收和发送请求图片,造成服务器请求压力过大,这将大大降低页面的加载速度,因此,为了有效地减少服务…

【源码阅读】Golang中的go-sql-driver库源码探究

文章目录 前言一、go-sql-driver/mysql1、驱动注册:sql.Register2、驱动实现:MysqlDriver3、RegisterDialContext 二、总结 前言 在上篇文章中我们知道,database/sql只是提供了驱动相关的接口,并没有相关的具体实现,具…

分层图像金字塔变压器

文章来源:hierarchical-image-pyramid-transformers 2024 年 2 月 5 日 本文介绍了分层图像金字塔变换器 (HIPT),这是一种新颖的视觉变换器 (ViT) 架构,设计用于分析计算病理学中的十亿像素全幻灯片图像 (WSI)。 HIPT 利用 WSI 固有的层次结…

JDK14特性

JDK14 1 概述2 语法层面的变化1_instanceof的模式匹配(预览)2_switch表达式(标准)3_文本块改进(第二次预览)4_Records 记录类型(预览 JEP359) 3 API层面的变化4 关于GC1_G1的NUMA内存分配优化2_弃用SerialCMS,ParNewSerial Old3_删除CMS4_ZGC on macOS and Windows 4 其他变化1…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.2-链接脚本

前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…