数字营销 · Web开发 · 基础设施

JavaScript对象与继承:__proto__、prototype、constructor含义及比较

通过实际代码理解 JavaScript 中 __proto__ 与 prototype 的区别,以及对象原型链的基本关系。

最近在学习 PHP 的类,对封装、继承和多态有了进一步的认识,于是反过来学习 JavaScript 中面向对象的方法,想把这一部分原来没有理解的内容彻底弄明白。

PHP 和 JavaScript 的封装,我已经在博客中进行过对比,可以说这一部分还是比较容易理解的。

但是到了继承这一块,理解起来就有点难度了,主要集中在 __proto__、prototype、constructor 这三个概念上。

constructor 相对比较容易理解,就是构造函数。那么另外两个属性到底是什么意思呢?

第一个问题:为什么实例化的对象没有 prototype 属性?

最先遇到的问题是下面这个例子:

<script>
function Human(){
this.height =180;

this.say =function(){
alert("我在说话");
}
}

var he = newHuman();
alert(he.prototype);
</script>

执行之后发现 he.prototype 得到的是 undefined。

所以第一个问题就出现了:

为什么实例化出来的对象没有 prototype 属性?

第二个问题则是:

prototype 和 __proto__ 到底有什么区别?

prototype 和 proto 的区别

当时看到过这样一幅图:

Javascript对象与继承
Javascript对象与继承

英文的解释大致是:

prototype is a property of a Function object. It is the prototype of objects constructed by that function.
__proto__ is an internal property of an object, pointing to its prototype.

简单翻译一下:

prototype 是函数对象的一个属性,它是由这个函数创建出来的对象的原型。

而 __proto__ 指向对象自己的原型。

这段话刚开始看起来有点拗口,但是换一个方式理解就比较容易了。

简单来说:

__proto__ 指向的是对象自己的原型,而 prototype 是构造函数上的一个属性。

例如:

function Human(){
this.height =180;

this.say =function(){
alert("我在说话");
}
}
var he=newHuman(); 这里 Human.prototype 是 Human 这个函数对象的 prototype 属性。

而:he.__proto__ 指向的就是 Human.prototype。

也就是说:he.__proto__ ===Human.prototype 结果为 true

所以可以简单理解成:

Human
|
| prototype
v
Human.prototype
^
| __proto__
|
he

JavaScript 是如何查找对象属性的?

理解了 __proto__ 之后,再来看 JavaScript 的属性查找就容易一些了。

当我们访问:

he.age

JavaScript 首先会查找对象 he 自己有没有 age 这个属性。

如果有,就直接返回。

如果没有,就继续通过对象的原型关系向上查找,也就是查找:

he.__proto__

所指向的对象。

如果这个对象中也没有 age,那么继续向它的原型查找。

就这样不断向上查找,直到找到对应的属性,或者到达原型链的末端。

这就是 JavaScript 对象属性查找的基本过程。

那么 prototype 到底有什么作用?

看到这里可能又会产生一个疑问:

既然我们可以直接通过:

he.__proto__

来操作对象的原型,那么 prototype 好像也没有什么作用。

例如:

<script>
function Human(){
this.height =180;

this.say =function(){
alert("我在说话");
}
}

var he=newHuman();

he.__proto__.age =20;

var she=newHuman();

alert(she.age); // 返回20
alert(he.age); // 返回20
</script>

在这个例子中,我们直接通过 he.__proto__.age =20 给 he 的原型增加了一个 age 属性。 之后重新创建一个对象 var she=newHuman();

she.age 同样可以得到 20 这是因为 he.__proto__ 和 Human.prototype 实际上指向的是同一个原型对象。

所以 he.__proto__.age =20 实际上相当于给 Human.prototype 增加了一个 age 属性。

因此,he 和 she 都可以通过原型链访问到这个属性。

为什么还需要 prototype?

那么问题又来了:

既然 __proto__ 可以做到这些事情,为什么还需要 prototype?

这里需要区分两个概念。

__proto__ 是对象与其原型之间的关系,而 prototype 是构造函数上的属性。

对于 function Human(){}

Human.prototype 就是这个构造函数用于创建实例时所关联的原型对象。

而通过 var he=newHuman();

创建出来的实例,其原型就与 Human.prototype 建立了关联。

所以才有 he.__proto__ === Human.prototype

也就是说,可以简单理解为:

实例对象通过 __proto__ 找到自己的原型,而构造函数通过 prototype 提供这个原型对象。

关于 proto

这里还需要说明一下,__proto__ 这个写法在当时的浏览器环境中属于比较特殊的属性。

它实际上对应对象内部的原型关系,后来也可以通过 Object.getPrototypeOf(he)

来获取对象的原型。现代 JavaScript 开发中通常更推荐使用标准 API,而不是直接依赖 __proto__。

不过在当时的学习过程中,我更多是通过 __proto__ 来理解 JavaScript 的原型链。

所以在这个例子中,可以这样理解 he.__proto__ ===Human.prototype

也就是:

子对象的原型,实际上就是父构造函数的 prototype 属性所指向的对象。


到这里,第二个问题:

prototype 和 __proto__ 到底有什么区别?

基本上就解决了。

而第一个问题:

为什么实例化的对象没有 prototype 属性?

这个问题,我会在接下来的文章中继续讲。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。