博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JS继承的实现方式 原型 原型链 prototype和_proto_的区别
阅读量:5124 次
发布时间:2019-06-13

本文共 6936 字,大约阅读时间需要 23 分钟。

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]]链不为空的话,就会继续查找下去。

转载于:https://www.cnblogs.com/hixxcom/p/7172984.html

你可能感兴趣的文章
10行代码实现小程序支付功能!丨实战
查看>>
用小程序·云开发两天搭建mini论坛丨实战
查看>>
巧用小程序·云开发实现邮件发送功能丨实战
查看>>
关于云开发新服务“实时数据推送”,你需要了解的全在这了!
查看>>
基于小程序·云开发构建高考查分小程序丨实战
查看>>
用小程序·云开发打造功能全面的博客小程序丨实战
查看>>
借助云开发轻松实现后台数据批量导出丨实战
查看>>
借助实时数据推送快速制作在线对战五子棋小游戏丨实战
查看>>
云开发的数据库权限机制解读丨云开发101
查看>>
云开发0基础训练营第二期热力来袭!
查看>>
【js监听报错】页面监听js报错问题
查看>>
【vue开发】vue导出Excel表格教程&demo
查看>>
【微信网页直接下载app】微信跳转-微信浏览器中直接唤起本地浏览器和App
查看>>
【vue开发】 父组件传值给子组件时 ,watch props 监听不到解决方案
查看>>
【vue开发】vue插件的install方法
查看>>
powershell some check
查看>>
delphi 参数化sql
查看>>
应该算是在说 delphi 的日志框架吧
查看>>
Erlang注册进程名称-tut16.erl
查看>>
Erlang消息传递-tut15.erl
查看>>