大家好,我是睡个好jo,今天要给大家分享的是js中的原型。
在JavaScript中,原型(Prototype)机制是实现对象继承和属性共享的核心概念。这篇文章将会带着大家去了解并掌握原型的相关知识
原型(Prototype)
每个函数在JavaScript中都自带一个特殊的属性------prototype
。这个属性是一个对象,用于定义通过该构造函数创建的所有实例所共享的属性和方法。简而言之,构造函数的prototype
对象就是其所有实例对象的公共祖先。
当然,让我们通过一个具体的例子来说明prototype
的工作原理。假设我们要创建一个表示动物的构造函数,特别是创建一个猫的类,我们可以这样做:
javascript
function Cat(name, color) {
this.name = name;
this.color = color;
}
// 给Cat构造函数的prototype添加一个方法
Cat.prototype.meow = function() {
console.log(this.name + ", the " + this.color + " cat, says 'Meow!'");
}
在这个例子中,Cat
是一个构造函数,用于生成猫的实例。我们通过Cat.prototype
给所有的猫实例添加了一个共享的方法meow()
。这意味着,所有通过new Cat()
创建的对象都将能够访问到meow
方法,而不需要在每个实例中单独定义。
接下来,我们创建两个猫的实例并调用它们的meow
方法:
javascript
let kitty1 = new Cat("Whiskers", "gray");
let kitty2 = new Cat("Snowball", "white");
kitty1.meow(); // 输出: Whiskers, the gray cat, says 'Meow!'
kitty2.meow(); // 输出: Snowball, the white cat, says 'Meow!'
尽管meow
方法是在Cat.prototype
上定义的,但kitty1
和kitty2
都能够访问到它,因为它们的原型链(__proto__
)指向了Cat.prototype
。这样,meow
方法就成为了所有由Cat
构造函数创建的对象所共享的功能,节省了内存并体现了面向对象编程中"继承"的概念。
以下内容需要先了解new,请先看你news什么new,stanley(是单例)
实例与原型的关系
当通过构造函数(使用new
关键字)创建一个对象时,该对象会自动获得一个内部属性[[Prototype]](可通过__proto__
访问,尽管非标准)。这个内部属性指向构造函数的prototype
对象,使得实例能访问原型上的属性和方法。这些属性虽然对实例可见,但修改它们实际上会影响所有通过该构造函数创建的实例,因为它们是共享的。
但是实例不能改变原型对象上的属性和方法:
假设我们有一个构造函数Person,并在其原型上定义了一个属性gender。
Javascript
function Person(name) {
this.name = name;
}
Person.prototype.gender = "unknown"; // 在原型上定义gender属性
放在浏览器上访问: 现在,我们创建两个Person的实例person1和person2。
Javascript
let person1 = new Person("Alice");
let person2 = new Person("Bob");
访问原型属性: 当我们访问person1.gender或person2.gender时,由于实例自身没有定义gender属性,JavaScript会沿着原型链找到Person.prototype.gender,因此两个实例都会显示"unknown"。
修改实例属性: 如果我们直接修改person1的gender属性,这实际上是在person1实例上新增了一个名为gender的属性,覆盖了从原型链上继承来的同名属性,但不影响person2或Person.prototype。
Javascript
person1.gender = "female";
console.log(person1.gender); // 输出:"female"
console.log(person2.gender); // 输出:"unknown",未受影响
console.log(Person.prototype.gender); // 输出:"unknown",原型属性未变
总结
- 直接在实例上修改一个从原型链上继承来的属性或方法,实际上是为该实例添加了一个新的局部属性或方法,而不是修改了原型对象上的那个。
- 这种做法不会影响到其他通过同一原型链继承的对象,也不会改变原型对象本身的属性或方法。
- 若要修改原型上的属性或方法,需直接在原型对象上操作,如Person.prototype.gender = "modified";这样会改变所有通过Person构造函数创建的对象的行为。
原型链
原型链是JavaScript实现对象属性查找的机制。当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript引擎会继续在其[[Prototype]](即隐式原型)所指向的对象中查找。这一过程会持续进行,沿着原型链逐级向上查找,直到找到该属性或到达原型链的末端(null
)。
分析原型链
我们来根据以下图片来分析:
分析结果
- f1 和 f2 的关系 :
f1
和f2
都是Foo
函数的实例,它们的__proto__
属性指向Foo.prototype
。这意味着这两个函数实例可以从Foo.prototype
继承属性和方法。 - o1 和 o2 的关系 :
o1
和o2
都是Object
函数的实例,它们的__proto__
属性指向Object.prototype
。同样,这两个对象实例也可以从Object.prototype
继承属性和方法。 Foo
和Object
的关系 :Foo
和Object
都是Function
函数的实例,它们的__proto__
属性分别指向Function.prototype
。这意味着这两个函数实例可以从Function.prototype
继承属性和方法。Function
的关系 :Function
函数的__proto__
属性指向Function.prototype
,这是由于Function
是由内置的Function
构造函数创建的,所以遵循同样的规则。Function.prototype
的关系 :Function.prototype
的__proto__
属性指向null
,表明这是原型链的终点。Object.prototype
的关系 :Object.prototype
的__proto__
属性也指向null
,这也是原型链的终点。Foo.prototype
的关系 :Foo.prototype
的__proto__
属性指向Object.prototype
,这是因为Foo.prototype
是一个普通的对象,也是由Object
函数创建的,所以遵循Object
的原型链。Object
和Function
的关系 :Object
和Function
的__proto__
属性都指向Function.prototype
,因为它们都是函数,由Function
构造函数创建。Foo
的关系 :Foo
的__proto__
属性指向Function.prototype
,因为它也是一个函数,由Function
构造函数创建。
隐式原型与显式原型
- 隐式原型(
__proto__
) :每个对象(除null
外)都有一个内部属性,即隐式原型,它指向创建该对象的构造函数的prototype
对象。 - 显式原型(
prototype
):主要存在于函数对象上,用于定义实例共享的属性和方法。
特殊案例:无原型对象
并非所有JavaScript对象都有原型。使用Object.create(null)
可以创建一个没有原型的对象,即该对象的__proto__
为null
,这样的对象不继承任何属性或方法,是真正的"纯净"对象。
示例代码解析
javascript
// 定义Car构造函数,为其原型添加属性
Car.prototype.name = 'su7'; // 品牌
Car.prototype.lang = 5000; // 长度
Car.prototype.height = 1400; // 高度
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
// 创建Car实例
let car1 = new Car('pink', 'ii');
let car2 = new Car('orange', 'jj');
let car3 = new Car();
// 输出验证
console.log(car1.name); // 输出: su7
console.log(car1.lang); // 输出: 5000
console.log(Car); // 输出: [Function: Car]
console.log(Car.prototype === Car.__proto__); // 输出: false
在上述示例中,我们定义了一个Car
构造函数,并通过其prototype
属性为所有Car
实例添加了共享属性。通过new Car()
创建的每个实例都能访问这些共享属性。最后一行输出false
是因为Car.prototype
是Car
函数的原型对象,而Car.__proto__
实际上是Function.prototype
,它们指向不同的对象。
通过这些概念和示例,我们可以更深刻地理解JavaScript的原型机制,它是实现面向对象编程、实现继承和属性复用的重要基石。