JavaScript---原型链
1、原型链
”一条链“:是JavaScript中实现对象之间继承属性和方法的一种机制,它允许对象之间共享属性和方法。
对象 ---> 对象._proto_ ---> 对象._proto_ . _proto_ ---> ....... ---> null
let arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__ === null); // true
2、_proto_
属性
proto 实际上是一个已经废弃的属性,现代JavaScript建议使用:
Object.getPrototypeOf(obj)
// 获取原型Object.setPrototypeOf(obj, prototype)
// 设置原型
(1)什么是 __proto__
proto 是 JavaScript 中每个对象的一个内部属性,它指向该对象的原型(即其构造函数的 prototype 属性)。通过这个链接,JavaScript 实现了对象之间的继承关系。
JavaScript 的对象系统基于原型,而非类(尽管 ES6 引入了类的语法糖)。原型链是通过对象的 proto 属性来构建的,它让我们可以通过链式查找机制来获取属性和方法。
由一个对象指向一个对象,即指向它们的原型对象(父对象)
作用: 当访问一个对象时。如果该对象内部不存在这个属性,那么就会去它的_proto_属性所指向的那个对象(父对象)里找,如果父对象也不存在这个属性,则继续往父对象的_proto_属性所指向的那个对象(爷爷对象)里找,如果没找到....,一直向上找,直到找到原型链的顶端null(原型链的终点)
平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠_proto_继承而来
const obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.toString()); // 输出 toString 方法,来自 Object.prototype
console.log(obj.b); // undefined
//在上面的例子中,obj 对象本身没有 toString 方法,但 JavaScript 会沿着 __proto__ 链查找,最终在 Object.prototype 中找到 toString。
(2)查看对象的原型
更推荐的做法是使用 Object.getPrototypeOf()
来获取对象的原型:
console.log(Object.getPrototypeOf(obj)); // 输出 Object.prototype
注意点:由于每次访问对象的属性时,JavaScript 都可能需要顺着原型链向上查找,过长的原型链会导致性能问题。特别是在高频访问的场景中,长原型链的性能开销可能会非常显著。
因此,在设计对象时,尽量保持原型链的简洁与清晰。
(3)__proto__
的替代方案:Object.create()
和 Object.setPrototypeOf()
Object.create()
Object.create()
是创建新对象并设置其原型的标准方法,它比直接使用__proto__
更加安全且可控:const animal = { speak() { console.log('Animal speaks'); } }; const dog = Object.create(animal); dog.bark = function() { console.log('Dog barks'); }; dog.bark(); // Dog barks dog.speak(); // Animal speaks
Object.setPrototypeOf()
我们可以使用
Object.setPrototypeOf()
来修改现有对象的原型,它提供了一个更为规范的方式来改变对象的原型:const cat = { meow() { console.log('Cat meows'); } }; const animal = { speak() { console.log('Animal speaks'); } }; Object.setPrototypeOf(cat, animal); cat.meow(); // Cat meows cat.speak(); // Animal speaks
3、prototype
属性
从一个函数指向一个对象,是函数的原型对象,这个属性是是函数独有的,函数的原型对象即这个函数(所有函数都可以作为构造函数)所创建的实例的原型对象。
作用:包含可以有特定类型的所有实例共享的属性和方法,即让该函数所实例化的对象们都可以找到公用的属性和方法。函数创建的时候,其实会默认同时创建该函数的prototype对象。
let set = new Set();
console.log(Set.prototype);//会打印出Set构造函数的所有属性和方法,所有Set实例可以使用其内置方法
prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。
4、__proto__
和 prototype
的区别
proto:对象的内部属性,指向其构造函数的原型。 对象--->函数
prototype:函数的属性,用于定义由该构造函数创建的实例共享的属性和方法。 函数--->对象
5、constructor属性
从一个对象指向一个函数,即指向该对象的构造函数,每个对象都有构造函数,即每个对象都可以找到其对应的constructor,因为创建对象的前提是需要constructor
function Person(name) {
this.name = name;
}
let person1 = new Person('Alice');
console.log(person1.constructor); // 输出:function Person(name) { ... }
作用:
确定对象的构造函数:
constructor
属性通常用于确定对象是由哪个构造函数创建的。通过这个属性,开发者可以了解对象的类型和来源,进而进行相应的操作。function Animal(type) { this.type = type; } let dog = new Animal('dog'); console.log(dog.constructor === Animal); // 输出:true //通过比较 dog.constructor 是否等于 Animal,可以确认 dog 对象是由 Animal 构造函数创建的。
创建新的实例:
/constructor
属性不仅指向构造函数,还允许开发者利用它创建相同类型的新实例。这个特性在某些需要动态生成对象的场景下非常有用。function Car(make, model) { this.make = make; this.model = model; } let car1 = new Car('Toyota', 'Camry'); let car2 = new car1.constructor('Honda', 'Civic'); console.log(car2); // 输出:Car { make: 'Honda', model: 'Civic' } //car2 通过 car1 的 constructor 属性创建,确保了 car2 的构造函数与 car1 相同。
原型对象对constructor的影响
我们创建一个构造函数时,JavaScript 会自动为该构造函数生成一个原型对象,并且该原型对象的 constructor 属性会指向这个构造函数。
如果修改原型对象的注意点:在开发过程中,开发者可能会自定义对象的原型。当我们手动修改对象的原型时,如果没有显式地设置 constructor 属性,原型对象的 constructor 属性可能会丢失。
function Shape() {} Shape.prototype = { draw: function() { console.log('Drawing shape'); } }; let shape1 = new Shape(); console.log(shape1.constructor); // 输出:[Function: Object] //在这个例子中,由于我们重写了 Shape 的原型,默认的 constructor 属性也被覆盖了,导致 shape1.constructor 指向了 Object 而不是 Shape。
为了避免这种情况,我们可以手动设置 constructor 属性:
Shape.prototype = { constructor: Shape, draw: function() { console.log('Drawing shape'); } }; console.log(shape1.constructor); // 输出:[Function: Shape]
(1)constructor 属性与 instanceof 操作符
constructor 与 instanceof 的关系
instanceof
操作符用于判断某个对象是否是由某个构造函数创建的。它会沿着对象的原型链查找,直到找到与构造函数原型匹配的对象。尽管 constructor 和 instanceof 经常一起使用,但它们是不同的概念。
function Vehicle() {}
let car = new Vehicle();
console.log(car instanceof Vehicle); // 输出:true
console.log(car.constructor === Vehicle); // 输出:true
//在这个例子中,car 是 Vehicle 的实例,因此 instanceof 返回 true,并且 car.constructor 也指向 Vehicle。
constructor 与 instanceof 的区别
尽管 constructor 和 instanceof 都能确定对象的构造函数来源,但它们的作用有所不同。constructor 是对象的属性,而 instanceof 是基于原型链的查找。如果对象的 constructor 属性被修改,instanceof 依然可以通过原型链正确判断对象的类型。
function Bird() {}
let sparrow = new Bird();
sparrow.constructor = Object; // 修改 constructor 属性
console.log(sparrow instanceof Bird); // 输出:true
console.log(sparrow.constructor === Bird); // 输出:false
(2)constructor 属性的实际应用场景
类继承中的使用:在类继承中,子类会继承父类的
constructor
,并可以通过super()
调用父类的构造函数。class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } } let dog1 = new Dog('Buddy', 'Golden Retriever'); console.log(dog1.constructor); // 输出:class Dog extends Animal
动态生成对象:
constructor
属性也可以用于在运行时动态生成与原对象类型相同的实例。在某些场景下,这种方式可以用于复制对象或生成特定类型的对象。function Computer(brand) { this.brand = brand; } let pc1 = new Computer('Dell'); let pc2 = new pc1.constructor('HP'); console.log(pc2); // 输出:Computer { brand: 'HP' }
// 用于类型判断
function isArray(obj) {
return obj.constructor === Array;
}
// 用于对象克隆
function clone(obj) {
return new obj.constructor(obj);
}
(3)constructor和prototype的区别
要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。注意不要混淆。
6、原型链图
7、如何创建没有原型的对象?
在JS中,正常创建的对象都会有原型(_proto_),即使是空对象 { } 也会继承Object.prototype。
而 Object.create(null) 可以创建一个真正“干净”的对象,它没有原型。
没有原型链的对象可以防止恶意代码更改原型的行为
特点和用途:
“干净”,没有任何继承的属性和方法
用作纯粹的数据容器
避免原型污染
Map的替代品(在某些情况下)
由于没有prototype属性,则可使用
Object.hasOwn(obj,prop)
或Reflect.has(obj,prop)
检查对象自身是否具有某个属性
小结
_proto_ 和 constructor 都是由一个对象指出去的,_proto_指向对象,constructor指向函数
prototype 是由一个函数指向一个对象
构造函数有一个prototype属性
对象有一个_proto_属性指向构造函数的prototype原型对象
而它们两者都有一个属性constructor指向构造函数本身