目录

理解Javascript面向对象

有点不同的面向对象

JavaScript采用基于原型的面向对象设计,这一选择既有历史因素也有技术优势。从语言诞生背景来看,JavaScript最初需要与Java形成差异化定位以避免直接竞争,因此刻意绕过了"类"的概念,转而通过原型机制实现继承关系。这种设计策略不仅实现了与Java的错位发展,更催生出独特的编程范式:

  1. 动态扩展能力

    原型系统的核心优势在于其动态性------对象结构和继承关系在运行时仍可自由调整。开发者能够随时为原型添加新属性/方法,所有实例将自动继承这些变更。这种特性突破了传统类式语言(如Java)在编译时固化类结构的限制,使得功能扩展如同"动态捏塑"般灵活。虽然JavaScript本身采用单继承原型链,但通过原型组合(如Object.assign())或混入模式,可以实现类似多继承的效果。

  2. 声明式语法与内存效率

    通过构造函数创建对象时,只需用new关键字即可快速实例化,无需预先编写冗长的类定义。更关键的是,方法统一存储在构造函数的prototype属性上,所有实例通过原型链共享同一份方法代码。相比每个实例单独存储方法的实现方式,这种设计显著减少了内存占用,在需要创建大量实例的场景下优势尤为明显。

  3. 原型即对象的统一模型

    JavaScript将所有实体抽象为对象(包括函数、原型甚至构造函数本身),形成了高度自洽的编程模型。这种设计让继承关系不再依赖特定的类结构,而是通过原型链的动态链接实现。开发者可以直接操作原型对象,例如通过Object.create()基于现有对象创建新类型,或是动态修改__proto__(现代更推荐用Object.setPrototypeOf())改变继承关系,展现出传统类式语言难以企及的灵活性。

这种基于原型的语言特性,使得JavaScript既能保持轻量简洁的语法形式,又能满足动态语言快速迭代的需求,最终成就了其在前端开发领域不可替代的地位。随着ES6引入class语法糖,虽然表面上更接近传统面向对象范式,但其底层实现仍完全基于原型系统。

"类"的实现机制

构造函数与原型链模型

JavaScript 的"类"本质是构造函数与原型链的协同运作。实际上,构造函数本质上与普通函数并无二致,底层并没有进行任何特殊处理。它们之间的主要区别在于使用方式和语义约定:首字母大写的构造函数用于定义对象的结构和初始化逻辑,而普通函数(通常首字母小写)则用于实现具体的功能。

当使用new调用时,引擎的执行步骤如下:

  1. 创建空对象
  2. 给对象的属性赋值,绑定原型链([[Prototype]]指向构造函数的prototype)
  3. 执行构造函数,将构造函数this指向新对象
  4. 根据构造函数返回值类型决定最终对象

代码示例:

javascript 复制代码
// 定义一个Person对象的构造函数
function Person(name,age){
	this.name = name 
	this.age = age
}
// 创建一个Person对象
const person1 = new Person("Jack",18) // Person {name:'Jack',age:'18'} 
console.log(person1.constructor.prototype === Person.prototype) // true
console.log(person1.__proto__ === Person.prototype) // true

原型链的动态性是其核心特征。所有实例共享原型方法(如上例中的greet),修改原型会立即影响所有已存在实例。这种设计在内存效率(避免重复存储方法)与扩展性(动态添加功能)之间取得平衡,但也要求开发者谨慎操作原型。

语法糖Class

为了迎合大众的开发习惯,ES6中引入了class语法糖来实现和别的语言类似的面向对象实现过程,但是本质上还是基于原型链来实现,是原型系统的结构化表达:

javascript 复制代码
class Person {
    static count = 0 // 静态属性(构造函数属性)
    constructor(name) {
        this.name = name
        Person.count++
    }
    greet() { // 自动存入Person.prototype
        console.log(this.name)
    }
}
// 等价于
function Person(name) { /*...*/ }
Person.count = 0
Person.prototype.greet = function() { /*...*/ }

类语法引入了几个重要改进:

  1. 规范了"类"的创建方法
  2. 强制使用new调用构造函数,避免全局污染
  3. 更直观的继承语法(extends/super)
  4. 静态属性声明和私有字段支持

但底层继承机制未变,通过Object.getPrototypeOf(Student) === Person可验证原型链关系。

封装的实现机制

在类中,某些重要数据(如不希望被恶意篡改或仅供内部使用的数据)需要隐藏起来,以确保安全性。此时,我们只希望暴露获取这些数据的方法,或者限制对这些属性的修改,此时就需要将这个属性定义为私有,这也称为封装(隐藏内部实现)。

在 ES6 之前,JavaScript 并没有提供原生的私有属性定义方式,因此我们需要自行实现。一种常见的方法是利用闭包的特性来封装私有属性。代码示例:

javascript 复制代码
function Person(id){
	let _id = id // 使用"_" 来区分私有属性
	this.getId = function(){
		return _id
	}	
	this.setId = function(value,authKey){
		if(authKey === "auth123"){
			_id = value // 如果授权密钥正确,更新私有属性
		}
		throw new Error("Invalid authKey!") // 如果授权密钥错误,抛出错误
	}
}

到了 ES6,新出了Symbol类型,由于每个Symbol的值都是唯一的,并且需要获取Symbol值需要知道该Symbol的引用。利用这一点,也可以实现私有属性,但不能完全实现,外部通过getOwnPropertySymbols 还是可以访问到。代码示例:

javascript 复制代码
function Person(id) {
  const _id = Symbol('_id')
  this[_id] = id;
  this.getId = function () {
    return this[_id];
  };
	this.setId = function (value, authKey) {
	  if (authKey === "auth123") {
	    this[_id] = value; // 如果授权密钥正确,更新私有属性
	  } else {
	    throw new Error("Invalid authKey!"); // 如果授权密钥错误,抛出错误
	  }
  }
}
const p = new Person(10)
Object.getOwnPropertySymbols(p)[0]

再发展到ES2019,JS真正实现了原生的私有属性,可以使用class结合"#"关键字来定义一个私有属性,这样子外部是完全访问不到这个属性的。代码示例:

javascript 复制代码
class Person {
  // 使用 # 前缀定义私有属性
  #_id;
  constructor(id) {
    this.#_id = id;
  }
  getId() {
    return this.#_id;
  }
  setId(value, authKey) {
    if (authKey === "auth123") {
      this.#_id = value; // 如果授权密钥正确,更新私有属性
    } else {
      throw new Error("Invalid authKey!"); // 如果授权密钥错误,抛出错误
    }
  }
}

继承的实现机制

Javascript使用原型链进行继承,每个对象都有一个内部属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问),它指向另一个对象(即其原型)。当访问对象的属性或方法时,如果当前对象没有定义该成员,JavaScript 会沿着原型链向上查找,直到找到或到达 null

使用原型链模拟继承:

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

function Student(name, grade) {
  Person.call(this, name);
  this.grade = grade;
}
Object.setPrototypeOf(Student.prototype, Person.prototype);
const student = new Student("Alice", 10);
student.sayHi(); // "Hi, I'm Alice"
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
excel几秒前
webpack 核心编译器 七 节
前端
Debug 熊猫7 分钟前
【Java基础】10章、单例模式、final关键字的使用技巧和使用细节、单例模式-懒汉式、单例模式-饿汉式【3】
java·javascript·后端·单例模式
一只月月鸟呀8 分钟前
HTML中数字和字母不换行显示
前端·html·css3
天下代码客28 分钟前
【八股】介绍Promise(ES6引入)
前端·ecmascript·es6
lb291741 分钟前
CSS 3D变换,transform:translateZ()
前端·css·3d
啊阿狸不会拉杆44 分钟前
第二十二章:Python-NLTK库:自然语言处理
前端·python·自然语言处理
萧寂1731 小时前
html实现手势密码
前端·css·html
excel1 小时前
webpack 核心编译器 八 节
前端
JoyZ1 小时前
AI的时代学习还有意义吗?
前端