• 第八章 面向对象之二:function与面向对象的关系(两个实现面向对象的重要属性)


          每一个函数建立完成之后,javaScript都会自动的给它添加两个属性和一个配套对象(配套类),即prototype属性和__proto__属性,而配套对象(类)叫做“原型对象”。


          只要是函数,它就有这个原型对象,我们无法删除它。


          原型对象的意义在于,它包含的属性和方法对于通过此函数创建的实例来说,是公用的。而我们在函数本身内部定义的属性和方法不是公用的,每次建立实例之后,虽然两个实例都具有想通的属性和方法,但是实际上是两份相同的拷贝,并不是同一段内存的属性和方法,它们的各自修改不会对其他实例造成影响,但是这对于内存的高效率使用是很不方便的。


          函数(类)才有原型对象属性(prototype属性),实例是没有的。这对于javaScript自带的构造函数(Array、Boolean、RegExp等)也是一样的。


          我们如何使用原型对象?函数本身的prototype属性保存的就是原型对象的地址,我们可以通过prototype属性访问(设置)原型对象的属性和方法(函数.prototype.属性/方法)。


          原型对象本身跟普通对象(函数、类)是基本一样的,它也可以有自定义的属性和方法,区别在于以下两点:


    一、 它没有prototype属性。(其实可以理解,本身已经是原型对象了)
    二、 它有一个constructor属性,保存的是函数(类)本身的地址。也就是通过constructor属性,原型对象可以找到它所配套的原始函数。(也就是它知道它是谁的原型对象)。

    <script>
    function Person() { // 创建一个空函数(类)
    
    }
    
    // 添加原型属性和方法
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "sofeware Engineer";
    Person.prototype.sayName = function () {
    console.log( this.name );
    };
    
    // Person.prototype = {
    
    // person1和person2的sayName是同一个内存地址的函数
    var person1 = new Person();
    person1.sayName();
    var person2 = new Person();
    person2.sayName();
    console.log( person1.sayName === person2.sayName ); 
    // -----------------------------------------------------
    
    // 看看函数是否有perototype属性(有的,而且Array是js自带的构造函数,当然也有)
    console.dir( Array );
    // -----------------------------------------------------
    
    // 看实例是否有prototype属性(实际没有,但是有__proto__属性)
    var a = new Array();
    console.dir( a );
    console.dir( person1 );
    // -----------------------------------------------------
    
    
    </script>

    请仔细查看console输出的信息,对照我们上边整理的知识点。

          __proto___跟prototype一样,都是函数自带的属性,当然这个属性不止是函数有,通过函数(类)创建的实例也是有的。


          在ECMAScript里管这个属性称为“[[Prototype]]”,在浏览器里的实现称为:__proto__。它包含的是指向创造这个实例函数(类)的原型对象。听着有点晕,就是实例一般都是通过new 函数()这种表达式创造出来的,这个实例的__proto__属性指向的就是这个函数的原型对象。


          __proto__本身是无法修改的,但是某些浏览器为了方便把__proto__暴露出来了,我们可以对他进行修改,比如firefox和chrome。


          __proto__属性是javaScript实现原型链的关键,javaScript实现面向对象的继承是基于原型链的,因此__proto__属性是javaScript实现继承的根本,搞清楚__proto__的真相就理解了继承。


          我们说所有的函数和实例都包含__proto__属性,javaScript内部的称为内建类型的Array类型(实际上Array是一个函数)也有,它的__proto__的指向应该是它的父类的原型对象,也就是Function的原型对象。


          Object在javaScript里是所有函数(类)的基类,实际上javaScript自带的内建类型(Array,Boolean,String,Function,RegExp,Error等)的父类都是object。


          在javaScript里,可以理解为所有的函数都是通过Function关键字来进行初始化的,比如Person(){}这个空函数,实际上可以理解为Person = new Function(),也就是说,我们创建一个自定义函数Person,它的__proto__应该指向Function的原型对象,而Function原型对象的__proto__指向Object的原型对象,Object的__proto__是null。如此,构成了从实例到根父类的连接,这就是原型链。


          因此可以说,函数是一个特殊的存在,它既是一个类,也是一个实例。


          

          当执行实例的一个方法时,javaScript这么规定,先找实例本身是否有此方法,没有按照 __proto__的指向找向父类的对象,如果父类对象没有这个方法则找父类对象的原型对象,看父类的原型对象是否有此方法,如果没有按照父类原型对象的__proto__的指向找父父类的原型对象,父父类对象没有去找父父类对象的原型对象,如果找到那么执行,如果还找不到就继续向上,一直到object的原型对象的__proto__指向的null。


         

          我们可以通过实例的__proto__访问父类原型对象的属性和方法,但是无法更改它,我们自定义一个同名的属性或方法会覆盖掉父类的属性和方法。如果我们覆盖了同名属性或方法,通过delete操作符可以删除实例的同名属性或方法,从而重新访问父类的属性和方法。

    person1.name = "Greg";
    person1.sayName(); // 来自实例
    person2.sayName(); // 来自继承
    
    
    delete person1.name;
    person1.sayName(); // 来自继承
    person2.sayName(); // 来自继承

    有关原型的几个函数:
    1、 isPrototypeOf/Object.getPrototypeof():访问__proto__。

    •  isPrototypeOf用法:父类.isPrototypeOf(实例),返回true表明实例的__proto__指向父类的原型对象,false则相反。
    • Object.getPototypeof(实例):返回实例的__proto__引用,并且Object.getPrototypeOf(实例).原型属性/方法可以直接调用原型的属性和方法。

    2、 hasOwnProperty:
    用法:实例.hasOwnProperty(属性/方法名)
    判断属性或方法名是否来自于实例(或原型),在实例内返回true,在原型中返回false。

  • 相关阅读:
    类和对象
    数组
    循环结构
    选择结构
    变量,数据类型和运算符
    什么是JDBC,JDBC的使用
    重拾JavaScript
    git使用日记
    Base包
    RabbitMQ(windows环境)下载与安装
  • 原文地址:https://www.cnblogs.com/jingubang/p/4661751.html
Copyright © 2020-2023  润新知