js原型链与自动装箱机制
目录
- 前言
- 基于原型生成对象
- 修改原型对象
- 构造函数的机制
- 原型对象与原型链
- 原型链相关方法
- 补充
- 1. 自动装箱机制
- 2. __proto__的存在原因
- 3. 关键区别
- 4. 示例验证
- 5. 总结
前言
在如今的主流语言中,大部分语言都是通过类来产生对象
但js是基于原型生成对象
- java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("我的名字是" + name + ",我今年" + age + "岁");
}
public static void main(String[] args) {
Person p = new Person("张三", 18);
p.sayHello();
}
}
- python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print(f"我的名字是{self.name},我今年{self.age}岁")
p = Person("张三", 18)
p.say_hello()
等等
基于原型生成对象
而js是通过原型生成对象的,简而言之就是复制原型对象再对新对象进行修改
主要原因是js设计者布兰登・艾奇喜欢函数式编程,并且最开始是为网页设计者而设计,尽量简单化语法
至于为什么后面又出现了class关键字以及具有二义性的function,是历史中为了迎合当时爆火的java语法靠拢。而箭头函数和class关键字就是解决函数二义性产生的
const obj = {};
console.log(obj.__proto__); // [Object: null prototype] {}
js所有方式创建的对象都会指向一个原型
function a() {
};
console.log(a.__proto__); // {}
修改原型对象
Object.create()
创建一个对象,并为其指定原型对象
const a = {
name: 'ad'
};
const b = Object.create(a);
console.log(b.__proto__); // { name: 'ad' }
后面还可以传入原型所需参数
const person = {
arm: 2,
legs: 2,
walk() {
console.log("walking");
},
};
const john = Object.create(person, {
name: {
value: "John",
enumerable: true,
},
age: {
value: 18,
enumerable: true,
},
});
console.log(john.__proto__ === person); // true
console.log(john); // { name: 'John', age: 18 }
john 打印结果不包含 arm 和 legs 是因为它们属于原型对象 person,而 console.log 默认只显示对象自身的可枚举属性。若需要查看原型链上的属性,可以手动展开控制台输出中的 __proto__
层级。
构造函数的机制
当使用new 来使用function时就会把function当做构造函数,与class一样,它们会经历以下步骤
// 1. 创建一个空的简单 JS 对象(即 { } )
// 2. 为步骤 1 新创建的对象添加属性 \__proto__,将该属性链接至构造函数的原型对象
// 3. 将步骤 1 新创建的对象作为 this 的上下文
// 4. 如果该函数没有返回对象,则返回 this
function Goods(name, price) {
// 创建空对象,将之后的内容放入其中
// 链接原型对象
// 使this指向此对象
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
// 函数没有返回值则返回this
}
console.log(Goods.prototype); // {}
console.log(Goods); // [Function: Goods]
class也是如此
class Goods {
constructor(name, price) {
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
}
}
console.log(Goods.prototype); // {}
console.log(Goods); // [Function: Goods]
原型对象与原型链
如前面所见,构造函数的原型对象与其实例的原型对象是一样的
class Goods {
constructor(name, price) {
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
}
}
const apple = new Goods('apple', 100);
console.log(Goods.prototype === apple.__proto__); // true
所以它们有如下关系
之所以方法要挂在原型对象上面,是因为由构造函数实例化出来的每一个实例对象,属性值是不相同的,所以需要每个对象独立有一份。
但是对于方法而言,所有对象都是相同的,因此我们不需要每个对象拥有一份,直接挂在原型对象上面共用一份即可。
所谓原型链就是链接实例对象、构造函数、原型对象的链路
不过不推荐直接向原型对象上挂载自己的方法,如有需要可以使用class extends 继承所需原型对象。
class myNum extends Number{
constructor(...args){
super(...args);
}
zhangsan(){}
}
const i = new myNum(1);
i.zhangsan();
原型链相关方法
- Object.getPrototypeOf
该方法用于查找一个对象的原型对象
class Goods {
constructor(name, price) {
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
}
}
const apple = new Goods('apple', 100);
console.log(Object.getPrototypeOf(apple) === Goods.prototype); // true
- instanceof 操作符
判断一个对象是否是一个构造函数的实例。如果是返回 true,否则就返回 false
class Goods {
constructor(name, price) {
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
}
}
const apple = new Goods('apple', 100);
console.log(apple instanceof Goods); // true
- isPrototypeOf
主要用于检测一个对象是否是一个另一个对象的原型对象,如果是返回 true,否则就返回 false
class Goods {
constructor(name, price) {
this.name = name;
this.price = price;
this.print = function () {
console.log(name, price);
};
}
}
const apple = new Goods('apple', 100);
console.log(Goods.prototype.isPrototypeOf(apple)); // true
- hasOwnProperty
判断一个属性是定义在对象本身上面还是从原型对象上面继承而来的。
如果是本身的,则返回 true,如果是继承而来的,则返回 false
const person = {
arm: 2,
legs: 2,
walk() {
console.log("walking");
},
};
const john = Object.create(person, {
name: {
value: "John",
enumerable: true,
},
age: {
value: 18,
enumerable: true,
},
});
console.log(john.hasOwnProperty("name")); // true
console.log(john.hasOwnProperty("arms")); // false
补充
对于基本数据类型的数据,则原型对象为其包装对象
const str = '1';
console.log(str.__proto__ === String.prototype); // true
不过可能会让人疑惑,之前说过在基本数据类型调用原型包装类型对象上的方法时,会临时创建一个临时包装对象进行调用方法后赋值给原数据,那么这里并没有调用方法,为什么还是会有对象(引用数据类型)才有的原型
const str = '1';
console.log(typeof str); // string
可以看到str也不是个对象
在JavaScript中,原始值(如字符串、数字、布尔)本身不是对象,但它们可以通过**自动装箱(Auto-boxing)**机制临时转换为对应的包装对象(如String、Number、Boolean),从而访问原型链上的属性和方法。以下是详细解释
1. 自动装箱机制
当对原始值(如’1’)调用方法(如split)或访问属性(如__proto__)时,JavaScript引擎会隐式创建一个对应的包装对象(new String(‘1’))。
这个临时对象继承自其构造函数的原型(如String.prototype),因此可以访问原型链上的属性和方法。
操作完成后,临时对象会被销毁。
2. __proto__的存在原因
原始值本身没有__proto__属性,但通过自动装箱,访问a.__proto__时:
引擎将原始值’1’转换为临时对象new String(‘1’)。
访问该临时对象的__proto__属性(即String.prototype)。
最终返回的结果是String.prototype,而原始值本身仍然保持为基本类型。
因此,表面上看似原始值拥有__proto__,实际是引擎隐式处理的结果。
3. 关键区别
原始值:'1’是基本类型,不存储任何属性或方法。
包装对象:new String(‘1’)是对象,包含__proto__指向String.prototype,从而继承方法。
每次访问原始值的属性时,都会新建一个临时包装对象,操作后立即销毁。
4. 示例验证
复制
const a = '1';
console.log(a.__proto__ === String.prototype); // true(引擎隐式创建String对象)
console.log(Object.getPrototypeOf(a) === String.prototype); // true(同样触发自动装箱)
console.log(typeof a); // "string"(a仍是原始值,未被修改为对象)
5. 总结
原型链的访问是通过临时包装对象实现的,原始值本身不存储__proto__。
这种机制使得原始值可以“借用”对象的行为(如调用方法),同时保持轻量级特性。
因此,即使未调用方法,访问a.__proto__也会触发自动装箱,导致看似原始值拥有原型链,实则是引擎的隐式转换机制。