最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面向对象编程

    正文概述 掘金(大大大石头)   2021-01-10   575

    面向对象编程

    什么是对象

    JS中我们可以理解成对象是一个容器,封装了属性和方法

    • 属性:对象的状态
    • 方法:对象的行为

    在实际开发中,对象是一个抽象的概念,可简单理解为数据(属性)和一些功能(方法)的集合

    ES262中,对象定义为无序属性的集合,其属性可以是基本值、对象或者函数等

    面向对象编程

    将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工和合作,完成对真实世界的模拟

    面向对象和面向过程

    • 面向过程就是亲力亲为,事无巨细我们都要考虑清楚,一旦中间某个环节没有考虑到都可能会发生错误,需要我们面面俱到,步步紧跟,有条不紊
    • 面向对象即使找一个对象,通过对象指挥得到结果
    • 面向对象是将执行者转变为指挥者
    • 面向对象不是面向过程的替代 ,而是面向过程的封装

    特性

    封装性:把所有面向过程的编程封装到对象中

    继承性:

    多态性 (抽象):传入不同的参数可能会输出不同的结果

    总结

    在面向对象编程思想中,每个对象都是功能中心 ,具有明确的分工,可以完成接收信息、处理数据、发出信息等任务

    因此,面向对象编程具有灵活性、代码可复用性、容易维护和开发,比起以一系列函数或者指令组成的传统面向过程编程,更适合多人合作的大型软件项目

    面向对象和面向过程

    //面向过程编程
    // var std1 = {
    //     name: '张三',
    //     chengji: 98
    //   },
    //   std2 = {
    //     name: '李四',
    //     chengji: 70
    //   };
    
    // function go(student) {
    //   console.log('姓名:' + student.name + ' 成绩:' + student.chengji);
    // }
    
    // go(std1);
    // go(std2);
    
    
    //面向对象编程
    function Student(name, chengji) {
      this.name = name;
      this.chengji = chengji;
      this.go = function () {
        console.log('姓名:' + this.name + ' 成绩:' + this.chengji);
      }
    }
    
    var std1 = new Student('张三', 98),
      std2 = new Student('李四', 70);
    
    std1.go();
    std2.go();
    

    面向对象编程设计思想

    • 抽象一个Class构造函数
    • 根据Class构造函数创建实例(instance)
    • 只会实例(instance)得到结果

    创建对象的方式

    • new Object()构造函数: 很复杂,我们需要分别定义它的各种属性和方法,而且需要多个对象需要书写重复代码
    • 对象字面量 = {}:也比较复杂,我们如果需要创建多个对象,需要书写很多重复性代码
    • 工厂函数:简单,我们可以很方便地创造对象,但创造出来的对象不是很具象,不能有效分类,因为这些对象都是属于Obiect创造的,我们不能知道生成的这个对象是通过那个构造函数构造的,而且总是需要return出来
    • 自定义构造函数:简单,当对象生成后可以得知是由哪个构造函数生成的,我们可以使用this.constructor查看是由哪个构造函数生成的

    判断对象具体的构造函数类型还是需要使用instanceof来判断,因为this.constructor是可以更改的,可能会有所改变

    构造函数和实例之间的关系

    • 构造函数是一个抽象模版,是根据具体事物抽象出来的
    • 实例对象就是根据这个模版(构造函数)创建出来的具体对象(根据传入参数不同,可能会形成不同的对象)
    • 每一个实力对象都可以通过constructor属性指向创建该对象的构造函数(constructor是实例属性的说法不是很严谨,具体的后面说)
    • 可以通过constructor属性判断实例和某个构造函数之间的关系(这种方法不是很严谨,还是推荐使用instanceof方法进行判断,后面会解释)

    静态成员和实例成员

    使用构造函数创造对象时,可以给构造函数和创建的实例添加属性和方法,这些属性和方法叫做成员

    实例成员:在构造函数内部,通过this.xxx创建的成员,在创建对象之后只能通过实例才能调用,不能直接通过构造函数调用

    静态成员:添加给函数自身的成员,排除全部通过this创造的成员,只能由构造函数调用,不能通过实例对象调用

    function Perope(name, age) {
      //实例成员:只有实例对象可以调用
      this.name = name;
      this.age = age;
      this.sayName = function () {
        console.log(this.name);
      }
    }
    //静态成员,只有构造函数自身可以调用
    Perope.sayHello = function () {
      console.log('hello');
    }
    
    //创建实例
    var p1 = new Perope('张三', 19);
    
    // 尝试调用实例成员
    console.log(p1.name); //张三
    console.log(p1.age); //19
    //构造函数无法调用
    console.log(Perope.name); //Perope(这里实际不能调用,但是构造函数自带一个name属性,所以会输出Perope)
    console.log(Perope.age); //undefined()不能调用
    
    //尝试调用静态成员
    Perope.sayHello(); //hello
    //实力对象无法调用
    p1.sayHello(); //报错
    

    总结:我们可以给构造函数创建成员,只有在构造函数内部使用this.xxx创建的成员有且仅能由实例调用,而使用构造函数名.xxx创建的成员实例是无法使用的,只能由构造函数调用

    构造函数的问题

    浪费内存

    如果构造函数内部有多个相同的方法或者属性,假如我们要创建多个对象,那么每个对象都会有这些相同的属性和对象,我们会发现这些方法和属性都是独立群在的(在内存中),所以说如果有大量的实例对象,那么就会非常消耗内存

    // 构造函数
    function Perope(name, age) {
      //实例成员:只有实例对象可以调用
      this.name = name;
      this.age = age;
      this.sayName = function () {
        console.log(this.name);
      }
    }
    //创建实例
    var p1 = new Perope('a', 19);
    var p2 = new Perope('b', 20);
    //验证p1和p2的sayname方法是否相同
    //结果显示flase,那么这两个对象的方法会在内存中占用两块内存,如果实例比较多,同样的方法会在内存中占用很多内存,导致内存浪费
    console.log(p1.sayName === p2.sayName);  //flase
    

    解决方案

    • 方案1:把方法写在全局的函数中,然后在构造函数内部把方法指向这些函数。
      • 缺点:如果我们的方法很多,我们就需要在全局创建大量函数,可能会出现一些问题
    • 方案2:把函数封装在一个对象内部,在构造函数内部调用这个对象的方法
      • 缺点:我们还是想让这些属性和方法归于我们实例对象自己,这样比较简洁,也便于管理

    原型对象

    使用原型对象可以很好的避免前面构造函数造成的内存浪费的问题

    prototype原型对象

    在任何函数内部都有一个pertotype属性,这个属性的属性值是一个对象

    • 可以在原型对象上添加方法或者属性

    • 构造函数的prototype默认都有一个cinstructor属性,指向原型对象所在的函数(构造函数)

    • 同构构造函数创建的实例内部会包含一个指针(_proto_),这个指针指向生成市里的构造函数内部的prototype

    • 实例对象可以直接访问原型对象的成员(属性和方法)

      面向对象编程

    在工作中,我们不会使用_proto_调用方法或者属性,我们会省略_proto_直接使用实例.方法/属性调用,因为_proto_不是一个标准的属性,是浏览器根据语法自动生成的

    构造函数浪费内存解决方案-原型对象

    • js规定,每个构造函数都有一个prototype属性,指向构造函数的原型对象
    • 实例对象可以直接或者间接的继承原型对象上面的方法或者属性

    因此,我们可以把所有实例对象需要的共享属性和方法定义在原型上

    // 构造函数
    function Perope(name, age) {
      this.name = name;
      this.age = age;
    }
    
    //把所有实例共享的方法或者属性添加给原型对象
    //给原型添加sayName方法
    Perope.prototype.sayName = function () {
      //这里面的this谁调用指向谁
      console.log(this.name);
    }
    //给原型添加sayAge方法
    Perope.prototype.sayAge = function () {
      //这里面的this谁调用指向谁
      console.log(this.age);
    }
    
    //创建两个实例对象
    var p1 = new Perope('a', 12),
      p2 = new Perope('b', 20);
    
    //执行实例对象的方法,这里省略_proto_
    p1.sayAge();
    p2.sayName();
    
    //判断一下p1和p2的sayName是不是同一个引用
    console.log(p1.sayName === p2.sayName); //true
    

    在工作中,我们可以把所有实例对象私有的属性或者方法放到构造函数上,把公共的方法或者属性添加到原型上

    原型链

    为什么实例对象可以调用原型上的属性和方法?

    面向对象编程

    当我们调用实例对象的某个方法或者属性时,会优先在实例对象内部寻找,如果对象实例中没有这个方法或者属性,会向上寻找构造函数的原型上的方法或者属性,如果还没有再向上寻找Object构造函数原型上的方法或者属性。

    // 构造函数
    function Perope(name, age) {
      this.name = name;
      this.age = age;
      //在构造函数上创建一个属性和一个方法
      this.num = 2;
      this.sayName = function () {
        console.log('hello');
      }
    }
    
    //把所有实例共享的方法或者属性添加给原型对象
    //给原型添加sayName方法
    Perope.prototype.sayName = function () {
      //这里面的this谁调用指向谁
      console.log(this.name);
    }
    //给原型添加sayAge方法
    Perope.prototype.sayAge = function () {
      //这里面的this谁调用指向谁
      console.log(this.age);
    }
    //在原型上添加一个构造函数内部就有的属性
    Perope.prototype.num = 1;
    
    //创建实例对象
    var p1 = new Perope('a', 12);
    //调用sayName方法,发现执行结果为hello,说明执行的是实例对象本身的sayName方法
    p1.sayName();
    //同上,也是调用了实例对象本身
    console.log(p1.num);
    
    //我们调用实例上没有的方法sayAge,可以发现会可以执行,说明调用了原型上的方法
    p1.sayAge();
    
    //我们可以看出来
    //当实例对象上和原型对象上有同样的方法的时候,会优先调用实例的方法,不会调用原型的方法
    //当我们调用实例上没有的方法的时候,会向上寻找调用原型上的方法
    

    查找机制

    每当代码读取某个对象的属性时,会执行一次查找

    1. 首先搜索实例对象本身,如果在实例中查找到了相应的属性,会返回属性值
    2. 如果搜索不到,则继续搜索指针指向的原型对象,在原型对象中查找,如果找到了,返回属性值

    实例对象读写原型对象成员

    读取:

    1. 自身查找,找到返回
    2. 找不到找原型,找到返回
    3. 如果找到原型链末端还没找到,返回undefined

    写入

    • 值类型成员写入(实例.值类型成员 = xxx):
      • 会屏蔽掉原型对象,也就是说无法修改原型对象上的属性或者方法,只能添加或者修改自身
    • 引用类型成员写入(实例.引用类型成员 = xxx):同上
    • 复杂类型成员修改(实例.成员.xx = xxx):
      • 现在自身寻找成员,找到修改自身,找不到找原型链,找到则修改原型链上的值,找不到报错
    // 构造函数
    function Perope(name, age) {
      this.name = name;
      this.age = age;
    }
    
    //给原型添加sayName方法
    Perope.prototype.sayName = function () {
      //这里面的this谁调用指向谁
      console.log(this.name);
    }
    //给原型添加sayAge方法
    Perope.prototype.sayAge = function () {
      //这里面的this谁调用指向谁
      console.log(this.age);
    }
    //原型上添加一个对象
    Perope.prototype.me = {
      num: 1
    };
    //原型上添加一个值属性
    Perope.prototype.sex = 'nan';
    
    //创建一个实例
    var p1 = new Perope('zhangsan', 19);
    
    //试图修改原型上的方法
    //修改引用
    p1.sayAge = function () {
      console.log('1111');
    }
    //修改值
    p1.sex = 'nv';
    //修改复杂类型成员
    p1.me.num = 2;
    
    //打印实例
    //我们发现,p1上面新增了sayAge和sex属性,并且属性值和我们试图修改的一样,说明我们修改值或者引用无法修改原型上的
    //同时我们发现,原型上p1.me.num的值变成了2,那么证明我们修改复杂类型成员却是可以成功的,并且也不会在实例上新建属性
    console.log(p1);
    

    更简单的原型语法

    使用对象字面量{} 重新赋值的方式书写,即构造函数.prototype = {}

    但要注意一个问题,这样直接修改会导致constructor丢失,我们打印会发现指向了Object,所以我们需要手动将constuctor指向正确的构造函数

    // 构造函数
    function Perope(name, age) {
      this.name = name;
      this.age = age;
    }
    
    // //给原型添加sayName方法
    // Perope.prototype.sayName = function () {
    //   //这里面的this谁调用指向谁
    //   console.log(this.name);
    // }
    // //原型上添加一个值属性
    // Perope.prototype.sex = 'nan';
    
    
    //上面添加原型方法的优化写法
    Perope.prototype = {
      //手动添加constructor并指向对应构造函数
      constructor: Perope,
      //直接在对象内部添加属性和方法
      sayName: function () {
        console.log(this.name);
      },
      sex: 'nan'
    }
    var p1 = new Perope('zhangsan', 1);
    console.log(p1);
    

    原型对象使用建议

    在定义构造函数时,可以根据成员的功能不同,分别进行设置:

    • 私有成员:一般是非函数成员,放在构造函数内部
    • 共享成员:一般是函数,放在原型中

    如果重置了prototype记得修正constructor的指向

    内置构造函数的原型对象

    所有函数都有prototype属性对象,js中内置的构造函数也有prototype原型对象属性:

    • Object.prototype
    • Function.prototype
    • Array.prototype
    • String.prototype
    • Number.prototype
    • .........

    在内置构造函数对象原型上添加方法

    1. 不允许使用对象字面量的方式直接更改原型对象,因为js内置会被保护起来,不允许直接更改
    2. 可以使用打点滴调用的方法添加方法,但是我们工作中一般是不允许在内置对象上添加方法的

    案例:随机方块

    需求:在一个舞台内,让10个小方块随机改变位置,刷新后小方块颜色可辨,不刷新小方块颜色不变

    • 这里我们采用面向对象编程,把每个小方块都想象成一个对象,那我们就需要自定义构造函数,使用构造函数构造小方块,小方块内部应该有可以把自己渲染到html结构的方法,还有可以修改自身位置的方法
    • 另外,我们还需要一个工具函数集合,可以写在一个对象内部,对象哪需要有能够生成随机数和随机颜色的的方法;
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <!-- 引入css样式 -->
      <link rel="stylesheet" href="./css/index.css">
    </head>
    
    <body>
      <!-- 创建容纳小方块的舞台 -->
      <div id="box" class="box"></div>
      <!-- 引入js代码 -->
      <script src="./js/tools.js"></script>
      <script src="./js/block.js"></script>
      <script src="./js/index.js"></script>
    </body>
    
    </html>
    
    /* 清空样式 */
    * {
      margin: 0;
      padding: 0;
    }
    
    /* 设置舞台定位,宽高、背景色 */
    .box {
      position: relative;
      width: 800px;
      height: 800px;
      background: #ccc;
    }
    
    /* 设置舞台内部小方块绝对定位 */
    .box span {
      position: absolute;
    }
    
    // 获取元素
    var box = document.getElementById('box');
    //创建一个数组,容纳创建的实例对象
    var blocks = [];
    // 循环10次,创建小方块对象
    for (let i = 0; i < 10; i++) {
      //创建实例对象
      var me = new Block(box, {
        // 传入随机颜色参数
        backgroundColor: Tools.getColor()
      });
      // 执行创建的实例对象的渲染方法
      me.toHtml();
      //把创建的实例对象添加进数组
      blocks.push(me);
    }
    //遍历数组里面的所有实例对象
    for (let i = 0; i < blocks.length; i++) {
      //创建定时器,每1秒一次,执行实例对象中的随机位置方法
      setInterval(function () {
        blocks[i].chagePosition();
      }, 1000)
    }
    
    // 创建构造函数,构建小方块
    //参数1:小方块的舞台元素
    //参数2:小方块的样式属性对象
    function Block(father, obj) {
      //避免用户没有传入boj,用来做备案
      var obj = obj || {};
      //创建所有小方块需要的属性
      this.width = obj.width || 20;
      this.height = obj.heigth || 20;
      this.backgroundColor = obj.backgroundColor || 'red';
      this.top = obj.top || 0;
      this.left = obj.left || 0;
      //创建舞台属性,获取舞台(这里必须获取,否则会显示未定义)
      this.father = father;
    }
    
    //原型对象修改
    Block.prototype = {
      //手动指向Block构造函数
      constructor: Block,
      //添加原型方法:渲染
      toHtml: function () {
        //创建元素节点,并把节点传入构造函数,以供其他方法使用
        this.me = document.createElement('span');
        //修改元素style行内样式
        this.me.style.width = this.width + 'px';
        this.me.style.height = this.height + 'px';
        this.me.style.backgroundColor = this.backgroundColor;
        this.me.style.top = this.top + 'px';
        this.me.style.left = this.left + 'px';
        //将小方块插入到父元素底部
        this.father.appendChild(this.me);
      },
      //原型方法:随机位置
      chagePosition: function () {
        //修改自己的left属性(使用Tools随机数(1-舞台宽度/小方块宽度 - 1)*小方块宽度),为了避免小方块重合
        this.left = Tools.getNumber(0, this.father.clientWidth / this.width - 1) * this.width;
        //同上,修改top值
        this.top = Tools.getNumber(0, this.father.clientHeight / this.height - 1) * this.height;
        //修改小方块定位属性
        this.me.style.top = this.top + 'px';
        this.me.style.left = this.left + 'px';
      }
    }
    
    //创建工具对象,防止随机数方法
    var Tools = {
      //对象方法:获取指定两个数之间的随机数,包括这两个数
      getNumber: function (min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min + 1) + min);
      },
      //返回随机颜色值
      getColor: function () {
        return 'rgb(' + this.getNumber(0, 255) + ',' + this.getNumber(0, 255) + ',' + this.getNumber(0, 255) + ')';
      }
    }
    

    起源地下载网 » 面向对象编程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元