每一个object
对象都有自己[[prototype]]
属性,它指向自己的原型对象(prototype),该原型对象又有自己的[[prototype]]
属性,指向自己的原型对象,层层向上直到一个对象的原型对象是null
。null
没有原型,作为这个原型链的最后一节。
访问一个object
对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null
。
__proto__
vs prototype
__proto__
每一个object
对象都有一个[[prototype]]
属性,它是一个隐藏属性。它指向对象的原型。在很多浏览器的实现中,把[[prototype]]
实现为__proto__
。
所以,__proto__
指向的原型是什么呢?这就由它的构造方法来决定。
对象的三种构造方法:
var a = {
name: 'lc'
}
这个a
对象,其__proto__
指向Object.prototype
。
实际上,字面量构造法是一个语法糖,本质上也是依靠构造函数创建的。
var a = Object.create();
a.name = 'lc';
function A(name) {
this.name = name;
}
var a = new A();
这个a
对象,其__proto__
指向A.prototype
。
new
内部是这样实现的:
// b = new A()
{
var obj = {};
obj.__proto__ = A.prototype;
A.call(obj);
return obj;
}
Object.create
构造法var b = {
name: 'lc'
}
var a = Object.create(a);
这个a
对象,其__proto__
指向b
。
Object.create
内部是这样实现的:
Object.create = function(b) {
function fn(){};
fn.prototype = b;
return new fn();
}
实际上,a
也是由new
方法创建,只是,a
的构造函数fn
只是存在了一瞬间。在外部,我们看不到a
的构造函数,只能看到它的原型是b
。
__proto__
的作用:可以在访问一个object
对象的属性的时候,先去查找对象本身有没有同名的属性,如果没有,就去对象的原型上去找,原型的原型,层层向上查询,直到找到同名的属性,或者直到找到原型链的终点:null
。我们称它为隐式原型。
我们可以通过Object.getPrototypeOf
获得。
prototype
每一个函数在创建之后都有一个prototype
属性,它指向函数的原型对象。
ps: 通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。
prototype
的作用:用来实现基于原型的继承与属性的共享。我们把它称之为显式原型。
prototype
和__proto__
关系a的构造函数是A,a的隐式原型指向A的prototype。
constructor
所有对象都会从它的原型对象上继承constructor
属性,它会指向对象的构造函数。
function A(name) {
this.name = name;
}
function B(name) {
A.call(this, name);
}
var a = new A('qq')
var b = new B('lc');
缺点: 子类父类没有公用的方法。函数复用无从谈起。
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
function B(name) {
this.name = name;
}
B.prototype = new A();
B.prototype.setName = function(name) {
this.name = name;
}
var b = new B('ls');
b.getName();
b.setName('qq');
缺点: 父类的构造函数的属性变成子类的__proto__
下的属性了。
所以,上述代码
b.age //3
// 实际上,b不应该由age属性
如图,B.prototype
的constructor
属性指向A
而不是B
。
假设,两个子类child1
和child2
,两个子类都继承一个父类parent
。父类的构造函数下的引用类型,就会被两个child
共享。并且共享的是引用值,实例之间的属性会互相干扰。
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
function B(name) {
A.call(this, name);
}
B.prototype = new A();
B.prototype.setName = function(name) {
this.name = name;
}
var b = new B('ls');
上面两种方法的组合。目前比较完美了。子类之间的属性既不会互相影响,又可以实现方法的共享。
缺点: 构造函数被执行了两次,new
的时候执行了一次,call
的时候执行了一次。
实际上解决这个问题,有一个可以优化的地方:
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
function B(name) {
A.call(this, name);
}
// 去掉这行
- B.prototype = new A();
// 添加这行
+ B.prototype = A.prototype;
B.prototype.setName = function(name) {
this.name = name;
}
var b = new B('ls');
这个可以解决构造函数被执行了两次,但是实际上,到组合式继承目前为止,都没有解决子类对象原型constructor
指向的问题。
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
var a = new A('ls');
var b = Object.create(a);
对象的浅复制。
b.__proto__ = a;
这个思路好清奇,没有了构造函数,原型,怪怪的。
子类不能创建子类的方法,只能继承父类的方法。
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
var a = new A('ls');
function clone(a) {
var obj = Object.create(a);
obj.setName = function(name) {
this.name = name;
}
return obj;
}
b = clone(a);
寄生式继承在创建函数内部创建的函数(如上例sayName),不能做到函数复用。
function A(name) {
this.name = name;
this.age = 3;
}
A.prototype.getName = function() {
return this.name;
}
function B(name) {
A.call(this, name);
}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.setName = function(name) {
this.name = name;
}
var b = new B('ls');
组合式继承的更进一步,解决了组合式继承的子类原型constructor
不指向子类构造函数的问题。
目前是最好的继承方式。
《javascript》高级程序设计
https://www.zhihu.com/question/34183746