JS继承的实现方式 原型 原型链 prototype和_proto_的区别

7.13 JS继承的实现方法

标签(空格分隔): JS继承的实现方法


JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。

js继承的实现方式

js继承的实现方式有6种

1.原型链继承
2.构造继承
3.实例继承
4.拷贝继承
5.组合继承
6.寄生组合继承

既然要实现继承,那么首先我们得有一个父类。代码如下:

    //定义一个动物类
    function Animal(name){
        //属性
        this.name = name || 'Animal';
        //实例方法
        this.sleep = function(){
            console.log(this.name + '正在睡觉');
        }
    }
    //原型方法
    Animal.prototype.eat = function(food){
        console.log(this.name + '正在吃:' +food);
    }

1、原型链继承

核心:将父类的实例作为子类的原型

    function Cat(){
    }
    Cat.prototype = new Animal();
    Cat.prototype.name = 'cat';

    //Test Code
    var cat = new Cat();
    console.log(cat.name); //cat
    console.log(cat.eat('fish')); //cat正在吃:fish
    console.log(cat.sleep());  //cat正在睡觉
    console.log(cat instanceof Animal); //true
    console.log(cat instanceof Cat); //true

特点:

1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例

2.父类新增的原型方法/原型属性,子类都能访问到

3.简单,易于实现

缺点:

1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

2.无法实现多继承

3.来自原型对象的引用属性是所有实例共享的

4.创建子类实例时,无法向父类构造函数传参

需要注意的两点:

1.别忘记默认的原型:

所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的
所有的函数的默认原型都是Object的实例,因此默认的原型都会包含一个内部指针,指向Object.prototype。这也正是所有的自定义类型都会继承toString(),valueOf()等默认方法的根本原因。

2.确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系。

  • 使用instanceof操作符
    只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true.(格式:alert(instance instanceof Object);//true)

instanceof 用于判断一个变量是否某个对象的实例

  • 使用isPrototypeOf()方法
    同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true.(格式:alert(Object.prototype.isPrototypeOf(instance));//true)

3.谨慎地定义方法

子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后。例子如下:

 function SuperType(){
        this.prototype = true;
    }

 SuperType.prototype.getSuperValue = function(){
        return this.property;
    };

 function SubType(){
        this.subproperty = false;
    }

 //继承了SuperType
 SubType.prototype = new SuperType();

 //添加新方法
 SubType.prototype.getSubValue = function(){
        return this.subproperty;
    };

 //重写超类型中的方法

 SubType.prototype.getSuperValue = function(){
        return false;
    };

 var instance = new SubType();
 alert(instance.getSuperValue());

不能使用字面量创建原型方法,因为这样就会重写原型链。例子如下:

 function SuperType(){
        this.property = true;
    }

    SuperType.prototype.getSuperValue = function(){
        return this.property;
    };
    function SubType(){
        this.subproperty = false;
    }
    //继承了SuperType
    SubType.prototype = new SuperType();

    //使用字面量添加新方法,会导致上一行代码无效

    SubType.prototype = {
        getSubValue : function(){
            return this.subproperty;
        },
        someOtherMethod : function(){
            return false;
        }

    };

    var instance  = new SubType();
    alert(instance.getSuperValue());//error;

2、构造函数

核心:使用父类的构造函数来增强子类的实例,等于是复制父类的实例属性给子类(没用到原型)

    function Cat(){
        Animal.call(this);//继承了Animal
        this.name = name || 'Tom';
    }
    //Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal);
    console.log(cat instanceof Cat);

特点:

1.解决了1中,子类实例共享父类引用属性的实例

2.创建子类实例时,可以向父类传递参数

3.可以实现多继承(call多个父类对象)

举例:

输入图片说明

apply举例:

输入图片说明

倒数第二句:通过apply方法,改变了Parent的指向,此时Parent的新指向为Child的实例,并且调用child的getName()方法。

去掉最后一句,Parent.apply(child,[child.getName()]);调用的顺序为:Child(‘张’)、getName()、Parent(‘张’),由于没人任何console.log,所以不会打印任何信息、

apply实现原理:改变函数内部的函数上下文this,使它指向传入函数的具体对象

缺点:

1.实例并不是父类的实例,只是子类的实例

2.只能继承父类的实例属性和方法,不能继承原型属性方法

3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

注意:

传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
例子:

    function SuperType(name){
        this.name = name;
    }

    function SubType(){
        //继承了SuperType,同时还传递了参数
        SuperType.call(this,"Nicholas");

        //实例属性
        this.age = 29;
    }
    var instance = new SubType();
    alert(instance.name);//"Nicholas"
    alert(instance.age);//29

3.实例继承

核心:为父类实例添加新特性,作为子类实例返回。

    function Cat(name){
        var instance = new Animal();
        instance.name = name || 'Tom';
        return instance;
    }

    //Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal);//true
    console.log(cat instanceof Cat);//false

特点:

  1. 不限制调用方式,不管是new 子类()还是 子类(),返回的对象都具有相同的效果
  2. 实例是父类的实例,不是子类的实例
  3. 不支持多继承

4.拷贝继承

    function Cat(){
        var animal = new Animal();
        for(var p in animal){
            Cat.prototype[p] = animal[p];
        }
        Cat.prototype.name = name || 'Tom';
    }

    //Test Code

    var cat = new Cat();
    console.log(cat.name); //Tom
    console.log(cat.sleep()); //Tom正在睡觉
    console.log(cat instanceof Animal); //false
    console.log(cat instanceof Cat);//true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5.组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的有点,然后通过将父类实例作为子类原型,实现函数复用。

        function Animal(name){
        //属性
        this.name = name || 'Animal';
        //实例方法
        this.sleep = function(){
            console.log(this.name + '正在睡觉');
        }
    }

    function Cat(name){
        Animal.call(this);
        this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();

    //Test Code
    var cat = new Cat();
    console.log(cat.name);//Tom
    console.log(cat.sleep());//Tom正在睡觉
    console.log(cat instanceof Animal);//true
    console.log(cat instanceof Cat);//true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  1. 函数可复用

缺点:

1.调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6.寄生组合式继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点

    function Cat(name){
        Animal.call(this);
        this.name = name || 'Tom';
    }
    (function(){
        //创建一个没有实例方法的类
        var Super = function(){};
        Super.prototype = Animal.prototype;
        //将实例作为子类的原型
        Cat.prototype = new Super();
    })();

    //Test Code
    var cat = new Cat();
    console.log(cat.name);//Tom
    console.log(cat.sleep());//Tom 正在睡觉
    console.log(cat instanceof Animal);//true
    console.log(cat instanceof Cat);//true

特点:堪称完美

缺点:实现较为复杂

原型链

prototype和__proto__的概念

prototype是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。

__proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,__proto__是对象的内置属性),是JS内部使用寻找原型链的属性。

用chrome和FF都可以访问到对象的__proto__属性,IE不可以。

拓展

我们可以直接获取一个对象的[[prototype]]链。

在ES5中,标准的方法是:Object.getPrototypeOf(a);

可以验证一下,这个对象引用和我们想象的是不是一样的:

Object.getprototypeOf(a) === Foo.prototype;//true

绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部[[prototype]]属性:

a._proto_ === Foo.prototype;//true

这里就要使用__proto__属性来链接到原型(也就是Person.prototype)进行查找。最终在原型上找到了age属性。

原型

JS中的对象有一种特殊的[[prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]属性都会被赋一个非空的值。

  var myObject = {
    a : 2;
  };
  myObject.a;//2

[[prototype]]引用的用处:
当你试图引用对象的属性时会触发[[Get]]操作,比如myObject.a
对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。

但是如果a不在myObject中,就需要使用对象的[[prototype]]链。
对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[prototype]]链。

 var anotherObject = {
    a:2;
 };
 //创建一个关联到anotherObject的对象
 
 var myObject = Object.create(anotherObject);
 myObject.a;//2

现在myObject对象的[[prototype]]关联到了anotherObject.显然myObject.a并不存在,但是尽管如此,属性访问仍然成功的(在anotherObject中)找到了值2.
但是,如果anotherObject中也找不到a并且[[prototype]]链不为空的话,就会继续查找下去。

posted @ 2017-07-14 22:28  xiexing  阅读(466)  评论(0编辑  收藏  举报