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

news2024/11/19 19:27:17

在这里插入图片描述

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.coloradmin.cn/o/1642115.html

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

相关文章

16、ESP32 Web

Web 服务器具有移动响应能力,可以使用本地网络上的任何设备作为浏览器进行访问。 示例功能: 构建 Web 服务器控制连接到 ESP32 的 LED在本地网络的浏览器上输入 ESP32 IP 地址访问 Web 服务器通过单击 Web 服务器上的按钮,更改 LED 状态 //…

C#语言基础

一、复杂数据类型 1. 枚举 1.1 基本概念 1.1.1 枚举是什么 枚举是一个被命名的整型常量的集合,一般用它来表示状态、类型等等 1.1.2 申明枚举和申明枚举变量 申明枚举和申明枚举变量是两个概念 申明枚举:相当于是创建一个自定义的枚举类型 申明枚…

C#实战—代码实现收发文件智能化

在信息化的今天,收发电子文档几乎是每个朋友都要经历的事情。比如班级学委和班长需要收发作业,企业管理者需要收发工作文件。但是!!! 每到要交结果时,往往会发现总会有一些人没有即使交上,50个…

【Leetcode每日一题】 综合练习 - 全排列 II(难度⭐⭐)(71)

1. 题目解析 题目链接:47. 全排列 II 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 算法思路梳理 为了生成给定数组nums的全排列,同时避免由于重复元素导致的重复排列,我们可以遵…

面试中算法(2的整数次幂)

判断一个正整数是否是2的整数次幂(如16是2的4次方,返回true;18不是2的整数次幂,则返回false),要求性能尽可能高。 使用一个整型变量,让它从1开始不断乘以2,将每一次乘2的结果和 目标整数进行比较…

【python的魅力】:教你如何用几行代码实现文本语音识别

文章目录 引言一、运行效果二、文本转换为语音2.1 使用pyttsx32.2 使用SAPI实现文本转换语音2.3 使用 SpeechLib实现文本转换语音 三、语音转换为文本3.1 使用 PocketSphinx实现语音转换文本 引言 语音识别技术,也被称为自动语音识别,目标是以电脑自动将…

【网站项目】社区互助平台

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

Object Desktop - Stardock 软件开发商系列套件

Windows 的一大优势在于可高度自定义,能让我们把它打造成一个最符合自己需求的工作和娱乐工具。 在桌面增强工具套件 Object Desktop 中,包含了 Stardock 旗下 10 款生产力和美化工具,可帮助我们打造出美观、实用、高效的 Windows 系统。 St…

Leetcode—163. 缺失的区间【简单】Plus

2024每日刷题&#xff08;126&#xff09; Leetcode—163. 缺失的区间 实现代码 class Solution { public:vector<vector<int>> findMissingRanges(vector<int>& nums, int lower, int upper) {int n nums.size();vector<vector<int>> an…

文件(夹)批量重命名数字、字母、日期、中文数字大写小写

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 目标是重命名下面5个文件&#xff08;也可以是文件夹等&#xff0c;任意&#xff09;&#xff0c;从大写中文数字“贰”开始 打开工具&#xff0c;找到“文…

SNR: Signal to Noise Ratio

https://www.xx.com/watch?vmyrZ_R6xIZA Fang, Y., Armin, A., Meredith, P. et al. Accurate characterization of next-generation thin-film photodetectors. Nature Photon 13, 1–4 (2019). https://doi.org/10.1038/s41566-018-0288-z Wang, F., Zhang, T., Xie, R. e…

[XYCTF新生赛]-PWN:fmt解析(scanf格式化字符串漏洞,任意地址写)

查看保护 查看ida 这里没什么好说的 完整exp&#xff1a; from pwn import* context(log_leveldebug) #pprocess(./fmt) premote(gz.imxbt.cn,20975) backdoor0x4012BEp.recvuntil(bgift: ) printf_addrint(p.recv(14),16) print(hex(printf_addr)) libcELF(./libc-2.31.so) …

【计算机网络】循环冗余校验:Cyclic Redundancy Check

1. 任务目标 利用循环冗余校验&#xff08;CRC&#xff09;检测错误。 循环冗余校验&#xff08;英语&#xff1a;Cyclic redundancy check&#xff0c;通称 CRC&#xff09;是一种根据网上数据包或计算机文件等数据产生简短固定位数校验码的一种散列函数&#xff0c;主要用来…

消息队列与信号量(基本概念及操作接口介绍)

一、消息队列 基本概念 System V消息队列是Unix系统中一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;它允许进程互相发送和接收数据块&#xff08;消息&#xff09; 操作系统可以在内部申请一个消息队列&#xff0c;可以让不同的进程向消息队列中发送数据块&…

Java中使用RediSearch进行高效数据检索

RediSearch是一款构建在Redis上的搜索引擎&#xff0c;它为Redis数据库提供了全文搜索、排序、过滤和聚合等高级查询功能。通过RediSearch&#xff0c;开发者能够在Redis中实现复杂的数据搜索需求&#xff0c;而无需依赖外部搜索引擎。本文将介绍如何在Java应用中集成并使用Red…

2024抖音直播带货-直播间拆解:抖店运营从入门到精通(56节课)

起号原理方式以及节点处理 类目的选择选品思路 付费流量投放原理 直播间进阶玩法 课程内容 直播间搭建标准自然起号(0-1)原理 方式 以及节点处理 老号重启(0-1)原理 方式 以及节点处理 账号在线人数稳定 原理 方式 以及节点处理 账号销售额放大 原理 方式 以及节点处理…

【Linux】namespace 隔离、cgroup 控制

文章目录 五、namespace 隔离dd -- 读取、转换并输出数据mkfs -- 格式化文件系统df -- 显示文件系统磁盘使用情况mount -- 加载文件系统到指定的加载点unshare -- 创建子进程&#xff0c;同时与父程序不共享namespace一个 demo 六、cgroup(Control Group) 相关命令pidstat -- 监…

Stable Diffusion AI绘画

我们今天来了解一下最近很火的SD模型 ✨在人工智能领域&#xff0c;生成模型一直是研究的热点之一。随着深度学习技术的飞速发展&#xff0c;一种名为Stable Diffusion的新型生成模型引起了广泛关注。Stable Diffusion是一种基于概率的生成模型&#xff0c;它可以学习数据的潜…

nginx变量自定义日志收集

内置变量 $remote_addr&#xff1b;存放了客户端的地址&#xff0c;注意是客户端的公网IP&#xff0c;也就是一家人访问一个网站&#xff0c;则会显示为路由器的公网IP。 $args&#xff1b;变量中存放了URL中的指令 [rootlocalhost conf.d]# cat pc.conf server {listen 80;se…