原型六问
我们先用一句话综合描述一下原型,再一层一层剥开他的心。
所有的函数都有一个prototype属性,存放着一个对象,当这个函数作为构造函数时,prototype中存放的对象就成为了构造函数构造出来的对象的原型。
我们用一个小例子来理解一下这段话,有如下一段代码:
// 声明了一个名为Fn的函数
function Fn() { }
// 用Fn()构造了一个对象,名为a
let a = new Fn();
// 将Fn()的prototype属性的值赋给b
let b = Fn.prototype;
接着我们通过一连串的问题来理解JavaScript中原型继承的实现
- 不过首先我们先介绍一个需要用到的工具。
[对象名] instanceof [构造函数名]
:用来判断一个对象是否为某个构造函数的示例。换句话说,判断是否是使用某个构造函数构造了某个对象。typeof [变量名]
:用来查询一个变量的类型
【问题1】是不是Fn() 构造了a?
a instanceof Fn // true
【问题2】既然Fn()构造了a,Fn.prototype(一个对象)就是a的原型咯?
a.__proto__ === Fn.prototype // true
【问题3】因为b = Fn.prototype,也就是说,b是a的原型咯?
a.__proto__ === b // true
【问题4】b是不是一个对象?
b instanceof Object // true
【问题5】如果b是对象,那么Object.prototype就是b的原型咯?
b.__ptoto__ === Object.prototype // true
【问题6】a, b和Object.prototype组成了一条原型链对吗?
- 即原型链是否是:
a
—->b
—->Object.prototype
【答】我们给 a
、b
和 Object.prototype
分别加上一个名为 identifier
的属性,然后使用console.dir(a)
打印一下 a
并全部展开看看:
a.identifier = 'a在这儿';
d.identifier = 'b在这儿';
Object.prototype.identifier = 'Object.prototype在这儿';
console.dir(a);
关于Constructor
- 在刚刚打印出来的b对象中有一个属性叫做
constructor
,我们点开它会发现一个非常有意思的现象。
- Fn() 和 Fn.prototype呈现出一种“你中有我我中有你”的状态。
- 事实也正是如此
- 构造函数的prototype属性中会存放它所构造对象的原型;
- 这个原型对象中的constructor属性会存放这个构造函数;
- 即:
Fn.prototype.constructor === Fn // true
new 和 Object.create() 的区别
- 在使用new时,其实包含了以下几步绑定: ```javascript // 执行let a = new Fn() 时发生的绑定:
// 创建一个简单的空对象 var obj = new Object(); // 将这个空对象的proto链接到Fn.prototype上 obj.proto = Fn.prototype; // 把构造函数的this指向obj,并执行构造函数把结果赋给result var result = Fn.call(obj); if (typeof(result) === ‘object’) { a = result; // 构造函数Fn的执行结果是引用类型,就把这个引用类型的对象返回给a } else { a = obj; // 构造函数Fn的执行结果是值类型,就返回obj这个空对象给a }
- 关于new和Object.create()的区别,stack overflow上有一个简洁而不简单的回答:
> [【原文】](https://stackoverflow.com/questions/4166616/understanding-the-difference-between-object-create-and-new-somefunction)
> Very simply said, `new X` is `Object.create(X.prototype)` with additionally running the `constructor` function. (And giving the `constructor` the chance to `return` the actual object that should be the result of the expression instead of `this`.)
> 【译文】
> 简单说来,new X 就是在执行 Object.create(X.prototype) 的同时执行constructor中的函数。(使得constructor中的函数有机会返回一个应该作为表达式结果的真正的对象而不是this)
<a name="kvXKr"></a>
### 基于原型的继承
- 在了解了原型链之后,我们终于可以看一下JavaScript中基于原型的继承了。
<a name="xPNVa"></a>
#### 属性的继承
- JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它**不仅仅在该对象上搜寻**,**还会搜寻该对象的原型**,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
- 举个例子,还是对于刚才的那段代码,我们对它进行一些修改。
```javascript
function Fn() {
this.name = 'Barry';
this.age = 24;
}
let a = new Fn();
Fn.prototype.age = 18;
Fn.prototype.gender = 'male';
// 现在的原型链:
// a ---> Fn.prototype ---> Object.prototype ---> null
// 详细写法:
// {name='Barry', age=24} ---> {age=18, gender='male'} ---> Object.prototype ---> null
console.log(a.name);
// name搜寻过程:
// name是自身属性吗?是的!name属性值为'Barry'
console.log(a.age);
// age搜寻过程:
// age是自身属性吗?是的!age属性值为24
// 原型链上的age属性并不会被访问到
// 即发生了属性屏蔽(Property Shadowing)
console.log(a.gender);
// gender搜寻过程:
// gender是自身属性吗?不是?那么就去原型里找找吧!
// gender是Fn.prototype的属性吗?是的!gender属性值为'male'
console.log(a.singing);
// singing搜寻过程:
// singing是自身属性吗?不是?那么就去原型里找找吧!
// singing是Fn.prototype的属性吗?不是?去上一层原型里继续找!
// singing是Object.ptototype的属性吗?不是?去上一层原型里继续找!
// 糟糕!上一层是null了,哪里都找不到singing
// 返回undefined
函数的继承
- JavaScript中对象中的函数其实也是对象的属性,所以与属性继承规则相同
- 唯一需要特别注意的就是当函数被调用时,this指向的是当前继承的对象,而不是继承的函数所在的对象。