一、JS中的抽象概念
1. 原型:
在js中万物皆对象,对象又分为两种:普通对象(Object)和(Function).–null也是对象,但是这里不做考虑。
任何对象都具有隐式原型属性(proto)
只有函数对象有显示原型属性(prototype)。
2. 原型链
原型链是针对原型对象的,在查找实例属性时,现在实例中查找,如果没有找到,再到obj.proto(f1.prototype)原型属性中查找,再到f1.prototype._proto_中查找,依次向上,直到Object.prototype。
3. 作用域链
作用域链是针对变量的,先在自己的变量范围内查找,如果找不到,就沿着作用域链向上查找,直到window结束。
4. 构造函数、原型对象和实例之间的联系
5. 构造函数、原型对象、子类构造函数、子类原型对象和子类实例之间的联系
二、ES5的继承方式
1.原型链继承
子类的原型为父类的一个实例对象
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function() {}
}
Person.prototype.setAge = function() {};
//子类型
function Student(price) {
this.price = price;
this.setScore = function() {};
}
Student.prototype = new Person;//核心代码
//子类的原型为父类的一个实例对象
var s1 = new Student(25);
var s2 = new Student(35);
console.log(s1, s2);
s1可以通过s1. _ proto _ = Student.prototype => s1._ proto _ = new Person。这样实例对象就可以访问到父类的私有方法,然后s1再通过_ proto _指向父类的prototype就可以获得父类原型上的方法。所以做的到了将父类的私有、公有方法和属性都作为子类的公有属性。
这就暴露出了问题,我们都知道在操作基本数据类型的时候操作的是值,在操作引用数据类型的时候操作的是地址,如果说父类的私有属性中有引用类型的属性,它被子类继承的时候会作为公有属性,这样s1操作这个属性的时候,就会影响到s2。
s1.play.push(4)
console.log(s1.play, s2.play)
console.log(s1.__proto__ === s2.__proto__)//true
console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__)//true
s2随着s1变化而变化。
我们需要在子类中添加新的方法或者重写父类方法的时候,切记一定要放在替换原型的语句之后
//Student.prototype.sayHello = function() { }//写在这无效
//因为会改变原型指向,所以要放在之后指定
Student.prototype = new Person();
Student.prototype.sayHello = function() { }
var S1 = new Student(25);
console.log(s1);
//Student {price: 15000, setScore: ƒ}
//price: 15000
//setScore: ƒ ()
//__proto__: Person
//age: undefined
//name: undefined
//sayHello: ƒ ()
//__proto__: Object
缺点:
- 无法实现多继承
- 来自原型对象的所有属性被实例共享
- 创建子类实例时,无法向父类构造函数传参
- 要想为子类新增属性和方法,必须放在
Student.prototype = new Person()
之后执行,不能放在构造器里。
2.构造函数继承
在子类型构造函数中通过call()调用父类构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function() { };
}
Person.prototype.setAge = function() {};
function Student(name, age, price) {
Person.call(this, name, age);//核心代码
this.price = price
}
var s1 = new Student('Tom', 20, 4999);
console.log(s1)
//Student {name: "Tom", age: 20, price: 4999, setName: ƒ}
//age: 20
//name: "Tom"
//price: 4999
//setName: ƒ ()
//__proto__: Object
这种方法只能实现部分的继承,如果父类的原型还有方法或者属性,子类是拿不到这些属性和方法的。
console.log(s1.setAge())//Uncaught TypeError: s1.setAge is not a function
特点:
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性个方法,不能继承原型属性和方法
- 无法实现函数复用,每个子类都有父类实例属性的副本,影响性能。
3.组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类的原型,实现函数复用。
function Person(name, age) {
this.name = name;
this.age = age;
this.setAge = function() {}
}
Person.prototype.setAge = function() {
console.log("111");
}
function Student(name, age, price) {
Person.call(this, name, age);//核心代码
this.price = price;
this.setScore = function() {}
}
Student.prototype = new Person();//核心代码
Student.prototype.constructor = Student //核心代码
//组合继承也是需要修复构造函数的指向
Student.prototype.sayHello = function() {};
var s1 = new Student('Tom', 20, 344);
var s2 = new Student('jack', 24, 355);
var p1 = new Person("mack", 33);
console.log(s1);
console.log(s1.constructor);
console.log(p1.constructor);
//Student {name: "Tom", age: 20, price: 344, setAge: ƒ, setScore: ƒ}age: 20name: "Tom"price: 344setAge: ƒ ()setScore: ƒ ()__proto__: Person
//VM776:21 ƒ Student(name, age, price) {
// Person.call(this, name, age);
// this.price = price;
// this.setScore = function() {}
}
//VM776:22 ƒ Person(name, age) {
// this.name = name;
// this.age = age;
// this.setAge = function() {}
}
组合继承是js中最常用的继承模式。
缺点:
- 会调用两次构造函数(一次在创建子类型原型的时候,另一次是在子类型构造函数内部)
优点:
- 可以继承实例属性、方法,也可以继承原型属性方法
- 不存在引用属性共享问题
- 可以传参
- 函数可复用
2.寄生组合式继承
借助原型可以基于已有的对象来创建对象,var B = Object.create(A)
以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setAge = function() {
console.log("111");
}
function Student(name, age, price) {
Person.call(this, name, age)//核心代码
this.price = price;
this.setScore = function() {}
}
Student.prototype = Object.create(Person.prototype) //核心代码
Student.prototype.constructor = Student //核心代码
var s1 = new Student("Tom", 20, 377);
console.log(s1 instanceof Student, s1 instanceof Person) //true true
console.log(s1.constructor) //Student
console.log(s1)
//true true
//VM784:17 ƒ Student(name, age, price) {
// Person.call(this, name, age)//核心代码
// this.price = price;
// this.setScore = function() {}
// }
//VM784:18 Student {name: "Tom", age: 20, price: 377, setScore: ƒ}
目前公认最完美的方法!!!
三、ES6中class的继承
ES6引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的继承方法。
ES5的继承,实质是创造子类的实力对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面,然后再用子类的构造函数修改this。
class Person{
//调用类的构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
//定义一般的方法
showName() {
console.log("调用父类的方法");
console.log(this.name, this.age);
}
}
let p1 = new Person('jack', 20);
console.log(p1);
//定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age)//调用父类的构造方法
this.salary = salary;
}
//在子类自身定义方法
showName() {
console.log("调用子类的方法");
console.log(this.name, this.age, this.salary)
}
}
let s1 = new Student('one', 38, 100000)
console.log(s1);
s1.showName();
四、实现多继承
1.ES5方法
function BaseClass1() {
this.age = 10;
this.sex = 'male'
}
BaseClass1.prototype.run = function() {
console.log("run");
}
function BaseClass2() {
this.name = 'jack';
this.salary = 5999;
}
BaseClass2.prototype.walk = function() {
console.log("walk");
}
function Child() {
this.city = "beijing";
this.hobby = "dance";
//实例属性继承
BaseClass1.call(this);
BaseClass2.call(this);
}
//原型链继承
for(let prop in BaseClass1.prototype) {
Child.prototype[prop] = BaseClass1.prototype[prop];
}
for(let prop in BaseClass2.prototype) {
Child.prototype[prop] = BaseClass2.prototype[prop];
}
//Child.prototype.constructor = Child;
var childInstance = new Child();
childInstance.run();
childInstance.walk();
console.log(childInstance.name);
console.log(childInstance.age);
//run
//walk
//jack
//10
2.ES6方法
// 共同的特性
class Base {
constructor() {
console.log("Base");
}
}
// 鱼类的特性【创建一个鱼类并继承Base】
const FishMixin = (superClass) => class extends superClass {
constructor() {
super();
console.log("FishMixin");
}
};
// 狗的特性【创建一个狗类并继承Base】
const DogMixin = (superClass) => class extends superClass {
constructor() {
super();
console.log("DogMixin");
}
}
// FishMixin 和 DogMixin 是没有任何继承关系的,如何Test都继承,就是多继承了
class test extends DogMixin(FishMixin(Base)) {
}
var child = new test();
//Base
//FishMixin
//DogMixin
总结
重点理解原理