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 的发展方向。

  • 相关阅读:
    Java内存模型原理,你真的理解吗?
    CentOS 7.4 下搭建 Elasticsearch 6.3 搜索群集
    CentOS 7下ElasticSearch集群搭建案例
    分布式系统理论基础
    分布式系统理论进阶
    Paxos,Raft,Zab一致性协议-Raft篇
    P2P 网络核心技术:Gossip 协议
    分布式系统Paxos算法
    Hacker News的热门排名算法(转)
    Elasticsearch分布式机制和document分析
  • 原文地址:https://www.cnblogs.com/weijiutao/p/12090916.html
Copyright © 2011-2022 走看看