关于原型链,已经被无数次的提起,每次回顾都有新的理解,今天我们再来说说原型链。
我们知道,每一个javascript对象(除了null)在被创建的时候都会与另一个对象关联起来,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
好,问题来了,我们需要原型链来做些什么呐?
原型的作用
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function() {
console.log(age + "岁的" + name + "在吃饭。");
}
}
let p1 = new Person("小夏", 18);
let p2 = new Person("小夏", 18);
console.log(p1.eat === p2.eat); // false
呐,其实结果我们也预想到了,每次创建对象都会重新开辟出新的堆,所以p1和p2指向的地址是不一样的,以前的讨论基本到此为止,那么今天问一下,有没有什么办法,可以让他们指向同一个地址,节省内存呢?我们需要开辟出一块公共的区域,这个概念,就是我们所说的原型对象。
function Person(name) {
this.name = name;
}
// 通过构造函数的 Person 的 prototype 属性找到 Person 的原型对象
Person.prototype.eat = function() {
console.log("吃饭");
}
let p1 = new Person("小夏", 18);
let p2 = new Person("小夏", 18);
console.log(p1.eat === p2.eat); // true
上述就是我们通过原型实现对eat的公共管理,所有通过Person实例化的对象,都会继承eat属性。
关于显式原型prototype和隐式原型_proto_的概念,这边不再过多赘述,如果不太了解,请移步小夏的另一篇原型和原型链了解后再来看本篇。
普通对象和函数对象
对象可以分为两类,普通对象和函数对象。
普通对象
除了函数对象以外所有的对象都是普通对象,包括new 函数对象()产生的实例,普通对象没有prototype,也就没有继承和原型链这一说了。
函数对象
包括两种:
- 由function创造出来的函数:
function f1(){}
var f2=function(){}
var f3=new Function('x','console.log(x)');
//以上都是函数对象
- 系统内置的函数对象:Function、Object、Array、String、Number,还有正则对象RegExp、Date对象等等,它们在js中的构造源码都是
function xxx(){[native code]}
,Function其实不仅让我们用于构造函数,它也充当了函数对象的构造器,比如Object对象的构造源码其实是Function Object(){[native code]}
,甚至它也是自己的构造器.
String._proto_===Function.prototype
//true
Object._proto_===Function.prototype
//true
Function._proto_===Function.prototype
//true
js中原型等式,对象._proto_===构造器.prototype
,由此可以看出他们之间的关系。
问题来了,我们知道所有对象继承自Object对象,但是我们刚刚又说Object对象继承自Function,嗯???是不是有点矛盾了?Object和Function是相互继承的吗?从一定程度上来讲,可以这么理解。
Object和Function
一切对象都最终继承自Object对象,Object对象直接继承自根源对象null
我们可以发现,在原型链分析中,所有对象的原型链的最终都是=>Object.prototype=>null
。一切对象都包含有Object的原型方法,Object的原型方法包括了toString
、valueOf
、hasOwnProperty
等等,在js中不管是普通对象还是函数对象都拥有这些方法。
这条定律放在Function对象上依然成立,只不过Function的原型链稍微复杂了一点,它比较特别的地方就是它的构造器是自己,即直接继承了自己,最终继承于Object,什么玩意?怎么这么怪,对,就是这么怪,Function继承了他自己,最终继承了Object。
Function._proto_===Function.prototype
//true
Function.prototype._proto_===Object.prototype
//true
Object.prototype._proto_===null
//true
练习
var F = function() {
};
Object.prototype.a = function() {
console.log('a()');
};
Function.prototype.b = function() {
console.log('b()');
};
var f = new F();
f.a();
f.b();
F.a();
F.b();
//a()
//报错
//a()
//b()
根据分析可以画出下面的关系图:
可以看到在函数对象F的原型链中包含Function的原型方法和Object的原型方法,但是普通对象没有Function的原型方法,只有Object的原型方法。所以f.b()
会报错。
拓展
new做了什么?
new关键字会进行如下操作:
1.创建一个空的简单js对象 (var a={}
)
2.为新创建的对象添加属性_proto_,将该属性链接到构造函数的原型对象(a._proto_=Foo.prototype
)
3.将新创建的对象作为this的上下文(var b=Foo.call(a)
)
4.如果该函数没有返回对象,则返回this.(return b
)