揭秘JavaScript中“一切皆对象”:包装对象机制和原型链继承

前言

在 JavaScript 中,我们经常能听到"一切皆对象" 这一句看起来有点绝对甚至细想是错误的话,因为我们之间明明介绍了存在原始数据类型和对象两种数据类型。这是因为尽管严格来说存在其它数据类型,但 JS 通过包装对象原型链继承函数的对象特性,让几乎所有数据都表现出对象的行为。以下从多个维度深入解析这一特性。

一、包装对象机制

1. 原始类型(Primitive Values)

JavaScript 包含 6 种原始类型(ES6 + 新增SymbolBigInt):

  • stringnumberbooleannullundefinedsymbolbigint

    特点:原始类型并非对象,存储在栈内存中,无法直接添加属性或方法。

2. 引用类型(Reference Types)

  • Object(普通对象)、ArrayFunctionDateRegExp

    特点:作为对象存储在堆内存中,可动态添加属性,通过引用地址访问。

3. 原始类型的 "对象化"------ 包装对象

当对原始类型调用方法时,引擎自动把字面量转换成对应的包装类对象,所以可以访问属性和方法。

所以按常理在原始类型中会报错的语句num.a='a'可以执行:

js 复制代码
var num = 10;
num.a='a';
console.log(num.a);

但为什么最后的输出结果是undefined呢?

这是因为js是弱类型语言,所以只有在赋值语句执行时才会判断值的类型,当值为原始类型时,就会自动将包装对象上的属性移除,所以实际执行为:

js 复制代码
var num = 10;;
num.a='a';//new Number(10)
//delete num.a;
console.log(num.a);//undefined
  • new做了什么(不完整):

    1. 创建一个this对象
    2. 执行构造函数,给this对象添加属性和方法
    3. 返回this对象
  • 包装对象类型StringNumberBooleanSymbolBigInt

  • 注意nullundefined没有包装对象,因此调用方法会报错(如null.toString())。


js 复制代码
var arr=[1,2,3];//new Array(1,2,3);
arr.length=1;
console.log(arr);//[1]

var string='abc';//
string.length=1;//new String('abc');自动装箱
//delete string.length;
console.log(string.length);//3
console.log(string);//abc

按我们之间的介绍string.length应该是undefined,但这里为什么能得到3呢,这是因为有原型的存在,让我继续学习下面知识

二、原型链继承

1.函数原型

函数是一种类型,同时也是 Function 对象的实例。每个函数都有两个与原型相关的重要属性:

  • prototype 属性:函数特有的属性,指向一个普通对象,用于实现原型链继承。
  • [[Prototype]] 内部属性 :指向 Function.prototype,即所有函数都继承自 Function.prototype
js 复制代码
function Person(name) {
    this.name = name;
}

// Person.prototype 是一个普通对象,默认包含 constructor 属性
console.log(Person.prototype.constructor === Person); // true

const alice = new Person("Alice");
// alice 的 [[Prototype]] 指向 Person.prototype
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
意义

将构造函数的一些固定属性和方法挂载到原型上,在创建实例的时候不用重复执行

js 复制代码
function Car(color){
    this.color=color;
}
Car.prototype={
    price:1000000,
    year:2010,
    brand:"BMW",
}
const car1=new Car("green");
const car2=new Car("red");//每次创建车时只重新赋值可变的颜色属性
console.log(car1.brand);// BMW
注意
  • 实例对象可以访问原型上的属性和方法。
  • 实例对象无法修改原型上的属性和方法。
  • 实例对象无法删除原型上的属性和方法。

2.对象原型

每一个对象都有一个属性_proto_,该属性值也是一个对象

js 复制代码
let obj = {
    name: '张三',
    age: 18,

}
function Car(){
    this.color = 'red';
}
console.log(obj.__proto__);//[Object: null prototype] {}
console.log(obj.__proto__ === Object.prototype);//true
obj=new Car();//obj.__proto__=Car.prototype;
console.log(obj.__proto__ === Car.prototype);//true
  • 实例对象的隐式原型===构造函数的显示原型
  • 在new过程中完成(完整)
    1. 创建一个空对象
    2. 让构造函数的this指向这个空对象
    3. 执行构造函数中的代码
    4. 将这个空对象的_proto_指向构造函数的原型对象
    5. 返回这个空对象

3.原型链

对象的原型继承关系

  • 所有对象(包括数组、函数、正则等)都继承自Object.prototype
js 复制代码
const arr = \[1, 2, 3];
console.log(arr.toString());       // 输出 "1,2,3"(继承自Object.prototype)
function Person() {}
console.log(Person.prototype);     // 输出 Person.prototype 对象
console.log(Person.prototype instanceof Object);  // 输出 true

原型链示意图

css 复制代码
\[自定义对象] → \[构造函数的prototype] → \[Object.prototype] → \[null]
  • 例如:Array的原型链为 Array.prototype → Object.prototype → null

三、"一切皆对象" 的实践意义

动态特性与灵活性

  • 可随时为对象添加 / 删除属性:
js 复制代码
const user = {};
user.name = "Alice";         // 动态添加属性
user.sayHi = function() {};  // 动态添加方法

统一的编程接口

  • 所有对象共享Object.prototype的方法(如toString()hasOwnProperty()),降低学习成本。

函数式编程与面向对象的融合

  • 函数作为对象,既可以作为 "行为"(函数调用),也可以作为 "数据"(存储属性、作为参数传递)。

总结:JS "一切皆对象" 的本质

JavaScript 通过包装对象机制原型链继承,让原始类型在使用时表现出对象的行为,同时函数、数组等引用类型本身就是对象。这种设计模糊了 "数据" 与 "对象" 的界限,形成了高度动态灵活的编程范式。

相关推荐
小着2 小时前
vue项目页面最底部出现乱码
前端·javascript·vue.js·前端框架
呆呆的小草4 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
WHOAMI_老猫5 小时前
xss注入遇到转义,html编码绕过了解一哈
javascript·web安全·渗透测试·xss·漏洞原理
sunny-ll6 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
testleaf6 小时前
前端面经整理【1】
前端·面试
小前端大牛马6 小时前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy6 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
秋田君7 小时前
深入理解JavaScript设计模式之策略模式
javascript·设计模式·策略模式
潘小磊8 小时前
高频面试之11Flink
面试·flink
菜鸡爱上编程9 小时前
React16,17,18,19更新对比
前端·javascript·reactjs·react