原型与原型链:JavaScript中的继承机制
前言
在JavaScript中,原型和原型链是理解对象继承和行为的关键概念。它们允许JavaScript对象通过一种非传统的、基于原型的继承机制来共享方法和属性,这与其他面向对象编程语言(如Java或C++)的类继承机制有所不同。接下来就让我来向你们介绍一下原型和原型链的概念吧,本文不仅有干巴的概念,作者也通过实例代码来解释各种概念帮助理解,请放心食用 *✧⁺˚⁺ପ(๑・ω・)੭ु⁾⁾
原型
在JavaScript中,原型(Prototype)是一种机制,允许对象共享属性和方法。当使用new
关键字调用一个构造函数来创建新对象时,这个新对象会隐式地继承构造函数原型上的属性和方法。这意味着,即使没有在对象实例上直接定义某个属性或方法,只要它存在于构造函数的原型上,对象实例也能访问到这个属性或方法。这种机制是JavaScript实现继承的基础。
实例对象的属性与原型上属性的关系
下面我将通过代码来讲解这些关系来帮助你更快理解这些干巴的知识点 get!٩( 'ω' )و
构造函数 new 出来的对象会 隐式继承到 构造函数原型上的属性和方法
第9行代码我们 new 一个实例对象 p1,访问p1,你只能看到p1从 Person构造函数中继承到的自有属性name,而不能看见原型上的lastname属性和say方法。但是只有当你继续访问p1.lastname,你才可以看到,所以我们称这种继承方式为隐式继承。
Person.prototype.lastname = '李四'
Person.prototype.say = function () {
console.log('hello');
}
function Person () {
this.name = '张三'
}
let p1 = new Person()
console.log(p1); // Person { name: '张三' }
console.log(p1.lastname); // 李四
p1.say() // hello
实例对象可以修改自有属性,但是无法修改隐式继承到的属性(原型上的属性)
创建实例对象p1,当我们想通过实例对象修改原型上的属性lastname,在第7行设置p1.lastname = '王五';
,我们访问这个lastname属性时,确实返回的是王五,但是我们继续访问p1,你会得到Person { name: '张三', lastname: '王五' }
,根据上一个例子我们会知道p1是隐式继承到构造函数Person上的属性和方法的,如果你单单打印p1是得不出lastname属性的,这个属性是原型上的属性。但是在这个例子中你访问到了lastname,这是为什么?
因为你访问到的是p1自己创建的同名属性,而不是原型上的属性。p1不能直接修改Person.prototype.lastname,但它可以创建自己的同名属性来遮蔽原型上的属性,现在p1.lastname是'王五',遮蔽了原型上的'李四'。但如果我们删除p1的lastname属性,原型上的lastname又会重新可见
Person.prototype.lastname = '李四';
function Person () {
this.name = '张三'
}
let p1 = new Person();
p1.lastname = '王五';
console.log(p1.lastname); // 王五
console.log(p1); // Person { name: '张三', lastname: '王五' }
// 删除p1的lastname属性
delete p1.lastname;
console.log(p1.lastname); // 李四
实例对象无法给原型上新增属性或方法,它们只能在自己的作用域内添加自有属性或方法
参照上一个代码示例,你会发现你在实例对象上创建的属性lastname只能是实例对象的自有属性,根本不会影响到原型。
实例对象无法删除原型上的属性,但可以删除自己作用域内的自有属性
依旧是参照上一个代码示例,delete p1.lastname;
也仅仅只是删除了实例对象的自有属性。如果你想删除删除原型上的属性,执行delete Person.prototype.lastname
,这将影响所有通过该构造函数创建的实例对象,但这不是由实例对象本身执行的。
显示原型(Prototype)与隐式原型(proto)
-
显示原型(Prototype) :在JavaScript中,每个函数都有一个
prototype
属性,这个属性是一个对象,被称为显示原型对象,它包含了所有实例对象共享的属性和方法。 -
隐式原型(proto) :每个对象(除了
null
和通过Object.create(null)
创建的对象,原因后面会叙述)都有一个内部属性[[Prototype]]
,这个内部属性指向创建该对象的构造函数的原型对象。
显示原型与隐式原型的关系:
-
对象的隐式原型(
__proto__
)指向其构造函数的显示原型(prototype
) -
构造函数的显示原型(
prototype
)是一个对象,它包含所有实例共享的属性和方法。
function Person() {
}
let p = new Person();
// p的隐式原型指向Person的显示原型
// 对象的隐式原型 === 创建它的构造函数的显示原型
console.log(p.__proto__ === Person.prototype); // true
// Person的显示原型是一个对象,它目前不包含任何自定义属性或方法
console.log(Person.prototype); // Person {}
// 如果我们在Person的prototype上添加方法,那么所有Person的实例都将能访问这个方法
Person.prototype.sayo = function() {
console.log("Hello!");
};
p.sayHello(); // 输出: Hello!
原型链
JavaScript引擎在查找对象的属性或方法时,会先在当前对象上查找,如果找不到,则沿着原型链(即对象的__proto__
所指向的对象)继续查找,直到找到该属性或方法或到达Object.prototype
为止。如果Object.prototype
上也没有找到,则返回undefined
。这种查找关系,称之为原型链。
在下面这个例子中,son 对象的原型链是:son --> Son.prototype (Father的实例) --> Father.prototype (GrandFather的实例) --> GrandFather.prototype --> Object.prototype --> null。
原型链的终点是 Object.prototype
GrandFather.prototype.say = function() {
console.log('hhh');
}
function GrandFather() {
this.age = 60
this.like = 'drink'
}
Father.prototype = new GrandFather()
function Father() {
this.age = 40
this.fortune = {
card: 'visa'
}
}
Son.prototype = new Father()
function Son() {
this.age = 18
}
let son = new Son()
console.log(son.age); // 18,Son有显示属性age
console.log(son.fortune); // { card: 'visa' }
console.log(son.like); // drink
son.say() // hhh
不是所有对象都有原型
在JavaScript中,无论是通过字面量语法(如{}
)还是通过构造函数(如new Object()
)创建对象,这个新对象都会自动获得一个原型链,其隐式原型(__proto__
)指向其构造函数的prototype
属性所引用的对象。然而,使用Object.create(null)
创建的对象是一个例外,它没有原型。
Object.create()
:创建一个新对象,并指定这个新对象的原型(即其内部[[Prototype]]
属性)为参数中指定的对象。
然而将null
作为Object.create()
方法的参数时,它会创建一个没有原型的对象。这意味着这个新对象不会继承任何对象默认的属性和方法
let a = {
name: 'Tom'
}
let obj = Object.create(a) // 创建一个新对象,让新对象隐式继承 a 对象的属性
console.log(obj.__proto__); // { name: 'Tom' }
let obj = new Object(null) // 没有原型
console.log(obj.__proto__); // [Object: null prototype] {}
总结
希望这篇文章能够帮助你更好地理解原型与原型链的概念,感谢观看!`・ω・´)ゞ敬礼っ
转载自:https://juejin.cn/post/7398376017164255282