一、浅谈原型

首先我们要知道创建对象的方法有两种:

1.通过字面量的方式直接创建


    var obj = {
        name:'baimao',
        age:21
    }

2.通过构造函数创建对象

    function People(name,age) {
        this.name = name;
        this.age = age;
    }

  而当我们使用构造函数创建对象时,对对象的属性或者方法的设置是通过传参的方式进行的,考虑一个问题,公有的属性或者方法如果仍在构造函数中通过传参设置,每次new出来的新的对象都保留有一份构造函数中的信息,且有部分信息时相同并不会更改的,比如,人生来就有四肢,能吃饭能说话等。这样就浪费了内存,相同的信息在内存中存在了很多份(每通过构造函数new一个实例就复刻了一份相同的东西)。

  而原型就是为了解决相同信息的复用问题,使用原型可以使得所有通过构造函数new出来的对象的原型指向的是同一个地址空间,避免了内存上的浪费,也使得继承概念更合理。

  这里面要指明的是,放在原型中的属性和方法在内存中只存在一份,所有通过该构造函数new出来的对象都使用的是这一份原型。所以js中的原型是引用原型。

来声明一下对象的 __proto__ 与构造函数的prototype区别。对象都存在__proto__ 属性,而通过调用构造函数new出来的对象,其__proto__ 属性是指向构造函数的prototype属性的,而构造函数的prototype属性本身也是一个对象,仍存在 __proto__ 属性,这将会引入后续的原型链的问题,在后面会介绍。

3.为构造函数设置原型的方法

    (1)     People.prototype.arms = 2;
            People.prototype.eat = function() {
                console.log(this.name + '会吃饭');
            }
    
    (2)     var people = new People('baimao',21);
            people.__proto__.arms = 2;
            people.__proto__.eat = function() {
                console.log(this.name + '会吃饭');
            }

二.js中的继承

  我们都理解的是js中实际是不存在类的概念的,所谓继承只是通过构造函数的继承和构造函数中的原型的继承来实现的。但我们下面直接用类的概念来讲更容易理解,假设js中存在类吧

1.构造函数的继承

  (1)首先介绍构造函数的继承

     function People(name,age) {
        this.name = name;
        this.age = age;
    }
    People.prototype.eat = function () {
        console.log(this.name + '正在吃...');
    }
    function Student(name,age) {
        People.call(this,name,age);
        //People.apply(this,arguments);
    }
    console.log(new Student('baimao',21));

  通过上图我们可以看到通过call或者apply的方法调用父类的构造函数是可以使用父类的构造函数对子类实例进行赋值的,总结一下,也就是说通过call,apply方法可以使用父类的构造函数。但是这种方法有局限性,细心的人可能会观察到,使用call或者apply方法继承到的只有父类的构造方法中的属性或者方法,父类原型中的属性和方法不会被继承,为了解决这一问题,我们可以通过如下方式:

2.子类原型的继承

    方法一:
    Student.prototype = Object.create(People.prototype);
    //在这里之所以要自己添加子类原型中的构造函数是因为我们直接使用
    //Object.create(People.prototype)创建一个对象出来给子类的原型赋值,会
    //覆盖掉子类原有的构造函数,所以需要手动设置
    Student.prototype.constructor = Student;

    方法二:
    Student.prototype.__proto__ = People.prototype;

    var student = new Student('baimao',21);
    student.eat();
    console.log(student);

  实际中我们可以发现,通过上述两种方式是在子类构造函数的原型的原型中添加方法,当我们通过new子类对象并调用方法时,该方法会先在子类自己的构造函数中找,找不到则在子类的原型中找,再找不到就去子类原型的原型中找,直到找到为止,否则报错,该方法未定义 not defined

即为原型链的概念:

关于原型链:
        先在对象自身去找(构造函数中找),找到后调用即可;
        如果找不到,就去自身原型中找,找到调用即可;
        如果还找不到,就去原型的原型中找,找到调用即可...
        直到找到Object的原型,还是找不到,就抛出异常

  通过继承父类而得到的子类对象,如果继续在子类的原型中添加父类中存在的属性或者方法,意义上为重写父类的方法,但实际意义上只是在子类的原型中添加了一个方法,并没有真正覆盖父类的方法,通过子类构造函数的原型的原型中还是可以查看到父类原型的属性或者方法.

关于isPrototypeOf 和 hasOwnProperty()

  1. isPrototypeOf

    判断原型和对象之间的关系

    isPrototypeOf 判断原型和对象之间的关系:判断对象是否存在于另一个对象的原型链中
  2. hasOwnProperty()

    用于指示一个对象自身(不包括原型链)是否具有指定名称的属性。 如果有,返回true,否则返回false。
  3. in

    可以查看原型链中是否存在某属性

    console.log('family' in dog)

  注意我们在修改原型时不能使用对象的打点调用:

dog.family = '哈哈';这样写是为对象新增加一个属性,不会修改到原型,通过打点调用的方法相当于只能get不能set

3.修改父类原型中的方法

  子类继承父类,对父类的原型中修改原先存在的属性,修改只供自身使用,对父类没有影响但如果子类对父类的原型中添加了父类不存在的属性,添加操作会影响父类。

    function Animal(family) {
        this.family = family;
        Animal.prototype.run = function () {
            console.log('动物都会跑');
        }
        Animal.prototype.legs = 4;
    }

    function Dog(family, name, color) {
        Animal.call(this, family);
        this.name = name;
        this.color = color;
    }

    // 用一个中间变量obj 作为桥梁去完成子类对父类的继承
    var obj = Object.create(Animal.prototype);
    Dog.prototype = obj;
    Dog.prototype.constructor = Dog;

    Dog.prototype.skill = function () {
        console.log('看家');
    }

    //修改了父类
    Dog.prototype.__proto__.add = function () {
        console.log('这是一个通过子类去修改父类的测试');
    }

    //会发现中间变量被改,所以js中对象的拷贝是引用拷贝,即拷贝地址的值拷贝
    console.log(obj);

    Dog.prototype.__proto__.legs = 2;

    var animal = new Animal('test');

    //在这里输出父类对象,会发现add方法增添了,但父类的legs属性并没有被改
    animal.add();
    console.log(animal.legs);

    

执行结果如下:

三、ES6中的继承新语法

在ES6中新增了class和extends关键字


        class People {
            // 构造函数
            constructor(name, age) {
                //初始化属性
                this.name = name;
                this.age = age;
            }

            eat(food) {
                console.log(this.name + '吃:' + food);
            }
        }

        //设置原型中的内容
        People.prototype.legs = 2;

        //创建People对象
        var people = new People('baimao', 21);
        people.eat('苹果');
        console.log(people);

        class Man extends People {
            constructor(name, age, sex) {
                //相当于会自动的去执行People的构造函数
                super(name, age);
                //新增,扩展自己的属性
                this.sex = sex;
            }

            playMJ(hours) {
                console.log(this.name + '打了' + hours + '个小时麻将');
            }
        }

        var man1 = new Man('翠花', 28, '男');
        man1.playMJ(2);

    

ES6中类的数据类型就是函数,类本身指向构造函数

在ES6中,除了constructor中的属性是放在类中,其余定义的方法都是放在构造函数的prototype中.

子类继承父类,使用super关键字调用父类的构造方法

JavaScript原型及继承的更多相关文章

  1. 深入理解:JavaScript原型与继承

    深入理解:JavaScript原型与继承 看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解. 首先JavaScr ...

  2. JavaScript原型与继承

    JavaScript原型与继承 原型 在JavaScript中,每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象.这个原型对象为所有该实例所共享.在默认情况下,原型对象 ...

  3. JavaScript原型与继承的秘密

    在GitHub上看到的关于JavaScript原型与继承的讲解,感觉很有用,为方便以后阅读,copy到自己的随笔中. 原文地址:https://github.com/dreamapplehappy/b ...

  4. JavaScript 原型与继承

    JavaScript 原型与继承 JavaScript 中函数原型是实现继承的基础.prototype.construct.原型链以及基于原型链的继承是面向对象的重要内容 prototype 原型即 ...

  5. javascript原型链继承

    一.关于javascript原型的基本概念: prototype属性:每个函数都一个prototype属性,这个属性指向函数的原型对象.原型对象主要用于共享实例中所包含的的属性和方法. constru ...

  6. JavaScript 原型与继承机制详解

    引言 初识 JavaScript 对象的时候,我以为 JS 是没有继承这种说法的,虽说 JS 是一门面向对象语言,可是面向对象的一些特性在 JS 中并不存在(比如多态,不过严格来说也没有继承).这就困 ...

  7. 8条规则图解JavaScript原型链继承原理

    原形链是JS难点之一,而且很多书都喜欢用一大堆的文字解释给你听什么什么是原型链,就算有图配上讲解,有的图也是点到为止,很难让人不产生疑惑. 我们先来看一段程序,友情提示sublimeText看更爽: ...

  8. 【Javascript】Javascript原型与继承

    一切都是对象! 以下的四种(undefined, number, string, boolean)属于简单的值类型,不是对象.剩下的几种情况——函数.数组.对象.null.new Number(10) ...

  9. 【前端知识体系-JS相关】深入理解JavaScript原型(继承)和原型链

    1. Javascript继承 1.1 原型链继承 function Parent() { this.name = 'zhangsan'; this.children = ['A', 'B', 'C' ...

随机推荐

  1. 用Eclipse的snippets功能实现代码重用

    snippets功能实现代码重用 Snippets 代码片段是Eclipse的一个插件. 很多时候可以通过这个功能,重复使用常用的代码片段,加快开发效率. 创建一个代码段的步骤: 在Eclipse的e ...

  2. html5小游戏基础知识

    显示一个DIV和隐藏一个DIV 首先,我们要显示一个DIV和隐藏一个DIV需要使用css里面使用: .hide{ display:none;} .show{display:block;} 在需要显示或 ...

  3. Dagger2学习笔记

    Dagger2是第一个使用生成代码的方式实现依赖注入的框架.作为Dagger的升级版本,自然有它的优势,优先注重的是执行效率.本文着重介绍Dagger2.官方据点传送门: https://google ...

  4. NPOI 表头、页眉页脚重复设置

    NPOI 是 POI 项目的 .NET 版本.POI是一个开源的Java读写Excel.WORD等微软OLE2组件文档的项目. 使用 NPOI 你就可以在没有安装 Office 或者相应环境的机器上对 ...

  5. RabbitMQ插件安装

    RabbitMQ的有些插件没有集成在初始的安装中,它们需要额外安装,这些文件的后缀为.ez,安装时需要将.ez文件拷贝到安装的插件目录.以下是不同系统中默认安装的插件目录路径: 插件目录 Linux ...

  6. VB6之HTTP服务器的实现(二)

    接上篇,这次做了小小的改动和提升.增加了对POST的支持和对其他方法(GET和POST之外的)选择405回复.另外,增加了对CGI的支持,目前可以使用C语言来写(是不是好蠢的赶脚).相对于上篇,整体做 ...

  7. VB6之多维数组中元素在内存中的排列情况

    Private Declare Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal ...

  8. poj 1948 Triangular Pastures 小结

    Description Like everyone, cows enjoy variety. Their current fancy is new shapes for pastures. The o ...

  9. vim中常用基本命令

    一般模式可用命令,含光标移动.复制粘贴.查找替换等功能 Ctrl+f:屏幕向下移动一页,相当Page Down按钮 Ctrl+b:屏幕向上移动一页,相当Page Up按钮 0或Home键:移动到行首 ...

  10. HTML5 服务器推送事件(Server-sent Events)

    服务器推送事件(Server-sent Events)WebSocket 协议的一种服务器向客户端发送事件&数据的单向通讯.目前所有主流浏览器均支持服务器发送事件,当然除了 Internet ...