继承无非是获取父类的属性和方法,所以各种继承方式都是想方设法获取父类中this修饰的属性和方法,以及原型prototype中的方法。
注意:父类的静态属性和静态方法虽然被继承,但是无法访问。
目录
核心想法就是:将父类的实例对象作为子类的原型对象。子类实例对象通过__proto__属性找自己的构造函数的原型,即父类的实例对象,从中获取定义在父类构造函数中的属性和方法;再通过其原型对象(父类的实例对象)的__proto__属性找到父类的原型,从而获取父类原型中的方法。
但是这种方式存在缺陷,1,子类的每一个实例对象会共享父类的引用类型属性,比如数组、对象等。2,子类实例对象无法给父类构造函数传参。
实现共享的原因为:父类的实例对象作为子类的原型,而原型会被子类所有实例对象所共享,所以子类的任何实例对象访问引用类型都是在访问其原型的属性,导致共享。
- function A() {
- this.arr = [1]; //数组引用类型
- this.age = 18;
- }
- /***************************************/
- A.prototype.getAge = function() {
- return this.age
- }
- /***************************************/
- function B() {}
- /***************************************/
- B.prototype = new A(); //只有在这个地方才能给父类构造函数传参,子类实例对象无法给父类构造函数传参,每个对象的个性无法满足
- /***************************************/
- b1 = new B();
- b1.arr.push(2); //对象b1添加2到arr数组中
- /***************************************/
- b2 = new B();
- console.log(b2.arr); //b2对象居然可以打印b1添加到数组中的2
- console.log(b2.getAge());
核心想法:使用call方法调用父类构造函数,让父类中的this变为子类实例对象的this。
优点:每个子类实例对象具有自己的属性值,不共享引用类型。
缺点:无法继承父类的原型对象中的方法。每个子类的实例对象都带有父类构造函数中的属性和方法,比较臃肿。
- function A(names) {
- this.arr = [1]; //数组引用类型
- this.age = 18;
- this.names = names;
- }
- /***************************************/
- A.prototype.getAge = function() {
- return this.age
- }
- /***************************************/
- function B(names) {
- A.call(this, names = names)
- }
- /***************************************/
- b1 = new B('b1');
- b1.arr.push(2); //对象b1添加2到arr数组中
- /***************************************/
- b2 = new B('b2');
- console.log(b2.arr); //b2对象不可以打印b1添加到数组中的2
核心想法:使用原型链继承父类的原型,使用构造函数继承父类构造函数中的属性和方法。
优点:父类可以复用传参,解决了原型链继承引用类型共享的问题。
解决共享的原因如下:call方法为子类实例对象添加了父类的引用类型属性,导致子类的实例对象没有必要再去其原型上寻找(自己有没必要去问原型要那个共享的)。
- function A(names) {
- this.arr = [1]; //数组引用类型
- this.age = 18;
- this.names = names;
- }
- /***************************************/
- A.prototype.getAge = function() {
- return this.age
- }
-
- /***************************************/
- function B(names) {
- A.call(this, names = names)
- }
- B.prototype = new A('test');
- /***************************************/
- b1 = new B('b1');
- b1.arr.push(2); //对象b1添加2到arr数组中
- /***************************************/
- b2 = new B('b2');
- console.log(b2.arr); //b2对象不可以打印b1添加到数组中的2
- console.log(b2.names);
- // [ 1 ]
- // b2
核心想法:利用对象的遍历,将父类的原型copy到子类构造函数的原型上。
优点:直观,方便。支持多继承。
缺点:慢。
- //构造函数继承,先继承父类的基础属性,在通过遍历原型对象继承父类原型
- function Person(name, age) {
- this.name = name;
- this.age = age;
- }
- Person.prototype.getName = function() {
- console.log(this.name);
- }
- Person.prototype.getAge = function() {
- console.log(this.age);
- }
-
- function Student(learn, name, age) {
- //基础属性:调用父类的构造函数,将Student实例对象的this传进父类,父类的this就指向Student实例对象
- Person.call(this, name, age);
- this.learn = learn;
- }
-
- //继承父类的原型,遍历原型对象,每一次的p都是原型的属性或者方法
- for (var p in Person.prototype) {
- Student.prototype[p] = Person.prototype[p];
- }
-
- Student.prototype.getLearn = function() {
- console.log(this.learn);
- }
-
- Student.prototype.constructor = Student;
-
- var student = new Student("张三", 19, "it");
- student.getAge();
- student.getName();
- student.getLearn();
核心思想:在子类构造函数中生成一个父类的实例对象,再对这个父类实例对象添加新的方法和属性,然后返回。
优点:直观。缺点:并不是子类的实例对象,而是被修饰了的父类对象。不支持多继承。
- // 定义一个动物类
- function Animal (name) {
- // 属性
- this.name = name || 'Animal';
- // 实例方法
- this.sleep = function(){
- console.log(this.name + '正在睡觉!');
- }
- }
- // 原型方法
- Animal.prototype.eat = function(food) {
- console.log(this.name + '正在吃:' + food);
- };
- // 定义一个猫类
- function Cat(name){
- var instance = new Animal();
- instance.name = name || 'Tom';//如果无参数传进来,就叫他Tom猫
- return instance;
- }
-
- // Test Code
- var cat = new Cat();
- console.log(cat.name);
- console.log(cat.sleep());
- console.log(cat instanceof Animal); // true
- console.log(cat instanceof Cat); // false
核心想法:将父类原型复制给中间人,中间人再把自己的实例对象给子类构造函数的原型。
优点:避免了父类构造函数多次执行消耗资源。
- // 定义一个动物类
- function Animal (name) {
- // 属性
- this.name = name || 'Animal';
- // 实例方法
- this.sleep = function(){
- console.log(this.name + '正在睡觉!');
- }
- }
- // 原型方法
- Animal.prototype.eat = function(food) {
- console.log(this.name + '正在吃:' + food);
- };
- // 定义一个猫类
- function Cat(name){
- Animal.call(this);
- this.name = name || 'Tom';
- }
- (function(){
- // 创建一个没有实例方法的类,也就是中间人,省略了构造函数中的属性和方法复制的时间
- var Super = function(){};
- Super.prototype = Animal.prototype;
- //将实例作为子类的原型
- Cat.prototype = new Super();
- })();
-
- Cat.prototype.constructor = Cat; // 需要修复下构造函数
- // Test Code
- var cat = new Cat();
- console.log(cat.name);
- console.log(cat.sleep());
- console.log(cat instanceof Animal); // true
- console.log(cat instanceof Cat); //true
核心想法:构造函数继承实现copy父类构造函数中的属性和方法。Object.create()实现原型拷贝。
- var Person = function(name) {
- this.name = name;
- }
- Person.prototype.say = function() { console.log('hello'); }
-
- var Boy = function(sex) {
- this.sex = sex;
- }
- Boy.prototype.hobby = 'football';
-
- //继承基础属性
- var Xiaomng = function(name, sex, age) {
- this.age = age;
- Person.call(this, name);
- Boy.call(this, sex);
- }
-
- //继承Person的原型
- Xiaomng.prototype = Object.create(Person.prototype); //create只能用一次
- //继承Boy的原型,assign的作用是合并两个对象,将Boy.prototype的属性加到Xiaomng.prototype对象上
- Object.assign(Xiaomng.prototype, Boy.prototype);
-
- //添加Xiaoming构造函数的原型的constructor属性
- Xiaomng.prototype.constructor = Xiaomng;
综上所述,可得到如下结论:
父类构造函数中的属性和方法建议采用构造函数的方式实现继承。这样单继承多继承都可以实现。
父类的原型采用寄生(中间人模式)或者Object.create()方式实现继承。