1.原型链

基本思想:利用原型链让一个引用类型继承另一个引用类型的属性和方法。 让原型对象(B.prototype)等于另一个类型的实例(new A()), 即B.prototype = new A() ,则B继承了A

构造函数、原型、实例的关系:

· 每个构造函数(function A)都有一个原型对象(A.prototype);

· 这个原型对象都包含一个指向构造函数的指针(A.prototype.constructor = A);

· 而实例都包含一个纸箱原型对象的内部指针(instance._proto_ = A.prototype);

例:

  1. function SuperType(){
  2. this.property = true;
  3. }
  4. SuperType.prototype.getSuperValue = function(){
  5. return this.property;
  6. }
  7. function SubType(){
  8. this.subproperty = false;
  9. }
  10. SubType.prototype = new SuperType();
  11. SubType.prototype.getSubValue = function(){
  12. return this.subproperty;
  13. }
  14. //重写父类的方法
  15. SubType.prototype.getSuperValue = function(){
  16. return false
  17. }
  18. var instance = new SubType();
  19. var instance1 = new SuperType();
  20. alert(instance.getSuperValue()); //false 调用重写的方法
  21. alert(instance1.getSuperValue()); //true 父类的实例调用的还是原来的方法

缺点1

包含引用类型值(object,array,function)的原型属性会被所有实例共享。例:

  1. function SuperType(){
  2. this.colors = ['red','blue','green'];
  3. }
  4. function SubType(){
  5. }
  6. SubType.prototype = new SuperType();
  7. var instance1 = new SubType();
  8. instance1.colors.push('black');
  9. alert(instance1.colors); //red,blue,green,black
  10. var instance2 = new SubType();
  11. alert(instance2.colors); //red,blue,green,black 《---!!!
  12. var superinstance = new SuperType();
  13. alert(superinstance.colors); //red,blue,green

SuperType的每一个实例都会有各自包含自己数组的colors属性。当SubType通过原型链继承了SuperType后,SubType.prototype就变成了SuperType的一个实例,因此它也有一个自己的colors属性(相当于SubType.prototype.colors),这个colors属性是在SubType的原型上,所以会被SubType的实例共享,所以instance1改变了这个值的时候,instance2的colors属性也跟着改变了。

缺点2

在创建子类型的实例时,不能向父类的构造函数中传递参数。(实际上是没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数)。

鉴于以上这两个问题,在实践中很少会单独使用原型链。

2.借用构造函数(apply() 或 call())

基本思想:在子类型构造函数的内部调用父类型构造函数。

  1. function SuperType(){
  2. this.colors = ['red','blue','green'];
  3. }
  4. function SubType(){
  5. SuperType.call(this); //改变SuperType中的this的指向,改为SubType
  6. }
  7. var instance1 = new SubType();
  8. instance1.colors.push('black');
  9. alert(instance1.colors); //red,blue,green,black
  10. var instance2 = new SubType();
  11. alert(instance2.colors); //red,blue,green 因为colors并不是prototype上的引用类型的属性,所以没有被共享
  12. var superinstance = new SuperType();
  13. alert(superinstance.colors); //red,blue,green

通过call方法,实际上是在SubType实例的环境中调用了SuperType构造函数,使得SubType对象上执行SuperType()函数中定义的所有对象初始化代码,所以SubType的每个实例都会有自己的colors属性的副本。

优点

可以在子类型构造函数中向父类型构造函数传递参数。例:

  1. function SuperType(name){
  2. this.name = name;
  3. }
  4. function SubType(){
  5. SuperType.call(this,'lee');
  6. //实例属性
  7. this.age = 22;
  8. }
  9. var instance = new SubType();
  10. alert(instance.name); //lee
  11. alert(instance.age); //22

因为是每个实例都各自调用SuperType中的构造函数进行初始化,所以不会传递参数不会互相影响。

缺点

1.构造函数模式的问题:方法都在构造函数中定义,无法进行函数的复用。

2.在父类型中定义的方法,对子类型而言是不可见的,会导致所有类型都只能使用构造函数模式。

鉴于以上这两个问题,在实践中也很少单独使用借用构造函数的方法。

3.组合式继承(原型链 + 借用构造函数)

基本思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既可以通过在原型上定义方法实现函数复用,又能保证每个实例否有自己的属性。

  1. function SuperType(name){
  2. this.name = name;
  3. this.colors = ['red','blue','green'];
  4. }
  5. SuperType.ptototype.getName = function(){
  6. alert(this.name);
  7. }
  8. function SubType(name,age){
  9. //继承属性
  10. SuperType.call(this,name);
  11. this.age = age;
  12. }
  13. //继承方法
  14. SubType.prototype = new SuperType();
  15. SubType.prototype.constructor = SubType; //弥补因上面修改了prototype而失去的默认constructor属性,这句语句改正前,constructor的值被改为了SuperType
  16. SubType.prototype.getAge = function(){
  17. alert(this.age);
  18. }
  19. var instance1 = new SubType('lee',22);
  20. instance1.colors.push('black');
  21. alert(instance1.colors); //red,blue,green,black
  22. instance1.getName(); //lee
  23. instance1.getAge(); //22
  24. var instance2 = new SubType('mone',23);
  25. alert(instance2.colors); //red,blue,green
  26. instance2.getName(); //mone
  27. instance2.getAge(); //23

自问自答:

问:为什么结合了两种继承方法后,不必再担心原型的共享属性,明明还是有用原型继承的方法(SubType.prototype = new SuperType();)?

答:这里SubType.prototype在new SuperType()之后仍然会使SubType的实例有共享的colors,但是在调用了call来继承属性时,已经等同于给每个实例都赋予了一个局部变量的colors,当我们修改和显示实例colors时,先查找了局部的colors,此时该变量存在,就修改和输出,不会再去查找和修改原型链上共享的colors,所以每个实例对colors的修改不会影响其他实例。

缺点

无论在什么情况下,都会调用两次父类的构造函数。

(1.SuperType.call(this,name); 2.SubType.prototype = new SuperType();

子类型最终会含有父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

4.原型式继承 (适用于对象,非函数式)

基本思想:原型可以基于已有的对象创建对象,同时还不必因此创建自定义类型(SuperType)。从本质上,是通过对已有对象进行浅复制来实现继承。

核心函数:

  1. function object(o){
  2. function F(){};
  3. F.prototype = o;
  4. return new F();
  5. }
  1. var person = {
  2. name:'lee',
  3. friends:['a','b','c']
  4. }
  5. var personA = object(person);
  6. personA.name = 'nakai'; //nakai
  7. personA.friends.push('d'); //a,b,c,d
  8. console.log(person.friends); //a,b,c,d
  9. var personB = object(person);
  10. personB.name = 'kimura'; //kimura
  11. personB.friends.push('e'); //a,b,c,d,e
  12. console.log(person.friends); //a,b,c,d,e
  13. console.log(personA.friends); //a,b,c,d,e

ES5通过新增Object.create()方法规范了原型式继承。

var personA = Object.create(person);

var personB = Object.create(person);

在不必兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

缺点

包含引用类型值得属性始终都会共享相应的值,就像使用原型模式一样。

5.寄生式继承 (适用于对象,非函数式)

基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回该对象。

  1. function createAnother(original){
  2. //通过调用函数创建一个新对象 ,这里的object是原型式继承的核心函数.
  3. //但是其实任何能够返回新对象的函数都可以。
  4. var clone = object(original);
  5. clone.sayHi = function(){
  6. alert('hi');
  7. }
  8. return clone;
  9. }
  10. //使用
  11. var person = {
  12. name:'lee',
  13. friends:['a','b','c']
  14. }
  15. var newPerson = createAnother(person);
  16. newPerson.sayHi(); //hi

基于person返回了一个新对象——newPerson,新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

6.寄生组合式继承 (借用构造函数 + 寄生式继承 + 原型链)

基本思想:不必为了指定子类型的原型而调用父类型的构造函数(SubType.prototype = new SuperType();),我们所需要的其实就只是父类型原型的一个副本而已。本质上,就是使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型。

核心函数:

  1. //创建一个SuperType.prototype的副本,并赋值给SubType.prototype。代替调用SuperType的构造函数
  2. function inheritPrototype(SubType,SuperType){
  3. var prototype = Object(SuperType.prototype);
  4. prototype.constructor = SubType;
  5. SubType.prototype = prototype;
  6. }
  1. function SuperType(name){
  2. this.name = name;
  3. this.colors = ['red','blue','green'];
  4. }
  5. SuperType.ptototype.getName = function(){
  6. alert(this.name);
  7. }
  8. function SubType(name,age){
  9. //继承属性
  10. SuperType.call(this,name);
  11. this.age = age;
  12. }
  13. inheritPrototype(SubType,SuperType); //《--!!!
  14. SubType.prototype.getAge = function(){
  15. alert(this.age)
  16. }

此时,因为没有了SubType.prototype = new SuperType();所以SubType的实例都没有了共享属性colors。

优点

高效,只调用一次SuperType构造函数,(改良了组合式继承要无论什么情况都要调用两次SuperType的构造函数),并且因此避免了在SubType.prototype上面创建不必要、多余的属性,与此同时,原型链保持不变。可以说,寄生组合式继承是引用类型最理想的继承范式。(如上面的例子,引用类型值colors不再被实例共享)

【js】实现继承的6种方法的更多相关文章

  1. JS 原型继承的几种方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. JS实现继承的几种方法

    父类: // 定义一个动物的类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = fu ...

  3. JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)

    JavaScript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script t ...

  4. js对象之间的"继承"的五种方法

    今天要介绍的是,对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = & ...

  5. js实现继承的5种方式 (笔记)

    js实现继承的5种方式 以下 均为 ES5 的写法: js是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承 ...

  6. ajax请求参数中含有特殊字符"#"的问题 (另附上js编码解码的几种方法)

    使用ajax向后台提交的时候 由于参数中含有#  默认会被截断 只保留#之前的字符  json格式的字符串则不会被请求到后台的action 可以使用encodeURIComponent在前台进行编码, ...

  7. 实现JS继承的几种方法

    总的来说,JS的继承大体上分为两种:借用构造函数方式和原型方式 首先,我们来看看借用构造函数方式的几种做法: //方式一function Person(name, sex){ this.name = ...

  8. js实现继承的两种方式

    这是面试时面试官会经常问到问题: js的继承方式大致可分为两种:对象冒充和原型方式: 一.先说对象冒充,又可分为3种:临时属性方式.call().apply(): 1.临时属性方式: 当构造对象son ...

  9. js(javascript) 继承的5种实现方式

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt240 js继承有5种实现方式:1.继承第一种方式:对象冒充  functio ...

  10. JavaScript面向对象(三)——继承与闭包、JS实现继承的三种方式

      前  言 JRedu 在之前的两篇博客中,我们详细探讨了JavaScript OOP中的各种知识点(JS OOP基础与JS 中This指向详解 . 成员属性.静态属性.原型属性与JS原型链).今天 ...

随机推荐

  1. 并发编程之 CopyOnWriteArrayList 源码剖析

    前言 ArrayList 是一个不安全的容器,在多线程调用 add 方法的时候会出现 ArrayIndexOutOfBoundsException 异常,而 Vector 虽然安全,但由于其 add ...

  2. [编程] TCP协议概述

    TCP 协议概述 1.TCP提供一种面向连接的.可靠的字节流服务. 2.两个应用程序通过TCP连接交换8bit字节构成的字节流. 3.每个TCP首部都包含源端和目的端的端口号,用于寻找发端和收端应用进 ...

  3. Table转换成实体、Table转换成实体集合(可转换成对象和值类型)

    /// <summary> /// Table转换成实体 /// </summary> /// <typeparam name="T">< ...

  4. unity中 TextMeshPro 的常用标签

    这个第二和第三个写反了. 例子10中的123标签需要用到另一个字体,详情看 TextMeshPro 的官方示例10.

  5. MyCat安装配置

    Mycat : 数据库分库分表中间件 http://www.mycat.io/ mycat运行需要JVM,所以先安装java环境,JDK1.7以上.数据库采用mysql5.7,或者8.0 下载 下载地 ...

  6. 乐字节-Java8新特性-接口默认方法之Stream流(下)

    接上一篇:<Java8新特性之stream>,下面继续接着讲Stream 5.流的中间操作 常见的流的中间操作,归为以下三大类:筛选和切片流操作.元素映射操作.元素排序操作: 操作 描述 ...

  7. linux安装MySQL5.7记录

    目录 linux安装MySQL5.7记录 1. 在根目录下创建文件夹/software和数据库数据文件/data/mysql 2. 从官网下载相应的MySQL版本 3. 解压并移动到/software ...

  8. .net解决Xss攻击

    首先要明白什么是Xss攻击 XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中.比如这些代码包括HTML代码和客户端脚本.攻击者利用XSS漏 ...

  9. Application作用域实现:当用户重复登录时,挤掉原来的用户

    Application作用域实现:当用户重复登录时,挤掉原来的用户 一.实现思想 1.application(ServletContext)是保存在服务器端的作用域,我们在application中保存 ...

  10. 【代码笔记】iOS-自定义选择框(高底强弱)

    一,效果图 二,代码. ViewController.h #import <UIKit/UIKit.h> @interface ViewController : UIViewControl ...