什么是原型对象?
- 只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象;
- 所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针;
- 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
什么是原型链?
综上所述,假如我们让原型对象等于另一个类型的实例,则此时的原型对象将包含一个指向另一个原型的实例,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就是原型链。
[注] 所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因。
function Person() {};
Person.prototype.name = 'sonia';
Person.prototype.age = 22;
var person1 = new Person();
person1._proto_.name = 'Jeremy'; //修改原型的name值
person1.age = 21; //对实例的age进行赋值,原型的age值不变
console.log(person1.hasOwnProperty('name')); //false(来自原型)
console.log(person1.hasOwnProperty('age')); //true(来自实例)
console.log(Person.prototype.isPrototypeOf(person1)); //true(检测实例是否来自原型)
如何实现继承?(实例–>对象–>Object)
- 通过原型链实现。不能使用对象字面量创建原型方法,因为这样会重写原型链。
缺点:最主要的问题是包含引用类型值的原型;其次,在创建子类型的实例时,不能向超类型的构造函数中传递参数。
- 借用构造函数。在子类型构造函数的内部调用超类型构造函数。使用apply()和call()方法,可以向超类型的构造函数中传递参数。apply()和call()的作用一样,只是专递的参数形式不同,apply()的参数是以数组的形式传递,call()中是展开的形式。
缺点:函数复用无从谈起,而且在超类型的原型中定义的方法,对子类型而言是不可见的。
- 组合继承(原型链+借用构造函数)。避免了上两种方法的缺陷,融合了他们的优点,成为javascript最常用的继承模式,而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
//私有的属性或方法可写在构造函数上
function Person(name, age) {
this.name = name;
this.age = age;
};
//把公共的属性或方法继承在原型上,可以提高性能,避免多次实例被多次创建,占用内存
Person.prototype.print = function() {
return this.name + '-' + this.age;
};
var person1 = new Person('Jeremy', 21);
var person2 = new Person('JeremyPC', 22);
person1.print(); //Jeremy-21
person2.print(); //JeremyPC-22
function Fa() {};
Fa.prototype.name = 'fa';
function Fb() {};
Fb.prototype = new Fa();
Fb.prototype.name = 'fb';
function Fc() {};
Fc.prototype = new Fb();
Fc.prototype.name = 'fc';
var aa = new Fc();
aa.__proto__.name = 'cc';
console.log(aa);