前言
在 JavaScript 中,我们经常能听到"一切皆对象" 这一句看起来有点绝对甚至细想是错误的话,因为我们之间明明介绍了存在原始数据类型和对象两种数据类型。这是因为尽管严格来说存在其它数据类型,但 JS 通过包装对象 、原型链继承 和函数的对象特性,让几乎所有数据都表现出对象的行为。以下从多个维度深入解析这一特性。
一、包装对象机制
1. 原始类型(Primitive Values)
JavaScript 包含 6 种原始类型(ES6 + 新增Symbol
和BigInt
):
-
string
、number
、boolean
、null
、undefined
、symbol
、bigint
特点:原始类型并非对象,存储在栈内存中,无法直接添加属性或方法。
2. 引用类型(Reference Types)
-
Object
(普通对象)、Array
、Function
、Date
、RegExp
等特点:作为对象存储在堆内存中,可动态添加属性,通过引用地址访问。
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做了什么(不完整):
- 创建一个this对象
- 执行构造函数,给this对象添加属性和方法
- 返回this对象
-
包装对象类型 :
String
、Number
、Boolean
、Symbol
、BigInt
-
注意 :
null
和undefined
没有包装对象,因此调用方法会报错(如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过程中完成(完整)
- 创建一个空对象
- 让构造函数的this指向这个空对象
- 执行构造函数中的代码
- 将这个空对象的_proto_指向构造函数的原型对象
- 返回这个空对象
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 通过包装对象机制 和原型链继承,让原始类型在使用时表现出对象的行为,同时函数、数组等引用类型本身就是对象。这种设计模糊了 "数据" 与 "对象" 的界限,形成了高度动态灵活的编程范式。