面向对象

74 阅读9分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天juejin.cn/post/712312…

面向对象(创建对象)

类和对象的关系:类是对象的抽象,而对象是类的具体事例

类只是对特征特点的一个描述,没有具体的数据,所以它不占内存,而对象是占内存的具体的事物。 面向对象的编程中都给出了创建“类”的方法 :calss 空格 类名{} 为什么说js是基于对象的方法?因为它里面没有类的概念以及创建类的方法 注意:但本质上是创建类,但不能说是创建类,要说是创建对象

js中创建对象的方法:

  1. 原始模式
  2. 工厂模式
  3. 构造函数模式
  4. 原型模式
  5. 混合模式(混合模式是原型模式和构造模式的混合。)

原始模式

原始模式语法
    var 对象名=new Object();
    对象名.属性名=值;
    对象名.属性名=值;
    对象名.方法名=function(){}
  • 对象里的属性就是用来存值的,方法放函数
  • 属性名自己定义,方法也是自己定义

缺点:没有实现封装,无法重复调用

字面量表示法
 var 对象名={"属性名":"值","属性名":"值","方法名":function(){}}
 var 对象名={ }的方式叫字面量表示法,直接生成,
 json对象就是典型的字面量表示法创建的

工厂模式

通过创建一个包含对象细节的函数来创建对象,然后返回这个对象

工厂模式语法
 function 函数名(参数1,参数2){
        var 对象名=new Object();
        对象名.属性名=参数1;
        象名.属性名=参数2;
        对象名.方法名=function(){};
        return 对象名;
    }
var 变量名=函数名(实参1,实参2);
对象名.方法名() //调用了函数里的方法

弊端:没有解决对象的识别问题,即怎么知道一个对象的类型,工厂模式解决了重复实例化多个对象的问题,没有解决对象识别的问题(工厂模式无法示别对象的类型,因为全部是Object,不像Data,Array)

构造函数模式

  • 构造函数就是用来构建对象的,没有返回值,不用return,它是最贴近“类”的概念。
  • 构造函数模式并不需要创建中间对象并且可以将构造函数的事例标识为一种特定的类型,构造函数使用new操作符来创建实例。
构造函数语法
    function 函数名(参数1,参数2){
        this.属性名=参数1;
        this.属性名=参数2;
        this.方法名=function(){};
    }
var 对象名1=new 函数名(实参1,实参2)
var 对象名2=new 函数名(实参1,实参2)
    //这是构造函数的固定语法,它就是构造函数
    //构造函数的名字就是对象的类型。
   //创建构造函数时函数名第一个字母基本都是大写
   //构造函数的事例指的是函数名
   //这里的this指的是实例化(构造)出来的对象

构造函数与工厂模式相比:

  1. 不用创建中间对象(不用new一个对象)
  2. 把属性和方法赋值给莪this
  3. 没有return语句

构造函数弊端:每个方法都会在每个实例上重新创建一次,person1和person2都有一个sayName()的方法,但两个方法不是同一个function实例,不同实例的同名函数是不相等的。

创建两个完全同样任务的function实例没有必要,而且还有this对象在,不需要在执行代码前就把函数绑定再特定对象上,可以向下面这样:

function Person1(n,a){
         this.name=n;
         this.age=a;
         this.eat=eat1;
         this.sayName=sn;
    }
    function eat1(){
        alert("我是全局eat1函数");
    }
    function sn(){
        alert("我是全局sayName函数")
    }
    var p3=new Person1("p3",1)
    var p4=new Person('p4',2)
    console.log(p3,p4);

对例子的解释:把sayName属性设置成全局的sn函数,这样,由于sayName包含的是一个指向函数的指针,因此p3和p4对象就共享同一个函数。但是如果对象需要定义很多方法,那么就要定义很多全局函数,自定义的引用类型也没有了封装的意义,为了解决上述问题,引入原型模式。

判断对象类型的方法

1. 获取当前(指定)对象的类型

语法:Object●prototype●toString●call(这里写对象的名字)

返回值:是个字符串,第一个参数告诉你是个对象,第二个参数告诉你是什么类型的对象。例:【boject,object】【object,Array】

2. 获取当前对象是什么类型

语法:对象名  instancof  Array

内置对象用instanceof来检查对象是什么类型,用来比较前面写的对象是不是后面的这个数据类型。(p1 instanceof Object),如果是返回true,不是返回flase

3. 获取当前对象的构造函数obj●constructor

语法:console.log(p1.constructor);

解释:指向当前对象的构造函数的,构造函数就是对象的类型。本身是对象里的一个属性

原型模式prototype

解释:prototype,它是函数的一个属性,所有的函数都有这个属性。

  • 构造函数的原型对象存在prototype属性里,直接构造函数名●prototype,得到的就是它的原型对象,然后再接着●属性,就可以看到原型对象里面的属性值。
  • 对象把数据存在了堆里面,栈里存放的是指针地址。Prototype本质指向一个对象,里面存的是指针。
  • 原型对象其实指的就是那个类。构造函数本身也有原型对象,构造函数根据原型对象模板生成的。用[ [ protoype ] ]括起来的函数(即实例化对象),他会继承构造函数里的prototype属性,也会指向构造函数的原型对象的protype属性,也能读取(继承)到原型对象的属性和方法
  • 原型对象其实指的就是那个类。构造函数本身也有原型对象,构造函数根据原型对象模板生成的。用[ [ protoype ] ]括起来的函数(即实例化对象),他会继承构造函数里的prototype属性,也会指向构造函数的原型对象的protype属性,也能读取(继承)到原型对象的属性和方法

原型:我们创建每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象,通过构造函数构造出来的对象都自动的拥有构造函数的prototype指针指向的对象的属性和方法(共享的),函数原型对象当中的所有方法和属性是不占内存的。并且prototype对象当中的属性和方法是可以修改的。

读取实例化对象继承来的原型对象的属性,直接实例化对象●属性名就可以,是因为有原型链的存在。原型链它自己会一层一层的向上查找该属性,先找自己本身有没有,没有的话就向上找,直到找到最顶层的原型对象Object,最顶层也没有输出undefined。 实例化对象的prototype属性写法是[ [ protoype ] ]。

原型链:

Function是对象,function的原型prototype也是对象,它们都会具有对象的共有特点。即对象具有属性porototype,每个对象都会在其内部初始化一个属性,就是prototype,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。

原型模式语法:
语法一:
function Person(n,a)
        // 如果是原型模式,构造函数里应该什么都不放,但根据语法
        //可以把设置原型对象的属性和方法的语句的放在构造函数里
        // 以下设置的是原型对象的属性和方法,我们把它放在了构造函数里,语法上是可以这么写的
        Person.prototype.name=n;
        Person.prototype.age=a;
        Person.prototype.eat=function(){
           alert("吃饭啦")
        }
    }
    var p1=new Person("tom",18);
    p1.eat()
    console.log(p1);//输出的是p1的类型,它里面包含了prototype这个属性,这个属性指向的是原型对象
    //并且可以读取到原型对象里面属性和方法
    console.log(Person.prototype)//输出的是构造函数的原型对象
    console.log(p1.prototype)//undefined  因为实例化对象的原型函数里面没有任何东西
    语法二:
    function Person(){}
        Person.prototype.name=”lily”;
        Person.prototype.city=[“上海”,“北京”];
        Person.prototype.eat=function(){
            alert("吃饭啦")
        }
    var p1=new Person();
   p1.eat()
   p1.name=“lucky”;
   p1.city.push(“杭州”);
    console.log(p1);
    console.log(Person.prototype)
    console.log(p1.prototype)

与构造函数相比

  1. 原型中第一次的所有函数和引用的对象都只创建一次;构造函数中的方法则会随着实例化的创建重复创建(如果有对象和方法的话)
  2. 原型中属性和方法都共享,构造函数中的属性和方法都不共享。
  3. 构造函数实例化出来的对象的[[porototype]]指向的是构造函数的原型对象。 原型模式弊端
  4. 原型模式最大的问题是由其共享的本性所导致的。
  5. 当修改某个实例原型中的属性值时,所有通过该构造函数创建的实例对象中的原型对象中的属性值都会被修改,因为它们是共享的。

混合模式(构造——原型混合模式)

  • 创建类最好的方法是用构造函数定义属性,原型模式定义方法。
  • 构造函数模是用于定义实例属性,原型模式用于定义方法和共享属性,这样每个实例都有自己的一份实例属性的副本,又同时共享着原型对象方法的引用,最大限度的节省了内存空间。
  • 根据原型模式的弊端,让原型所有的属性都不共享,所有的方法都共享,基本上方法(人的动作)都不怎么去修改,从而生成混合模式。
混合模式:是原型模式和构造函数模式的结合体
    function Person(n,a){
        this.name=n;
        this.age=a;
        Person.prototype.sayName=function(){//也可以把该方法放在构造函数外
            alert(this.name);
        }
    }
    var p1=new Person("tom",20);
    var p2=new Person("小明",18)
    console.log(p1,p2)
    // 在这里把方法放在函数外面也没有影响,它不是全局函数,而是构造函数的原型对象。

New关键字补充

  • 类没有实例化之前,就是new之前,它的属性、方法等在内存中都不是存在的,只有new了以后,这个类的一些东西在内存中才会真的存在,也就是只有new了之后,这个类才能使用。
  • 在javascript中,通过new可以产生原生对象的一个实例对象,而这个实例对象继承原对象的方法和属性。因此,new存在的意义在于它实现了javascript中的继承,而不仅仅是实例化了一个对象!
  • New本身是运算符