JS实现类与继承

算是东拼西凑的一篇文章吧,但是确实觉得js实现类和公有私有方法,也就是面向对象的一些东西是蛮重要的

基础知识

JavaScript中的类

JavaScript实际上是一种弱类型语言,与C++和Java等语言不同。因此,在JavaScript中,没有强调类(class)这一概念,但实际运用中,类还是很重要的,比如写一款游戏,如果我们不停地调用函数来完成创建角色,移动角色的话,那会是什么样的呢?可能会出现非常多的重复代码,因此我们需要一个类来统一这些代码。所谓的类,就是把程序中的代码分类,比如说游戏中的关于角色的代码算作一类,游戏背景算作一类,游戏特效又是一类。这样一来,我们对类进行操作,就不会使代码显得很凌乱,冗杂。虽然Js是弱类型语言,但是也提供了类这一概率。 
定义Js中的类,实际上用的是function,总所周知,这个语法其实是用来定义函数的。不同于定义函数的是,我们可以在function中通过this.xxx的方式来定义属性和方法。比如说:

function People () {
    this.name = "Yorhom";

    this.getName = function () {
        return this.name
    };
}

使用的时候使用new

var yorhom = new People();
// "Yorhom"
alert(yorhom.getName());

可以看到,这样就可以使用到我们定义的类和类中的方法了。 
也许你会问this.xxx只能定义公有属性和方法,那私有属性和方法怎么办呢?这个可以用到js闭包的知识来解决:

function People () {
    this.name = "Yorhom";

    var age = 16;

    this.getName = function () {
        return this.name
    };

    this.getAge = function () {
        return age;
    };
}

var yorhom = new People();
// undefined
alert(yorhom.age);
// 16
alert(yorhom.getAge());

可以看到,这里的age就是一个私有属性了。

JavaScript中的prototype

上面的代码美中不足的地方就是,如果一个类有很多方法,同时用到这个类的地方又有很多(也就是new出来的对象有很多),那么用上面的代码就会出现内存占用过剩的问题。问题的根本原因在于,每次实例化一个对象,这个类就会执行构造器里的代码(以People类为例就是function People () {…}执行的代码),因此每当这个类被实例化的时候,这些方法和属性就会被拷贝到实例化出来的对象中。这样一来,就会造成“吃”内存的现象。 
于是js中的prototype就诞生了。prototype的作用通常是给一个类添加一系列常量或者方法。 每当一个类被实例化之后,实例化出来的对象会自动获取类的prototype中定义的方法和属性。只不过这里的获取类似于C++里面的引用,不会在内存里对这些方法和属性进行复制,而是指向这些方法和属性。示例:

function People () {
    this.name = "Yorhom";
}

People.prototype.getName = function () {
    return this.name;
};

var yorhom = new People();
// "Yorhom"
alert(yorhom.getName());

这种方法虽然可以节约内存,但是,美中不足的是,无法定义私有属性。

继承


版本1:来自书上的官方说法,老实说不太容易记住

一.类式继承

简介:将父类对象的实例赋值给子类的原型,则子类的原型可以访问父类原型上的属性和方法,以及父类构造函数中复制的属性和方法。

[javascript]  view plain  copy
  1. //1.类式继承  
  2. //声明父类  
  3. function SuperClass() {  
  4.   this.superValue = true;  
  5. }  
  6. //为父类添加公有方法  
  7. SuperClass.prototype.getSuperValue = function () {  
  8.   return this.superValue;  
  9. };  
  10. //声明子类  
  11. function SubClass() {  
  12.   this.subValue = false;  
  13. }  
  14. //继承父类  
  15. SubClass.prototype = new  SuperClass();//将父类对象赋值给子类原型,子类原型可访问父类原型上的属性和方法--类式继承原理  
  16. //子类添加公有方法  
  17. SubClass.prototype.getSubValue = function() {  
  18.   return this.subValue;  
  19. };  
  20.   
  21. //测试  
  22. var instance = new SubClass();  
  23. console.log(instance.getSuperValue());//t  
  24. console.log(instance.getSubValue());//f  
  25. //  
  26. console.log(instance instanceof SuperClass);//t  
  27. console.log(instance instanceof SubClass);//t  
  28. console.log(SubClass instanceof SuperClass);//f  
  29. console.log(SubClass.prototype instanceof SuperClass);//t  
通过instanceof来检测某个对象是否是某个类的实例,或者某个对象是否继承了某个类,这样可以判断对象和类之间的继承关系。instanceof通过判断对象的prototype链来确定这个对象是否是某个类的实例。需要注意的是,inatanceof是判断前面的对象是否是后面类(对象)的实例,并不表示两者的继承。所以console.log(SubCass instanceof SuperClass)会打印出false,而console.log(SubClass.prototype instanceof SuperClass)才会打印出true。

类式继承有两个缺点

1.子类通过其原型prototype对父类实例化,继承了父类。但当父类中的共有属性是引用类型时,会在子类中被所有的实例共用,如此在一个子类实例中更改从父类中继承过来的公有属性时,会影响到其他子类。

[javascript]  view plain  copy
  1. function SuperClass() {  
  2.   this.book = ['javascript','html','css'];  
  3. }  
  4. function SubClass() {}  
  5. SubClass.prototype = new SuperClass();  
  6. var ins1 = new SubClass();  
  7. var ins2 = new SubClass();  
  8. console.log(ins1.book);  
  9. console.log(ins2.book);  
  10. ins1.book.push("Node");  
  11. console.log("after");  
  12. //引用类型在子类中被所有实例共用  
  13. console.log(ins1.book);//[ 'javascript', 'html', 'css', 'Node' ]  
  14. console.log(ins2.book);//[ 'javascript', 'html', 'css', 'Node' ]  

2.由于子类是通过原型prototype实例化父类实现继承的,所以在创建父类的时候,无法向父类传递参数,因而在实例化父类的时候无法对父类构造函数内的属性初始化。


二.构造函数式继承

简介:通过在子类的构造函数中执行一次父类的构造函数实现。

[javascript]  view plain  copy
  1. //2.构造函数式继承  
  2. //声明父类  
  3. function SuperClass(id) {  
  4.   this.book = ['javascript','html','css'];//引用类型共有属性  
  5.   this.id = id;//值类型公有属性  
  6. }  
  7. //父类声明原型方法  
  8. SuperClass.prototype.showBooks = function() {  
  9.   console.log(this.books);  
  10. }  
  11. //声明子类  
  12. function SubClass(id) {  
  13.   //继承父类  
  14.   SuperClass.call(this,id);  
  15. }  
  16. //测试  
  17. var ins1 = new SubClass(1);  
  18. var ins2 = new SubClass(2);  
  19. ins1.book.push("Node");  
  20. console.log(ins1.id);//1  
  21. console.log(ins1.book);//['javascript', 'html', 'css', 'Node']  
  22. console.log(ins2.id);//2  
  23. console.log(ins2.book);//['javascript', 'html', 'css']  
  24.   
  25. ins1.showBooks();//TypeError: ins1.showBooks is not a function  
SuperClass.call(this,id)是构造函数式继承的中心。call方法可以改变函数的作用环境,在子类中调用这个方法就是将子类中的变量在父类中执行,父类中给this绑定属性,因而子类继承了父类的共有属性。

缺点:

这种类型的继承没有涉及原型prototype,所以父类的原型方法不会被子类继承。如想被子类继承就必须放在构造函数中,这样创造的每个实例都会单独拥有一份而不能共用,违背了代码复用原则。

三.组合式继承

简介:综合以上两种模式的优点,在子类原型上实例化父类,在子类构造函数中执行一遍父类的构造函数。这样融合了类式继承和构造式继承的优点,过滤了缺点。

[javascript]  view plain  copy
  1. //3.组合式继承  
  2. function SuperClass(name) {  
  3.   this.name = name;  
  4.   this.book = ['javascript','html','css'];  
  5. }  
  6. SuperClass.prototype.getName = function () {  
  7.   console.log(this.name);  
  8. };  
  9. function SubClass(name,time) {  
  10.   //构造函数式继承,继承父类name属性  
  11.   SuperClass.call(this,name);  
  12.   this.time = time;  
  13. }  
  14. //类式继承,子类原型继承  
  15. SubClass.prototype = new SuperClass();  
  16. //子类原型方法  
  17. SubClass.prototype.getTime = function () {  
  18.   console.log(this.time);  
  19. };  
  20. //测试  
  21. var ins1 = new SubClass('Node',2016);  
  22. ins1.book.push("Node");  
  23. console.log(ins1.book);  
  24. ins1.getName();  
  25. ins1.getTime();  
  26.   
  27. var ins2 = new SubClass('React',2015);  
  28. console.log(ins2.book);  
  29. ins2.getName();  
  30. ins2.getTime();  
缺点:

父类的构造函数执行了两遍:一次在子类的构造函数中call方法执行一遍,一次在子类原型实例化父类的时候执行一遍。


四.原型式继承

简介:对类式继承的封装,过渡对象相当于子类。

[javascript]  view plain  copy
  1. //4.原型式继承  
  2. function inheritObject(o) {  
  3.   //声明过渡函数对象  
  4.   function F() {}  
  5.   //过渡对象的原型继承父类  
  6.   F.prototype = o;  
  7.   return new F();  
  8. }  
  9. //测试  
  10. var book = {  
  11.   name : "javascript",  
  12.   book : ['js','css']  
  13. };  
  14. var newbook = inheritObject(book);  
  15. newbook.name = "ajax";  
  16. newbook.book.push("Node");  
  17. var otherbook = inheritObject(book);  
  18. otherbook.name = "xml";  
  19. otherbook.book.push("React");  
  20. console.log(newbook.name);//ajax  
  21. console.log(newbook.book);//[ 'js', 'css', 'Node', 'React' ]  
  22. console.log(otherbook.name);//xml  
  23. console.log(otherbook.book);//[ 'js', 'css', 'Node', 'React' ]  
  24. console.log(book.name);//javascript  
  25. console.log(book.book);//[ 'js', 'css', 'Node', 'React' ]  
缺点:

和类式继承一样,父类对象的引用类型值被共用。

五.寄生式继承

简介:寄生式继承其实是对原型继承的第二次封装,并且在第二次封装的过程中对继承的对象进行了拓展。

[javascript]  view plain  copy
  1. //5.寄生式继承  
  2. function inheritObject(o) {  
  3.   //声明过渡函数对象  
  4.   function F() {}  
  5.   //过渡对象的原型继承父类  
  6.   F.prototype = o;  
  7.   return new F();  
  8. }  
  9. //声明基对象  
  10. var book = {  
  11.   name : "javascript",  
  12.   book : ['js','css']  
  13. };  
  14. function createBook(obj) {  
  15.   //通过原型继承方式创建新对象  
  16.   var o = new inheritObject(obj);  
  17.   //拓展新对象  
  18.   o.getName = function() {  
  19.     console.log(name);  
  20.   }  
  21.   //返回拓展后的新对象  
  22.   return o;  
  23. }  
  24.   
  25. var newbook = createBook(book);  
  26. newbook.name = "ajax";  
  27. newbook.book.push("Node");  
  28. var otherbook = createBook(book);  
  29. otherbook.name = "xml";  
  30. otherbook.book.push("React");  
  31. console.log(newbook.name);//ajax  
  32. console.log(newbook.book);//[ 'js', 'css', 'Node', 'React' ]  
  33. console.log(otherbook.name);//xml  
  34. console.log(otherbook.book);//[ 'js', 'css', 'Node', 'React' ]  
  35. console.log(book.name);//javascript  
  36. console.log(book.book);//[ 'js', 'css', 'Node', 'React' ]  

六.寄生组合式继承

简介:寄生式继承和构造函数式继承结合使用。

[javascript]  view plain  copy
  1. //寄生组合式继承  
  2. function inheritObject(o) {  
  3.   //声明过渡函数对象  
  4.   function F() {}  
  5.   //过渡对象的原型继承父类  
  6.   F.prototype = o;  
  7.   return new F();  
  8. }  
  9. //寄生式继承 继承原型  
  10. function inheritPrototype(subClass,superClass) {  
  11.   //复制一份父类的原型副本保存在变量中  
  12.   var p = inheritObject(superClass.prototype);  
  13.   //修正因为重写子类原型导致子类的constructor属性被修改  
  14.   p.constructor = subClass;  
  15.   //设置子类的原型  
  16.   subClass.prototype = p;  
  17. }  
  18. function SuperClass(name) {  
  19.   this.name = name;  
  20.   this.colors = ["red","blue","green"];  
  21. }  
  22. //定义父类原型方法  
  23. SuperClass.prototype.getName = function() {  
  24.   console.log(this.name);  
  25. }  
  26. function SubClass(name,time) {  
  27.   SuperClass.call(this,name);  
  28.   this.time = time;  
  29. }  
  30. //寄生式继承父类原型  
  31. inheritPrototype(SubClass,SuperClass);  
  32. //子类新增原型方法  
  33. SubClass.prototype.getTime = function() {  
  34.   console.log(this.time);  
  35. }  
  36. //测试  
  37. var ins1 = new SubClass("js",2014);  
  38. var ins2 = new SubClass("css",2015);  
  39. ins1.colors.push("black");  
  40. console.log(ins1.colors);  
  41. console.log(ins2.colors);  
  42. ins2.getName();  
  43. ins2.getTime();  
  44. from http://blog.csdn.net/qq_31280709/article/details/52137086

版本二、大牛的个人总结贴

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>继承</title>
</head>

<body>
  <script>
  /**
   * [使用对象冒充实现继承(多继承)]
   */
  function Parent(name) {
    this.name = name
    this.sayName = function() {
      console.log(this.name)
    }
  }
  var iParent = new Parent('james')
  iParent.sayName()

  function Child(name) {
    this.parent = Parent
    this.parent(name)
    delete this.parent
    this.saySome = function() {
      console.log('my name: ' + this.name)
      this.sayName()
    }
  }

  var iChild = new Child('kobe')
  iChild.saySome()
  console.log(iChild.constructor)

  /** 
   * Call/Apply方法改变函数上下文实现继承(该种方式不能继承原型链,若想继承原型链,则采用混合模式)
   * 实现原理:改变函数内部的函数上下文this,使它指向传入函数的具体对象
   */
  function Parent(firstname) {
    this.fname = firstname;
    this.age = 28;
    this.sayAge = function() {
      console.log(this.age);
    }
  }

  function Child(firstname) {
    Parent.call(this, firstname); // 将this传给父构造函数
    //Parent.apply(this, [firstname]); // 与call作用相同
    this.saySomeThing = function() {
      console.log(this.fname);
      this.sayAge();
    }
    this.getName = function() {
      return firstname;
    }
  }
  var myChild = new Child('Lee');
  myChild.saySomeThing();
  console.log(myChild.constructor)
  myChild.constructor == Child; // true

  /**
   * 原型链实现继承
   * 实现原理:使子类原型对象指向父类的实例以实现继承,即重写类的原型,弊端是不能直接实现多继承
   */
  function Parent() {
    this.sayAge = function() {
      console.log(this.age);
    }
  }

  function Child(firstname) {
    this.fname = firstname;
    this.age = 28;
    this.saySomeThing = function() {
      console.log(this.fname);
      this.sayAge();
    }
  }
  Child.prototype = new Parent();
  var myChild = new Child('Lee');
  myChild.saySomeThing(); // Lee 28

  /**
   * 混合模式
   */
  function Parent() {
    this.sayAge = function() {
      console.log(this.age);
    }
  }
  Parent.prototype.sayParent = function() {
    alert('This is parent!');
  }

  function Child(firstname) {
    Parent.call(this);
    this.fname = firstname;
    this.age = 28;
    this.saySomeThing = function() {
      console.log(this.fname);
      this.sayAge();
    }
  }
  Child.prototype = new Parent();
  var myChild = new Child('Lee');
  myChild.saySomeThing();
  myChild.sayParent();
  console.log(myChild.constructor)

  function Parent(hello) {
    this.hello = hello;
  }
  Parent.prototype.sayHello = function() {
    alert(this.hello);
  }

  function Child(hello, world) {
    Parent.call(this, hello); //将父类的属性继承过来 
    this.world = world; //新增一些属性 
  }
  Child.prototype = new Parent(); //将父类的方法继承过来 
  Child.prototype.sayWorld = function() { //新增一些方法 
    alert(this.world);
  }
  var c = new Child("zhangsan", "lisi");
  c.sayHello();
  c.sayWorld();

  /**
   * es6继承
   */
  class Animal {
    //构造函数
    constructor(props) {
      this.name = props.name || '未知';
    }

    eat() {
      alert(this.name + "在吃东西...");
    }
  }

  //class继承
  class Bird extends Animal {
    //构造函数
    constructor(props) {
      //调用实现父类的构造函数
      super(props);
      this.type = props.type || "未知";
    }

    fly() {
      alert(this.name + "在飞...");
    }
  }
  var myBird = new Bird({
    name: '鹦鹉'
  })
  myBird.eat()
  myBird.fly()
  </script>
</body>

</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值