1. 原型链
1.1 基础介绍
每次利用构造函数生成对象,则部分重复的内容则会占用不必要的内存,因此利用原型prototype,则可以减少这些内存开销,每一个生成的对象都有一个隐式的_proto_
指向原型,如下图所示。
如上图所示的原型代码,则会形成一个原型链,如下图所示。
属性访问:属性访问会沿着原型链进行查找,首先在自身属性上查找,若没有则沿着原型链一层一层往下找。
复制 tom .name; // "Tom"
tom .job; // "teacher"
tom .toString (); // 内置Object对象含有该属性
属性删除:属性删除永远删除的是对象的自身属性,除非指定删除prototype的属性。
复制 del tom .job; // 只删除tom的job属性,对原型无影响
tom .job; //再访问job则会获得原型的job属性值
属性修改:属性修改永远修改的是对象的自身属性,除非指定修改prototype的属性。
复制 tom .job = "policeman" ; // 此时只会在tom对象上生成job属性,并且Teacher的prototype上的job值不改变
Teacher . prototype .job = "assistant" ; // 此时则会修改Teacher的prototype上的job值
如下图所示,bill 对象继承了 Teacher 原型,bill 对象调用 addCourse 方法时,改变了 teacher 的 course。实际为 bill.__proto__.add("haha");
是 bill 的原型调用了 add,this 指的是原型 Teacher。
1.2 核心知识*
我们需要牢记两点:①__proto__ 和 constructor 属性是对象所独有的;② prototype 属性是函数所独有的,但因为函数也是一种对象,所以函数也拥有 __proto__ 和 constructor属性。
__proto__ 属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的 __proto__ 属性所指向的那个对象(父对象)里找,一直找,直到 __proto__ 属性的终点 null,再往上找就相当于在 null 上取值,会报错。通过 __proto__ 属性将对象连接起来的这条链路即我们所谓的原型链.
prototype 属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即 f1.__proto__ === Foo.prototype
。
constructor 属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向 Function。
简单来说
__proto__
和constructor
属性是对象所独有的;prototype
属性是函数所独有的,函数也是对象,因此拥有3个属性。
__proto__
对象实例指向父对象,constructor
原型指向构造函数,prototype
构造函数指向其原型。
__proto__
最终会到达Object.prototype
至null,constructor
最终会达Function()
。
2. 继承
2.1 ES5 继承
复制 function Animal (name) {
// 实例属性
this .name = name;
// 实例方法
this . sleep = function () {
console .log ( this .name + '正在睡觉!' );
}
}
Animal . prototype . eat = function (food) {
console .log ( this .name + '正在吃:' + food);
};
2.1.1 原型链继承
子类的原型是父类的实例。
复制 function Cat () {}
Cat . prototype = new Animal ();
Cat . prototype .name = 'cat' ;
缺点:
创建子类实例时无法向父类构造函数传参,const cat = new Cat('name')
;
来自原型对象的所有属性被所有实例共享,如果是引用类型则更改会影响其他子类实例;
2.1.2 构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。
复制 function Cat (name) {
Animal .call ( this , name);
}
解决了原型继承的缺点1和缺点2(因为每一次都创建了新的父类实例)。缺点:
2.1.3 实例继承
为父类实例添加新特性,作为子类实例返回。
复制 function Cat3 (name) {
const cat = new Animal (name);
cat .age = 18 // 实例添加属性
return cat
}
缺点:
2.1.4 拷贝继承
为父类实例添加新特性,作为子类实例返回。
复制 function Cat (name) {
const animal = new Animal (name);
for ( let pro in animal) {
Cat . prototype [pro] = animal[pro];
}
}
缺点:
2.1.5 组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
复制 function Cat (name) {
Animal .call ( this , name);
}
Cat . prototype = new Animal ();
Cat . prototype . constructor = Cat5;
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)。
2.1.6 寄生组合继承
通过寄生方式,砍掉父类的实例属性。
复制 function Cat (name) {
Animal .call ( this , name);
}
( function () {
// 创建一个没有实例方法的类
const Super = function () {};
// 将实例作为子类的原型
Super . prototype = Animal . prototype ;
Cat . prototype = new Super ();
})();
2.2 ES6 Class 继承
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。完整内容详见 Class 的继承 。
复制 class Point { /* ... */ }
class ColorPoint extends Point {
constructor (x , y , color) {
this .y = y // ReferenceError
super (x , y); // 调用父类的constructor(x, y)
this .color = color;
}
toString () {
return this .color + ' ' + super.toString (); // 调用父类的toString()
}
}
父类的静态方法,也会被子类继承,而父类的实例对象不能使用父类的静态方法。
复制 class A {
static hello () {
console .log ( 'hello world' );
}
}
class B extends A {
}
B .hello (); // hello world
const b = new A ();
b .hello (); // TypeError: newB.hello is not a function
继承关系说明
复制 class A { }
class B extends A { }
console .log ( B . __proto__ === A ); // true
console .log ( B . prototype . __proto__ === A . prototype ); // true
super关键词的用法
复制 class A {}
class B extends A {
constructor () {
super ();
}
}
当作对象使用时,在普通方法中,指向父类的原型对象(简单说就是父类中没有static的部分),也就是说在父类实例上的方法或属性是无法通过super
调用。在子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例。
复制 class A {
constructor () {
this .p = 2 ;
}
print () {
console .log ( this .x);
}
}
A . prototype .x = 2 ;
class B extends A {
constructor () {
super ();
this .x = 4 ;
}
get m () {
return super.p;
}
get x (){
return super.x;
}
y () {
super.print ();
}
}
let b = new B ();
b .m // undefined
b .x // 2
b .y () // 4
当作对象使用时,在静态方法中,指向父类(简单说就是父类中static的部分)。类相当于实例的原型对象。
复制 // super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
class Parent {
static myMethod (msg) {
console .log ( 'static' , msg);
}
myMethod (msg) {
console .log ( 'instance' , msg);
}
}
class Child extends Parent {
static myMethod (msg) {
super.myMethod (msg);
}
myMethod (msg) {
super.myMethod (msg);
}
}
Child .myMethod ( 1 ); // static 1
var child = new Child ();
child .myMethod ( 2 ); // instance 2