最近在学习 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 的区别
当时看到过这样一幅图:

英文的解释大致是:
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("我在说话");
}
}而:he.__proto__ 指向的就是 Human.prototype。
也就是说:he.__proto__ ===Human.prototype 结果为 true
所以可以简单理解成:
JavaScript 是如何查找对象属性的?
理解了 __proto__ 之后,再来看 JavaScript 的属性查找就容易一些了。
当我们访问:
JavaScript 首先会查找对象 he 自己有没有 age 这个属性。
如果有,就直接返回。
如果没有,就继续通过对象的原型关系向上查找,也就是查找:
所指向的对象。
如果这个对象中也没有 age,那么继续向它的原型查找。
就这样不断向上查找,直到找到对应的属性,或者到达原型链的末端。
这就是 JavaScript 对象属性查找的基本过程。
那么 prototype 到底有什么作用?
看到这里可能又会产生一个疑问:
既然我们可以直接通过:
来操作对象的原型,那么 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
欢迎分享你的看法,也欢迎补充不同的实践经验。