面向对象思想

对象概念

  • 对象是单个事物的抽象
  • 对象是一个载体,承载了属性 (property)和方法(method)
    • 属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用"属性"记录具体是那一种动物,使用"方法"表示动物的某种行为(奔跑、捕猎、休息等等)。
    • 在实际开发中,对象是一个抽象的概念,可以将其简单理解为:数据集或功能集。
    • ECMAScript-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。
    • 严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。

什么是面向对象

面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维护性。

面向对象编程 ------ Object Oriented Programming,简称 OOP,是一种编程开发思想

它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。

因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。

面向对象与面向过程:

  • 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
  • 面向对象就是找一个对象,指挥得结果
  • 面向对象将执行者转变成指挥者
  • 面向对象不是面向过程的替代,而是面向过程的封装

面向对象的特性:

  • 封装
  • 继承
  • 多态

特性说明

封装

  • 封装是将对象的属性和方法包装在一个类中,并通过访问控制来隐藏对象的内部状态,只暴露必要的方法给外部使用。

继承

  • 继承是指一个类(子类)从另一个类(父类)继承属性和方法,从而实现代码的重用

多态

  • 多态性是指在继承关系中,子类可以覆盖父类的方法,并且子类的实例可以被当作父类的实例使用

原型和原型链

构造函数与实例关系

构造函数是创建实例的工厂函数,通过 new 操作符调用构造函数可以创建一个实例对象。构造函数在 JavaScript 中扮演着重要的角色,它不仅定义了实例的初始化行为,还通过 prototype 属性定义了实例共享的属性和方法。

构造函数的定义

构造函数是一种特殊的函数,用于创建和初始化对象。通过 new 关键字调用构造函数会创建一个全新的对象,并将这个对象的 [[Prototype]](即 __proto__ 属性)指向构造函数的 prototype 属性。

代码示例:

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Person); // true

实例与构造函数的关系

每个通过 new 关键字创建的实例都与构造函数具有特殊的关系。实例的 constructor 属性指向其构造函数,而构造函数的 prototype 属性定义了实例共享的属性和方法。

代码示例:

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};
const person = new Person('Alice');
person.sayHello(); // 输出: Hello, my name is Alice

实例与构造函数 prototype 属性的关系

当访问实例的属性或方法时,JavaScript 引擎会首先在实例本身上查找,如果找不到,则会沿着原型链向上查找,直到找到属性或方法或者到达原型链的终点(null)。

代码示例:

javascript 复制代码
function Person() {}
Person.prototype.species = 'human';
const person = new Person();
console.log(person.species); // 输出: human
console.log('species' in person); // 输出: false

prototype 属性与原型链

prototype 属性的作用

每个函数都有一个 prototype 属性,该属性是一个对象,用于定义该函数创建的实例共享的属性和方法。通过 prototype 属性,可以实现代码复用,避免在每个实例中重复定义相同的属性或方法。

代码示例:

javascript 复制代码
function Animal() {}
Animal.prototype = {
  constructor: Animal,
  eat: function() {
    console.log('The animal is eating.');
  }
};
const dog = new Animal();
const cat = new Animal();
dog.eat(); // 输出: The animal is eating.
cat.eat(); // 输出: The animal is eating.

原型链的概念

原型链是 JavaScript 实现继承的核心机制。每个对象都有一个 __proto__ 属性(即 [[Prototype]]),该属性指向其构造函数的 prototype 属性。通过这样的链式结构,实例可以访问到其构造函数、父级构造函数等层级的属性和方法。

代码示例:

javascript 复制代码
function Shape() {}
Shape.prototype.draw = function() {
  console.log('Drawing a shape.');
};
function Circle(radius) {
  this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
const circle = new Circle(5);
circle.draw(); // 输出: Drawing a shape.
console.log(circle.__proto__ === Circle.prototype); // true
console.log(Circle.prototype.__proto__ === Shape.prototype); // true

prototype 链的查找机制

当访问一个对象的属性或方法时,JavaScript 引擎会按照以下顺序进行查找:

  • 首先在对象自身的属性中查找。
  • 如果找不到,则沿着 __proto__ 属性向上查找,直到找到属性或方法或者到达原型链的终点(null)。
    代码示例:
javascript 复制代码
const obj = {
  a: 1
};
const obj2 = Object.create(obj);
obj2.b = 2;
const obj3 = Object.create(obj2);
obj3.c = 3;
console.log(obj3.a); // 输出: 1
console.log(obj3.b); // 输出: 2
console.log(obj3.c); // 输出: 3

原型链的实现细节

在 JavaScript 中,prototype__proto__ 是两个关键属性,前者定义了构造函数的原型,后者定义了实例对象的原型。通过设置 __proto__,可以手动修改对象的原型链。

代码示例:

javascript 复制代码
const proto = {
  foo: 'bar'
};
const obj = {
  __proto__: proto
};
console.log(obj.foo); // 输出: bar

实例与原型的关系

实例的 proto 属性

每个实例对象都有一个 __proto__ 属性,该属性指向其构造函数的 prototype 属性。通过 __proto__ 属性,实例可以访问到 prototype 上定义的属性和方法。

代码示例:

javascript 复制代码
function Person() {}
Person.prototype.name = 'Alice';
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(person.name); // 输出: Alice

原型的 prototype 属性

构造函数的 prototype 属性是一个对象,它包含了该构造函数创建的实例共享的属性和方法。通过 prototype 属性,可以实现代码复用和继承。

代码示例:

javascript 复制代码
function Animal() {}
Animal.prototype.species = 'animal';
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const dog = new Dog();
console.log(dog.species); // 输出: animal
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
原型链的层级

在 JavaScript 中,原型链可以有多个层级,每个层级对应一个构造函数的 prototype 属性。通过这种方式,可以实现多层级的继承。

代码示例:

javascript 复制代码
function Shape() {}
Shape.prototype.draw = function() {
  console.log('Drawing a shape.');
};
function Rectangle() {}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
function Square() {}
Square.prototype = Object.create(Rectangle.prototype);
Square.prototype.constructor = Square;
const square = new Square();
square.draw(); // 输出: Drawing a shape.

原型链的实现机制

原型链的查找过程

当访问一个对象的属性或方法时,JavaScript 引擎会按照以下步骤进行查找:

  1. 首先在对象自身的属性中查找。
  2. 如果找不到,则沿着 __proto__ 属性向上查找,直到找到属性或方法或者到达原型链的终点(null)。
    代码示例:
javascript 复制代码
const obj = {
  a: 1
};
const obj2 = {
  b: 2,
  __proto__: obj
};
const obj3 = {
  c: 3,
  __proto__: obj2
};
console.log(obj3.a); // 输出: 1
console.log(obj3.b); // 输出: 2
console.log(obj3.c); // 输出: 3

原型链的优化

在 JavaScript 中,可以通过优化原型链的设计来提高代码的性能和可维护性。例如,避免在原型链中定义大量属性和方法,而是将它们分散到多个层级中。

代码示例:

javascript 复制代码
function Shape() {}
Shape.prototype.draw = function() {
  console.log('Drawing a shape.');
};
function Rectangle() {}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.drawRect = function() {
  console.log('Drawing a rectangle.');
};
function Square() {}
Square.prototype = Object.create(Rectangle.prototype);
Square.prototype.constructor = Square;
Square.prototype.drawSquare = function() {
  console.log('Drawing a square.');
};
const square = new Square();
square.drawSquare(); // 输出: Drawing a square.
square.drawRect(); // 输出: Drawing a rectangle.
square.draw(); // 输出: Drawing a shape.

原型链的常见问题

在使用原型链时,需要注意一些常见的问题,例如属性覆盖、性能问题等。通过合理设计和优化,可以避免这些问题。

代码示例:

javascript 复制代码
function Animal() {}
Animal.prototype.species = 'animal';
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.species = 'dog';
const dog = new Dog();
console.log(dog.species); // 输出: dog
console.log(Animal.prototype.species); // 输出: animal

创建对象的四种方式及优缺点分析

由于前端开发中对象是核心概念之一,理解创建对象的方式及其优缺点对于高级前端开发工程师和架构师来说至关重要。本文将从工厂模式、构造函数模式、原型模式和组合模式四个方面详细解读每种创建对象方式的特点、代码实现以及适用场景。

工厂模式

工厂模式是一种常见的创建对象的方式,它通过工厂函数来生成对象实例。

概念

工厂模式的核心思想是将对象的创建逻辑封装在工厂函数中,调用工厂函数即可生成对象实例。这种方式能够将对象的创建过程与使用过程解耦,提高代码的可维护性和可扩展性。

代码示例

javascript 复制代码
// 工厂函数
function createPerson(name, age, gender) {
  return {
    name: name,
    age: age,
    gender: gender,
    sayHello: function() {
      console.log(`Hello, my name is ${this.name}`);
    }
  };
}
// 使用工厂函数创建对象
const person1 = createPerson('Alice', 30, 'female');
const person2 = createPerson('Bob', 25, 'male');

优点

  • 代码复用:工厂函数可以多次调用,减少重复代码。
  • 封装性:对象的创建逻辑被封装在工厂函数中,外部只需要调用即可。
  • 扩展性:可以通过修改工厂函数轻松增加新的属性或方法。

缺点

  • 内存占用:每个对象的方法都是独立的,无法共享,可能导致内存浪费。
  • 维护成本:如果需要修改方法,需要修改所有相关工厂函数。

适用场景

  • 当需要动态创建对象时。
  • 当希望将对象的创建逻辑与使用逻辑解耦时。

构造函数模式

构造函数模式是一种通过构造函数创建对象的方式,它利用JavaScript的原型机制来实现对象的创建。

概念

构造函数模式通过定义一个构造函数,然后使用new关键字来创建对象实例。构造函数中的this关键字指向新创建的对象。

代码示例

javascript 复制代码
// 定义构造函数
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
  };
}
// 使用构造函数创建对象
const person1 = new Person('Alice', 30, 'female');
const person2 = new Person('Bob', 25, 'male');

优点

  • 直观简单:代码结构清晰,易于理解。
  • 可扩展性:可以通过构造函数参数灵活传递初始化值。
  • 内存优化:方法在构造函数中定义,每个实例都有自己的方法引用。

缺点

  • 内存浪费:每个实例都包含自己的方法副本,占用更多内存。
  • 方法重复:相同的方法在每个实例中都会被多次定义,影响性能。

适用场景

  • 当需要初始化对象时需要传递参数。
  • 当希望以面向对象的方式创建对象时。

原型模式

原型模式是一种利用JavaScript原型链来创建对象的方式,它通过在构造函数的原型对象上定义方法和属性,实现方法的共享。

概念

原型模式的核心思想是将方法和属性定义在构造函数的prototype属性上,这样所有实例都可以共享这些方法和属性,从而减少内存占用。

代码示例

javascript 复制代码
// 定义构造函数
function Person() {}
// 在原型上定义属性和方法
Person.prototype.name = 'Default Name';
Person.prototype.age = 0;
Person.prototype.gender = 'other';
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};
// 使用构造函数创建对象
const person1 = new Person();
person1.name = 'Alice';
person1.age = 30;
person1.gender = 'female';
const person2 = new Person();
person2.name = 'Bob';
person2.age = 25;
person2.gender = 'male';

优点

  • 内存优化:所有实例共享原型上的方法,节省内存。
  • 动态扩展:可以在运行时动态添加或修改原型上的方法和属性。
  • 代码复用:原型上的方法可以被所有实例共享,减少代码重复。

缺点

  • 属性共享:原型上的属性会被所有实例共享,可能导致意外的副作用。
  • 初始化问题:无法在构造函数中初始化实例属性,需要在实例化后单独设置。

适用场景

  • 当需要多个实例共享方法时。
  • 当希望动态扩展对象方法时。

组合模式

组合模式是将构造函数模式和原型模式相结合的一种方式,它通过构造函数定义实例属性,通过原型定义共享方法,从而兼顾两者的优点。

概念

组合模式通过构造函数初始化实例的属性,同时在构造函数的原型上定义共享的方法,既保证了实例属性的独立性,又实现了方法的共享。

代码示例

javascript 复制代码
// 定义构造函数
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
}
// 在原型上定义共享方法
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};
// 使用构造函数创建对象
const person1 = new Person('Alice', 30, 'female');
const person2 = new Person('Bob', 25, 'male');

优点

  • 内存优化:方法共享,节省内存。
  • 初始化灵活:可以通过构造函数参数灵活初始化实例属性。
  • 代码复用:共享方法,减少重复代码。

缺点

  • 代码复杂性:需要同时管理构造函数和原型,代码结构稍微复杂。
  • 维护成本:修改原型上的方法需要重新测试所有相关实例。

适用场景

  • 当需要同时初始化实例属性和共享方法时。
  • 当希望在构造函数中进行复杂初始化逻辑时。

:::info 工厂模式适合动态创建对象,构造函数模式适合需要初始化参数的场景,

原型模式适合需要共享方法的场景,

而组合模式则是在构造函数和原型之间找到了一个平衡点,兼顾了两者的优点。

在实际开发中,通常会结合使用这些模式,以达到最佳的代码结构和性能表现。例如,可以使用构造函数模式初始化实例属性,使用原型模式定义共享方法,同时结合工厂模式动态创建对象,从而实现灵活且高效的对象创建机制。

:::

相关推荐
小小小小宇1 分钟前
webComponent实现一个拖拽组件
前端
满怀10151 分钟前
【Python核心库实战指南】从数据处理到Web开发
开发语言·前端·python
PBitW9 分钟前
工作中突然发现零宽字符串的作用了!
前端·javascript·vue.js
VeryCool10 分钟前
React Native新架构升级实战【从 0.62 到 0.72】
前端·javascript·架构
小小小小宇11 分钟前
JS匹配两数组中全相等对象
前端
xixixin_14 分钟前
【uniapp】uni.setClipboardData 方法失效 bug 解决方案
java·前端·uni-app
狂炫一碗大米饭14 分钟前
大厂一面,刨析题型,把握趋势🔭💯
前端·javascript·面试
星空寻流年20 分钟前
css3新特性第五章(web字体)
前端·css·css3
加油乐26 分钟前
JS计算两个地理坐标点之间的距离(支持米与公里/千米)
前端·javascript
小小小小宇27 分钟前
前端在 WebView 和 H5 环境下的缓存问题
前端