原型
基本属性说明
prototype
js中,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性,指向原型对象。prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承,因此可以将公用的属性和方法,直接定义到prototype对象属性上。
tips: 只有函数才有prototype属性
function Person(){
}
console.log(Person.prototype)
//{
// constructor:f Person(),
// __proto__:Object
//}
prototype的用法prototype最主要的用法是将属性和方法暴露成公用的。
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(this.name + "say hello");
}
}
let p1 = new Person("jack",23);
let p2 = new Person("rose",23);
console.log(p1.name); //jack
console.log(p2.name); //rose
console.log(p1.sayHello === p2.sayHello); //false
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(this.name + "say hello");
}
}
Person.prototype.sayHello = function(){
console.log(this.name + "say hello");
}
let p1 = new Person("jack",23);
let p2 = new Person("rose",23);
console.log(p1.name); //jack
console.log(p2.name); //rose
console.log(p1.sayHello === p2.sayHello); //true
对比上述两种代码。第一种代码,每一个实例,都是一个与构造函数同名但独立的Object,因此两个不同的实例去访问方法的时候,并不指向同一个方法,指向的是各自Object中的方法。但是,当方法声明在构造函数的原型中时,实例对象去访问方法的时候,指向的是同一个。
constructor
如上所述,构造函数有一个prototype属性,指向原型对象。自定义构造函数的时候,原型对象默认会获得constructor属性,指向这个构造函数。二者循环引用。构造函数的实例的constructor属性,指向构造函数
function Person(){
}
let p = Person()
console.log(Person.prototype.constructor == Person) //true
console.log(Person.constructor); //function Function(){}
//每个函数其实是通过new Function()构造的
console.log(Object.constructor); // function Function() {}
//Object也是一个函数,它是Function()构造的
console.log(p.constructor == Person) //true
__proto__
每个对象(null除外)都会有__proto__属性,指向该对象的原型对象
function Person(){
}
console.log(Person.prototype.__proto__ == Object.prototype) //true
原型对象、构造函数及实例之间的关系
如上所述,构造函数的prototype属性,指向原型对象。原型对象的constructor属性指向构造函数。形成一个小闭环。通过new()方法可以创建构造函数的实例,它是一个与构造函数同名的object,这个object是独立的,它只包含了一个__proto__指针,指向构造函数的prototype原型对象。关系图如下:
tips: 实例没有
prototype,强行访问则会输出undefined
function Person(){
}
let p = new Person()
console.log(p.prototype) //undefined
console.log(p.__proto__ == Person.prototype) //true
console.log(p.__proto__.constructor == Person) //true
原型链
原型链是ECMAScript的主要继承方式。基本思想就是通过原型继承多个引用类型的属性和方法
原型链的基本构想: 如上所述的原型、构造函数及实例之间的关系。假设原型对象是另一个类型的实例,那么意味着这个原型对象本身有一个内部指针指向另一个原型,对应的,另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
实现原型链涉及的代码模式如下:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true
上述代码中,定义了2个类型:SuperType和SubType。两个类型分别定义了一个属性和一个方法。区别在于,SubType将SuperType的实例赋给了自己的原型SubType.prototype从而实现了对SuperType的继承。这个赋值操作重写了SubType最初的原型,将其替换为SuperType的实例。意味着SuperType实例可以访问的所有属性和方法也会存在于SubType.prototype中。
在调用
instance.getSuperValue()会按照instance,SubType.prototype和SuperType.prototype这三个步骤才找到这个方法。
tips:
-
- 对属性和方法的搜索会一直持续到原型链的末端
-
SubType.prototype的constructor属性被重写为指向SuperType,所以instance.constructor也指向SuperType
默认原型
默认情况下,所有引用类型都继承自Object。任何函数的默认原型都是一个Object的实例,意味着这个实例有一个内部指针指向Object.prototype。所以Object.prototype上的所有包括toString()、valueOf()在内的默认方法都会被这些实例所继承。
原型与继承关系
原型与实例的关系可以通过以下两种方式来确定:
-
instanceof操作符 如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
-
isPrototypeOf()方法
原型链中的每个原型都可以调用这个方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true
关于方法
子类有增加父类没有的新方法或覆盖父类原有的方法时,必须在原型赋值后在添加到原型上。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
第二个getSuperValue()会覆盖原有的getSuperValue()方法。所以在SubType的实例上调用该方法时,调用的是第二个getSuperValue()方法。但是SuperType的实例仍会调用,第一个getSuperValue()方法
// 继承SuperType
SubType.prototype = new SuperType();
// 新方法
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
// 覆盖已有的方法
SubType.prototype.getSuperValue = function () {
return false;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // false
tips: 使用对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。覆盖后的原型是Object的实例,而不再是SuperType的实例。SubType和SuperType的关系也就断了
// 继承SuperType
SubType.prototype = new SuperType();
// 通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = {
getSubValue() {
return this.subproperty;
},
someOtherMethod() {
return false;
}
};
let instance = new SubType();
console.log(instance.getSuperValue()); // 出错!
原型链的问题
-
- 实例属性变成了原型属性
在使用原型实现继承时,原型实际上变成了另一个类型的实例。此时,原先的实例属性也变成了原型属性。
- 实例属性变成了原型属性
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
// 继承SuperType
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black"
-
- 子类型在实例化的时候不能给父类型的构造函数传参