zoukankan      html  css  js  c++  java
  • 理解 javascript 的继承

    javascript是一种基于原型链的语言,继承是在平时的工作遇到最多的一个知识点下面讨论下在es5中的几种继承方式.
    1.原型继承
    原型继承即是根据原型链的规则把父类挂在子类的prototype上面,原型继承有两种形式:继承到实例对象,继承到原型对象
    注意:原型继承在改变原型对象的时候,需要把原型里面的constructor重新指向原构造函数,以免破坏原型链规则。
    第一种:继承到实例对象
    function Person() {
      this.name = "person";
      this.arr = [1];
      this.sayName = function() {
        return this.name;
      };
    }
    Person.prototype.gender = "man";
     
    function Student() {
      this.age = "12";
    }
    Student.prototype = new Person();
    console.log(Student.prototype.constructor); // f Person()
    Student.prototype.constructor = Student; //改变原型对象的指向后,会破坏原型链的继承规则,必须手动纠正,要把原型对象的构造函数指向本身的构造函数,
    var student = new Student();
    var person = new Student();
    console.log(student.sayName()); //person
    console.log(student.name); //person`
    student.name = "student";
    console.log(student.name); //student
    console.log(person.name); //person
    student.arr.push(2); //
    console.log(student.arr); //[1,2]
    console.log(person.arr); //[1,2]
    缺点:
    1.因为原型对象是公用一个对象,所以当原型对象里面的值是引用类型时候,改变会发生相互影响。
    2.创建子类实例时,无法向父类构造函数传参
     
    第二种:继承到原型对象
    Student.prototype = Person.prototype;
    Student.prototype.constructor = Student;
    var student = new Student();
    var person = new Person();
    console.log(student.gender); //man`
    Student.prototype.gender = "felman"; // 因为这里的Student.prototype和Person.prototype是共同的引用,所以改变其中一个互相都有形象
    console.log(student.gender); //felman
    console.log(person.gender); //felman
     
    解决办法:取用一个空的函数作为构造函数过度
    var F = function() {};
    F.prototype = Person.prototype;
    Student.prototype = new F();
    Student.prototype.constructor = Student;
    var student = new Student();
    var person = new Person();
    console.log(student.gender); //man`
    Student.prototype.gender = "felman"; 
    console.log(student.gender); //felman
    console.log(student.name); //undefined
    console.log(person.gender); //man
     
    // 封装一个基于原型的继承函数
    function extend(Child, Person) {
      var F = function() {};
      F.prototype = Person.prototype;
      Child.prototype = new F();
      Child.prototype.constructor = Child;
      Child.uber = Parent.prototype; // 创建一个继承指针
    }
    缺点:只继承了父类的原型对象上的属性方法,无法继承其构造函数里面的属性方法。
     
    2.构造函数/绑定继承
    利用借用父类的构造函数来增强子类的实例,相当于复制一份父类的构造函数到子类
    function Person(val) {
      this.name = val;
      this.arr = [1];
      this.sayName = function() {
        return this.name;
    };
    }
    Person.prototype.gender = "man";
    function Student(val) {
      Person.call(this, val);
      this.age = "12”; 
    }
    var student = new Student("student");
    var student1 = new Student("student");
    console.log(student.name); //student
    console.log(student.age); //12
    console.log(student.sayName()); //student
    console.log(student.gender) //undefined
    console.log(student.sayName === student1.sayName); //false
    缺点:没创建一个实例都要存一份sayName函数,无法实现函数复用(没有用到原型)
     
    3.构造函数+原型=组合继承
    前面两种继承,一种无法继承父类的原型对象,一种无法继承父类的构造函数,所以选择两种结合的方式进行互相弥补。
    function Person(val) {
      this.name = val;
      this.arr = [1];
    }
    Person.prototype.gender = "man";
    Person.prototype.sayName = function() {
      return this.name;
    };
     
    function Student(val) {
      Person.call(this, val);
      this.age = "12";
    }
    Student.prototype = new Person();
    Student.prototype.constructor = Student;
    var student = new Student("student");
    var student1 = new Student("student");
    console.log(student.name); //student
    console.log(student.age); //12
    console.log(student.gender); //man
    console.log(student.sayName()); //student
    console.log(student.sayName === student1.sayName); //true
    利用call实现构造函数的复制,和原型对象对父类实例的继承实现对父类的彻底继承。
    缺点:这种方式有两个缺陷,一个是原型继承的缺陷没有避免,二是会重复继承父类的构造函数里面的属性方法。
     
    4.寄生组合继承
    为了解决上面方法的缺点,并实现对父类的完美继承,我们就去要对其进行改进
    function Person(val) {
      this.name = val;
      this.arr = [1];
    }
    Person.prototype.gender = "man";
    Person.prototype.sayName = function() {
      return this.name;
    };
    function Student(val) {
      Person.call(this, val); // 继承构造函数的属性
      this.age = "12";
    }
    var F=function(){}
    F.prototype=Person.prototype
    Student.prototype=new F() // 只继承原型上的属性
    Student.prototype.constructor = Student;
    var student = new Student("student");
    var student1 = new Student("student");
    console.log(student.name); //student
    console.log(student.age); //12
    console.log(student.gender); //man
    console.log(student.sayName()); //student
    console.log(student.sayName === student1.sayName); //true
    利用一个空对象继承父类的原型,然后让子类去继承这个对象实现对父类原型的继承,利用call来实现子类构造函数对父类构造函数的继承。
    缺点:有点麻烦,不容易理解。
     
    5.拷贝继承
    实现继承的原则就是要让子类用到父类的属性和方法,这些方法和属性都存在父类的内存中,复制一份到子类是一种简单粗暴的方式,鉴于浅拷贝会造成共享引用类型的值的改变会产生影响。我们直接用深拷贝来完成继承。
    检查类型
    function checkType(target) {
      return Object.toString(target).slice(8, -1);
    }
     
    function deepClone(target) {
      let result;
      let targetType = checkType(target);
      if (targetType === "array") {
        result = [];
      } else if (targetType === "object") {
        result = {};
      } else {
        return target;
      }
      // 遍历
      for (let i in target) {
        let value = target[i];
    // 递归的判断条件为引用类型的时候递归
        if (checkType(value) === "Object" || checkType(value) === "Array") {
           result[i] = deepClone(value);
        } else {
           result[i] = value;
        }
      }
      return result;
     }
     
    function Person() {
      this.name = "person";
      this.arr = [1];
      this.sayName = function() {
        return this.name;
      };
    }
    Person.prototype.gender = "man";
    var person = new Person();
    var student = deepClone(person);
    console.log(student.gender); //man
    console.log(student.name); //person
    缺点:没有用到原型带来的好处,对象较复杂情况用深拷贝带来的性能问题。
     
    总结:不同的继承方式有适用的场景,继承的原理基本是基于原型链,改变引用对象(call)或者拷贝,理解继承的原理和优化继承的方式就能更好的理解继承。
     
     
     
     
     
     
     
  • 相关阅读:
    遍历迭代map的集中方法
    雅可比迭代法
    Myeclipse无法开启Servers视图解决办法
    JS去除空格方法记录
    10分钟学会前端调试利器——FireBug
    Linux入门
    Maven工程引入jar包
    android一些常用的代码1(收藏)
    android中列表的滑动删除仿ios滑动删除
    android 中使用缓存加载数据
  • 原文地址:https://www.cnblogs.com/chrissong/p/10474648.html
Copyright © 2011-2022 走看看