类的声明
原生方法:
function Animal(name){
this.name = name;
}
ES6方法:
class Animal{
constructor(name){
this.name = name;
}
}
类与继承
实现继承的原理就是原型链。
继承的几种方式
第一种:借助构造函数实现继承
function Parent(name){
this.name = name;
}
function Child(age,name){
//在子类构造函数内执行父级构造函数
//call方法在这里改变了Parent函数运行时的上下文,这样在实例化子类时会将父类中的属性和方法挂载到子类实例上
Parent.call(this,name);
//为子类添加自己的属性
this.age = age;
}
console.log(new Child(18,'happychen'));
借助构造函数实现继承的缺点:
Parent.call(this)
只是改变了子类实例中的上下文,因此只能继承父级构造函数体内的内容,而Parent.prototype上的内容无法继承。比如:Parent.prototype.say = function(){...}
原型链上的say()方法Child子类是无法继承下来的。
第二种:借助原型链实现继承
先区分三个:
function Parent(name){
this.name = name;
}
Parent.prototype.say = function(){};
function Child(age){
this.age = age;
}
Child.prototype = new Parent();
console.log(Parent);
console.log(Parent.prototype);
console.log(new Parent());
继续:
function Parent(name){
this.name = name;
}
Parent.prototype.say = function(){};
function Child(age){
this.age = age;
}
//prototype是子类构造函数的属性,当然,每个函数都有prototype属性,这个属性是可以赋值的,这里赋值为Parent的实例对象,
//new Parent()其实就是一个对象,现在变成了子类prototype的值
Child.prototype = new Parent();
//原型链中介绍过,new Child(18).__proto__ === Child.prototype;
//所以在这里new Child(18).__proto__ 实际上引用的是父类的实例对象
//接着就是按照原型链的查找方式
console.log(new Child(18));
借助原型链实现继承的缺点:
function Parent(name){
this.name = name;
this.nums = [1,2,3];//新增一个引用类型的属性
}
Parent.prototype.say = function(){};
function Child(age){
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
console.log(child1.nums,child2.nums);
child2.nums.push(4);
console.log(child1.nums,child2.nums);
因为原型链中的原型对象是公用的,child1.__proto__ === child2.__proto__
引用的是同一个对象,也就是父类的实例对象,而nums在父类实例对象上并且是引用类型,所以child1.nums
,child2.nums
会相互影响。
第三种:组合方式(把以上两种方式结合,弥补互相的不足)
function Parent(name){
this.name = name;
this.nums = [1,2,3];
}
function Child(age,name){
Parent.call(this,name);//父类会执行
this.age = age;
}
Child.prototype = new Parent();//父类会执行
child1 = new Child(18,'happychen');
child2 = new Child(18,'kedaya');
child1.nums.push(4);
console.log(child1);
console.log(child2);
虽然组合方式比较通用,但也有缺点:
在子类实例化对象时,父类会执行两次(子类函数体内执行了一次,在原型继承时又执行了一次)。然而这两次没必要。
优化继承方法:
function Parent(name){
this.name = name;
this.nums = [1,2,3];
}
function Child(age,name){
Parent.call(this,name);//父类会执行
this.age = age;
}
// Child.prototype = new Parent();
Child.prototype = Parent.prototype;//此时子类的原型对象和父类的原型对象引用的是同一个对象,而且会改变子类的constructor
child1 = new Child(18,'happychen');
child2 = new Child(18,'kedaya');
child1.nums.push(4);
console.log(child1.constructor);
这种方法也有一个缺点:
继续优化继承方法:
Object.create(参数)方法创建的对象的原型对象就是它里面的参数。
function Parent(name){
this.name = name;
this.nums = [1,2,3];
}
function Child(age,name){
Parent.call(this,name);//父类会执行
this.age = age;
}
// Child.prototype = new Parent();
// Child.prototype = Parent.prototype;
Child.prototype = Object.create(Parent.prototype);//这样可以将父类和子类的原型对象隔离
child1 = new Child(18,'happychen');
child2 = new Child(18,'kedaya');
child1.nums.push(4);
console.log(child1.constructor);
console.log(child1.nums,child2.nums);
Child实例的原型对象是Child.prototype,Child.prototype又是Object.create(参数)的参数。
但是Child的constructor还是Parent,这是手动修改:
function Parent(name){
this.name = name;
this.nums = [1,2,3];
}
function Child(age,name){
Parent.call(this,name);//父类会执行
this.age = age;
}
// Child.prototype = new Parent();
// Child.prototype = Parent.prototype;
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child // 手动修改Child的constructor
child1 = new Child(18,'happychen');
child2 = new Child(18,'kedaya');
child1.nums.push(4);
console.log(child1.constructor);
console.log(child1.nums,child2.nums);
以上一步一步弄清楚了类继承的原理和区别。