zoukankan      html  css  js  c++  java
  • JavaScript 继承小记

    面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。

    大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class(ES6 引入了class 语法),而是通过“原型对象”(prototype)实现。那么在JS中常见的继承方式有几种呢?

    首先我们先来通过 es5 里的方法做一个类,即构造方法,如下:

     1 function Person() {
     2     // 定义参数
     3     this.name = "张三";
     4     this.age = 18;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 // 通过类添加属性和静态方法
    17 Person.city = "北京";
    18 Person.eat = function () {
    19     console.log(this.name + "在吃")
    20 };
    21 
    22 // 实例化 Person
    23 var p = new Person();
    24 p.run(); // 张三在运动
    25 p.work(); // 张三在工作
    26 // p.eat(); // 报错 p.eat is not a function
    27 Person.eat(); // Person在吃  // this 指向发生改变,为全局,该 name 不是 Person 类中定义的 name

    在上面的代码中,我们根据构造函数方法创建了一个 Person 类,并通过内部定义参数方法,原型链扩展属性和方法,通过类添加属性和方法。接下来我们就根据这个 Person 类来实现集中继承的方法。

    1、对象冒充实现继承

     1 function Person() {
     2     // 定义参数
     3     this.name = "张三";
     4     this.age = 18;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 
    17 function Student() {
    18     Person.call(this);
    19 }
    20 var s = new Student();
    21 console.log(s.name); // 张三
    22 s.run(); // 张三在运动
    23 /**
    24  * 对象冒充继承不能实现
    25  * 父类原型链上的属性和方法
    26  */
    27 console.log(s.sex); // undefined
    28 s.work(); // 报错 s.work is not a function

    在上面的代码中,我们可以通过对象冒充的方法进行继承,但是原型链上的属性和方法是不能被继承的。

    2、原型链继承

     1 function Person() {
     2     // 定义参数
     3     this.name = "张三";
     4     this.age = 18;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 // 通过类添加属性和静态方法
    17 Person.city = "北京";
    18 Person.eat = function () {
    19     console.log(this.name + "在吃")
    20 };
    21 
    22 function Student() {
    23 
    24 }
    25 Student.prototype = new Person();
    26 var s = new Student();
    27 console.log(s.name); // 张三
    28 s.run(); // 张三在运动
    29 console.log(s.sex); //
    30 s.work(); // 张三在工作

    在上面的代码中,我们通过原型链的方法进行继承,这样构造方法内的方法和原型链上的方法我们都能继承了,看似很不错,但是其实存在着一些问题,如下:

     1 function Person(name,age) {
     2     // 定义参数
     3     this.name = name;
     4     this.age = age;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 var p = new Person("张三",18);
    17 p.run(); // 张三在运动
    18 p.work(); // 张三在工作
    19 
    20 function Student(name,age) {
    21 
    22 }
    23 Student.prototype = new Person();
    24 var s = new Student("李四", '20');
    25 console.log(s.name); // undefined
    26 s.run(); // undefined在运动
    27 console.log(s.sex); //
    28 s.work(); // undefined在工作

    在上面的代码中,我们可以看出,通过原型链继承,在实例化子类的时候没法给父类进行传参。

    3、原型链+对象冒充组合继承

     1 function Person(name, age) {
     2     // 定义参数
     3     this.name = name;
     4     this.age = age;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 var p = new Person("张三", 18);
    17 p.run(); // 张三在运动
    18 p.work(); // 张三在工作
    19 
    20 function Student(name, age) {
    21     Person.call(this, name, age); // 对象冒充
    22 }
    23 
    24 Student.prototype = new Person();
    25 var s = new Student("李四", '20');
    26 console.log(s.name); // 李四
    27 s.run(); // 李四在运动
    28 console.log(s.sex); //
    29 s.work(); // 李四在工作

    上面的代码中我们通过原型链继承+对象冒充的组合方式实现了继承,不过也存在缺点就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。

    4、组合继承优化

     1 function Person(name, age) {
     2     // 定义参数
     3     this.name = name;
     4     this.age = age;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 var p = new Person("张三", 18);
    17 p.run(); // 张三在运动
    18 p.work(); // 张三在工作
    19 
    20 function Student(name, age) {
    21     Person.call(this, name, age); // 对象冒充
    22 }
    23 
    24 /**
    25  * 可以只继承 Person 的原型链
    26  * 因为上面已经通过对象冒充继承了 Person 构造方法
    27  */
    28 Student.prototype = Person.prototype;
    29 var s = new Student("李四", '20');
    30 console.log(s.name); // 李四
    31 s.run(); // 李四在运动
    32 console.log(s.sex); //
    33 s.work(); // 李四在工作
    34 
    35 console.log(s instanceof Student, s instanceof Person); //true true
    36 console.log(s.constructor); //Person

    这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。

    但是这种方法没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。

    5、组合继承继续优化

     1 function Person(name, age) {
     2     // 定义参数
     3     this.name = name;
     4     this.age = age;
     5     // 定义方法
     6     this.run = function () {
     7         console.log(this.name + "在运动");
     8     }
     9 }
    10 
    11 // 通过原型链扩展属性和方法
    12 Person.prototype.sex = "男";
    13 Person.prototype.work = function () {
    14     console.log(this.name + "在工作");
    15 };
    16 var p = new Person("张三", 18);
    17 p.run(); // 张三在运动
    18 p.work(); // 张三在工作
    19 
    20 function Student(name, age) {
    21     Person.call(this, name, age); // 对象冒充
    22 }
    23 
    24 Student.prototype = Object.create(Person.prototype);
    25 Student.prototype.constructor = Student;
    26 var s = new Student("李四", '20');
    27 console.log(s.name); // 李四
    28 s.run(); // 李四在运动
    29 console.log(s.sex); //
    30 s.work(); // 李四在工作
    31 
    32 console.log(s instanceof Student, s instanceof Person); // true true
    33 console.log(s.constructor); // Student

    在上面的代码中,我们通过借助原型可以基于已有的对象来创建对象, var B = Object.create(A)  以A对象为原型,生成了B对象。B继承了A的所有属性和方法。

    同样的,Student 继承了所有的 Person 原型对象的属性和方法。目前来说,最完美的继承方法!

    上面的代码我们都是基于 ES5 的特性进行的继承,在 ES6 中为我们提供了 class 类来帮助我们更快更好的实现继承。

    ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

    ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.call(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

    需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。

     1 class Person {
     2     //调用类的构造方法
     3     constructor(name, age) {
     4         this.name = name;
     5         this.age = age;
     6     }
     7     
     8     //定义方法
     9     run() {
    10         console.log(this.name + "在运动");
    11     }
    12 }
    13 
    14 let p = new Person('张三', 18);
    15 console.log(p); // Person { name: '张三', age: 18 }
    16 
    17 //定义一个子类
    18 class Student extends Person {
    19     constructor(name, age) {
    20         //通过super调用父类的构造方法
    21         super(name, age)
    22     }
    23 
    24     //在子类自身定义方法
    25     work() {
    26         console.log(this.name + "在工作");
    27     }
    28 }
    29 
    30 let s = new Student('李四', 18);
    31 console.log(s); // Student { name: '李四', age: 18 }
    32 s.run(); // 李四在运动
    33 s.work(); // 李四在工作

    上面通过 class 关键字所创建的类的继承简单易懂,是未来 JS 的发展方向。

  • 相关阅读:
    Android 开发 深入理解Handler、Looper、Messagequeue 转载
    Android 开发 Handler的基本使用
    Java 学习 注解
    Android 开发 AlarmManager 定时器
    Android 开发 框架系列 百度语音合成
    Android 开发 框架系列 Google的ORM框架 Room
    Android 开发 VectorDrawable 矢量图 (三)矢量图动画
    Android 开发 VectorDrawable 矢量图 (二)了解矢量图属性与绘制
    Android 开发 VectorDrawable 矢量图 (一)了解Android矢量图与获取矢量图
    Android 开发 知晓各种id信息 获取线程ID、activityID、内核ID
  • 原文地址:https://www.cnblogs.com/weijiutao/p/12090916.html
Copyright © 2011-2022 走看看