原型prototype
每个函数中都有一个叫prototype的属性,prototype指向函数的原型对象,每个原型对象都有一个constructor属性,包含原型对象所在函数的指针。
原型链
每个构造函数都包含一个原型对象,(每个原型对象都包含一个指向构造函数的指针),每个实例都包含指向原型对象的指针,若让原型对象等于另一个类型的实例,则原型对象将包含一个指向另一个原型的指针,若另一个原型又是另一个类型的实例,则原型和实例的这种关系依然存在,就是原型链。
__proto__
__proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,proto是对象的内置属性),是JS内部使用寻找原型链的属性。
Object.prototype.__proto__ === null;//true
Object.constructor === Function;
Object.constructor === Function.constructor;
Object.constructor === Function.prototype.constructor
用chrome和FF都可以访问到对象的proto属性,IE不可以。
new 的过程
var Person = function(){};
var p = new Person();
var p={};
也就是说,初始化一个对象pp.__proto__ = Person.prototype;
Person.call(p);
也就是说构造p,也可以称之为初始化p- 若上一步中Person有Object类型的返回值o,则返回o,否则返回p。
js只有函数对象具备类的概念,要创建对象,必须使用函数对象,函数对象内部的
[[constructor]]
用于构造对象,[[call]]
用于调用对象。
var obj = new Object()
使用内置的Object函数实例化对象。var obj = {}
或者var obj = []
使用js引擎触发Object和Array的构造过程。function fn() {};var obj = new fn{}
使用用户自定义类型实例化对象。
new创建的对象和函数的prototype对象之间存在继承关系
function A(){
this.a = 'a in A func';
};
A.prototype.b = 'b in A prototype';
var i = new A();
i.b;//'b in A prototype';
i instanceof A;//true;
A.prototype = {};
i.b;//'b in A prototype';
i instanceof A;//false;
i.__proto__;
// {b: "b in A prototype", constructor: ƒ}
从上面代码看出,将A的prototype置为{}空对象后,i instanceof A;
返回false,可见继承关系是通过prototype实现的。
由于new
创建对象时将构造函数的prototype
属性赋值给实例的__proto__
属性,因此__proto__
属性和prototype
指向同一引用,如果将prototype
同对象字面量方式赋值为其他对象的话,则切断了__proto__
和prototype
之间的联系,因为prototype
和其最初的引用之间没有关系了,这里考虑引用类型的存储即可明白,__proto__
依旧指向之前prototype
的引用,所以__proto__
属性上存在的属性不会消失。
将A
的prototype
置为{}
空对象后对A
的实例i来说,i的隐式__proto__
属性并未改变,即i被实例化后__proto__
不受prototype的影响,相当于实例化后,通过父类prototype
继承来的属性和方法都会保存在自身的__proto__
属性上,与父类没有关系,因此也不能通过实例更改父类原型上的方法,这里的前提都是A.prototype = {}
。如下:
i.b = 123;//查找属性时,实例属性优先
//123
i.__proto__.b
//"b in A prototype"
Object,Function,Number,String
,等的构造器constructor
都是Function
,实例对象的构造器会从原型链上继承自父类。
Number.constructor === Function;//true
Number.__proto__ === Function.prototype;//true
Function
是最顶层的构造器,他具有自举性,
typeof Function.prototype;//"function"
Function.constructor === Function;//true
Function.__proto__ === Function.prototype;//true
所有函数的__proto__
属性都指向Function.prototype,Function
的proto也指向Function.prototype,
function A(){}
A.__proto__ === Function.prototype
//true
Function.__proto__ === Function.prototype;//true
继承
1. 原型链继承:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){}
SubType.prototype = new SuperType();
原型链继承的缺点:若原型中包含引用类型的变量,则原型链继承会导致此引用变量被共享,因此可能会无意中被修改
2.借用构造函数继承
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
SuperType.call(this)
}
原型链上的属性是共享的,而构造函数中的属性是每个实例自己的,因此,借用父类的构造函数后即可解决原型链继承的缺陷。
借用构造函数继承缺点:方法都在构造函数中定义,复用无从谈起。
3.组合继承
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var son = new SubType();
组合继承最常用,原型链继承共享属性和方法,借用构造函数继承实例属性。
以上3种继承方式都必须使用new关键字定义子对象
3.原型式继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var obj = {friends:[1,2,3]}
var another = object(obj);
another.friends.push(4);
console.log(obj.friends);//1,2,3,4
若传入的参数
o
中包含引用类型,则返回的对象可修改该引用类型,ES5
规范了原型式继承,Object.create(baseobject,{name:'\**'}),
第二个参数可修改baseobject
的属性,也可以定义额外属性。
4. 寄生式继承
function createAnother(original) {
var clone = object(original);
clone.sayHi = function () {}
return clone;
}
var another = createAnother(original);
another.sayHi();
寄生式继承通过将
original
赋值给空构造函数F
的原型后,返回F
的实例, 再在F的实例上添加方法,最后返回F的实例。
5.寄生组合式继承
由于组合继承中会调用两个SuperType
构造函数
SubType.prototype = new SuperType();//SubType原型中保留name和colors
new SubType('zy','13');//实例中保留name和colors和age,因此有两组name和colors。
寄生组合式继承(最佳):
function inheritePrototy(subType,superType) {
var p = object(superType.prototype);
p.constructor = subType;
subType.prototype = p;
}
举例:
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritePrototy(SuType,SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
};
- 还是利用借用构造函数继承父类构造函数中的属性:
SuperType.call(this);
这一步将SuperType
构造函数上的实例属性继承下来。- 父类原型上的方法不在通过
subType.prototype = new SuperType();
实现,而是直接将subType.prototype = (new SuperType()).__proto__;
,避免了通过new
创建对象时将SuperType
构造函数上的实例属性传递到subType.prototype
上去。
题目
function Mike(){this.sayMike=function(){};}
function Tom(){};
Tom.prototype=new Mike();
Tom.prototype.sayTom=function(){};
var tom = new Tom();
则:
tom.__proto__=== Tom.prototype;
以上因为tom
是TOM
的实例,因此tom.__proto__
就是Tom.prototype
;
tom.__proto__.__proto__ === Mike.prototype;
以上因为Tom.prototype
是Mike
的实例,因此:
Tom.prototype.__proto__ === Mike.prototype;
tom.__proto__.__proto__ === Mike.prototype;
tom.__proto__.__proto__.__proto__ === Object.prototype;
以上因为Mike.prototype
并未赋任何值,所以他是个空对象,因此:
Mike.prototype.__proto__ === Object.prototype;
tom.__proto__.__proto__.__proto__ === Object.prototype;
tom.__proto__.__proto__.__proto__.__proto__ === null;
以上因为Object.prototype.__proto__ === null;