JavaScript面向对象编程核心概念与实现详解

作者:闭包旅人

时间:2026-10-30

浏览:140

本文系统讲解JavaScript面向对象编程(OOP)的核心概念,涵盖继承、封装、多态与抽象四大特性。详细对比浅拷贝与深拷贝的实现机制,解析Object.create()的使用与模拟方法,并深入探讨类继承、静态成员、私有公有变量及模块化模式,帮助开发者掌握JS中模拟传统OOP的最佳实践。

文章目录

JavaScript面向对象编程概述

面向对象程序设计(Object-oriented programming,简称OOP)是一种程序设计的范型,也是软件开发的重要方法。在OOP中,对象被视为程序的基本单元,它是类的具体实例。通过将程序逻辑与数据封装在对象内部,OOP旨在提升软件系统的可重用性、灵活性以及可扩展能力。

通常,面向对象编程包含四个核心特征:继承、封装、多态和抽象。

在JavaScript中,对象形式的继承可以通过多种方式实现,其中拷贝是一种常见手段。

对象拷贝:浅拷贝与深拷贝

浅拷贝是指仅复制对象的第一层属性。如果属性是基本类型,则复制其值;如果是引用类型,则只复制指向内存的地址。


var Person = {
  name: 'allin',
  age: 18,
  address: {
    home: 'home',
    office: 'office',
  }
  sclools: ['x','z'],
};

var programer = {
  language: 'js',
};

function extend(p, c){
  var c = c || {};
  for( var prop in p){
    c[prop] = p[prop];
  }
}
extend(Person, programer);
programer.name; // allin
programer.address.home; // home
programer.address.home = 'house'; //house
Person.address.home; // house

从上述浅拷贝的结果可以看出,其缺陷在于:当修改子对象中引用类型的属性值时,会同时影响父对象中的对应值。这是因为在浅拷贝过程中,对引用类型的处理仅仅是复制了内存地址,导致父子对象指向了内存中的同一份数据副本。

为了解决浅拷贝的问题,可以使用深拷贝技术。


function extendDeeply(p, c){
  var c = c || {};
  for (var prop in p){
    if(typeof p[prop] === "object"){
      c[prop] = (p[prop].constructor === Array)?[]:{};
      extendDeeply(p[prop], c[prop]);
    }else{
      c[prop] = p[prop];
    }
  }
}

通过递归遍历对象的所有属性,对引用类型进行深层复制,可以实现深拷贝。这样,子对象中引用类型的修改将不会影响到父对象,因为它们在内存中拥有独立的副本。


extendDeeply(Person, programer);
programer.address.home = 'allin';
Person.address.home; // home
利用call和apply继承
function Parent(){
  this.name = "abc";
  this.address = {home: "home"};
}
function Child(){
  Parent.call(this);
  this.language = "js"; 
}
ES5中的Object.create()
var p = { name : 'allin'};
var obj = Object.create(o);
obj.name; // allin

Object.create()与原型链模拟

Object.create()方法作为new操作符的替代方案,是在ES5标准引入后才出现的。开发者也可以手动模拟该方法的行为:


//模拟Object.create()方法
function myCreate(o){
  function F(){};
  F.prototype = o;
  o = new F();
  return o;
}
var p = { name : 'allin'};
var obj = myCreate(o);
obj.name; // allin

目前,主流浏览器的最新版本(包括IE9及以上)均已支持此方法。若需兼容较老的浏览器,可以使用以下代码自行部署该功能:


  if (!Object.create) {
    Object.create = function (o) {
       function F() {}
      F.prototype = o;
      return new F();
    };
  }

类的继承与父类方法调用

在JavaScript中实现类的继承是面向对象编程的关键环节。


Object.create()
function Person(name, age){}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){
  console.log('eating...');
}
function Programmer(name, age, title){}

Programmer.prototype = Object.create(Person.prototype); //建立继承关系
Programmer.prototype.constructor = Programmer; // 修改constructor的指向

在子类中调用父类方法是继承实现中的重要步骤,通常通过apply或call方法完成。


function Person(name, age){
  this.name = name;
  this.age = age;
}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){
  console.log('eating...');
}

function Programmer(name, age, title){
  Person.apply(this, arguments); // 调用父类的构造器
}


Programmer.prototype = Object.create(Person.prototype);
Programmer.prototype.constructor = Programmer;

Programmer.prototype.language = "js";
Programmer.prototype.work = function(){
  console.log('i am working code in '+ this.language);
  Person.prototype.eat.apply(this, arguments); // 调用父类上的方法
}

封装、命名空间与模块化

封装是将数据和操作数据的方法绑定在一起,并隐藏内部实现细节的过程。

命名空间用于避免变量名冲突,在JavaScript中尤为重要。

由于JavaScript原生没有命名空间的概念,开发者可以利用对象来模拟命名空间,从而组织代码结构。


var app = {}; // 命名空间app
//模块1
app.module1 = {
  name: 'allin',
  f: function(){
    console.log('hi robot');
  }
};
app.module1.name; // "allin"
app.module1.f(); // hi robot

静态成员属于类本身而非类的实例,通常用于工具函数或常量定义。


function Person(name){
  var age = 100;
  this.name = name;
}
//静态成员
Person.walk = function(){
  console.log('static');
};
Person.walk(); // static

在JavaScript中区分私有与公有成员是实现封装的关键。


function Person(id){
  // 私有属性与方法
  var name = 'allin';
  var work = function(){
    console.log(this.id);
  };
  //公有属性与方法
  this.id = id;
  this.say = function(){
    console.log('say hello');
    work.call(this);
  };
};
var p1 = new Person(123);
p1.name; // undefined
p1.id; // 123
p1.say(); // say hello 123

模块化是一种将代码分解为独立、可复用单元的开发模式。


var moduleA;
moduleA = function() {
  var prop = 1;

  function func() {}

  return {
    func: func,
    prop: prop
  };
}(); // 立即执行匿名函数

通过立即执行函数或特定模式,prop和func不会被泄露到全局作用域。另一种写法是使用new关键字创建实例:


moduleA = new function() {
  var prop = 1;

  function func() {}

  this.func = func;
  this.prop = prop;
}

多态、方法重载与抽象类

多态允许不同类的对象对同一消息做出不同的响应,增强了代码的灵活性。

JavaScript可以通过arguments对象模拟方法重载的行为。

利用arguments属性可以获取函数调用时传入的实际参数个数,基于这一点可以模拟方法重载的效果。


function demo(a, b ){
  console.log(demo.length); // 得到形参个数
  console.log(arguments.length); //得到实参个数
  console.log(arguments[0]); // 第一个实参 4
  console.log(arguments[1]); // 第二个实参 5
}

demo(4, 5, 6);
//实现可变长度实参的相加
function add(){
  var total = 0;
  for( var i = arguments.length - 1; i >= 0; i--){
    total += arguments[i];
  }
  return total;
}

console.log(add(1)); // 1
console.log(add(1, 2, 3)); // 7


// 参数不同的情况
function fontSize(){
  var ele = document.getElementById('js');
  if(arguments.length == 0){
    return ele.style.fontSize;
  }else{
    ele.style.fontSize = arguments[0];
  }
}
fontSize(18);
console.log(fontSize());

// 类型不同的情况
function setting(){
  var ele = document.getElementById('js');
  if(typeof arguments[0] === "object"){
    for(var p in arguments[0]){
      ele.style[p] = arguments[0][p];
    }
  }else{
    ele.style.fontSize = arguments[0];
    ele.style.backgroundColor = arguments[1];
  }
}
setting(18, 'red');
setting({fontSize:20, backgroundColor: 'green'});

方法重写允许子类提供父类中已存在方法的特定实现。


function F(){}
var f = new F();
F.prototype.run = function(){
  console.log('F');
}
f.run(); // F

f.run = function(){
  console.log('fff');
}
f.run(); // fff

抽象类用于定义接口规范,不应被直接实例化。

在构造函数中抛出异常(如throw new Error(''))可以防止该类被直接调用,从而模拟抽象类的行为。


function DetectorBase() {
  throw new Error('Abstract class can not be invoked directly!');
}

DetectorBase.prototype.detect = function() {
  console.log('Detection starting...');
};
DetectorBase.prototype.stop = function() {
  console.log('Detection stopped.');
};
DetectorBase.prototype.init = function() {
  throw new Error('Error');
};

// var d = new DetectorBase();// Uncaught Error: Abstract class can not be invoked directly!

function LinkDetector() {}
LinkDetector.prototype = Object.create(DetectorBase.prototype);
LinkDetector.prototype.constructor = LinkDetector;

var l = new LinkDetector();
console.log(l); //LinkDetector {}__proto__: LinkDetector
l.detect(); //Detection starting...
l.init(); //Uncaught Error: Error

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章