面向对象

141 阅读7分钟

面向对象

一种编程行业通用的写项目级的代码的思维,引导我们如何编写高质量的代码,万物皆对象 - 看待事物的角度,(属性:数据,行为:动作(方法))。代码特点是封装和继承

对象在内存中的示意图

35f12e19-dd0e-42b7-ad05-50e1983b11c1.png

创建对象的几种方式

字面量-创建对象

  • 简单粗暴
  • 不适合创建多个同样类型的对象的场景
		 //   字面量-创建对象
      const obj1 = { username: '悟空1', tel: 123321 };
      const name1 = 123;
      const obj2 = { username: '悟空2', tel: 123322 };
      const name2 = 123;
      const obj3 = { username: '悟空3', tel: 123323 };
      const name3 = 123;
      const obj4 = { username: '悟空4', tel: 123324 };
      const name4 = 123;
      const obj5 = { name: '悟空5', tel: 123325 };
      // 好处 方便
      // 不方便维护-修改
      // 不想要name属性,修改 username
		 // 要一个一个修改麻烦

工厂函数

  1. 容易理解
  2. 失去血缘关系,无法简单分辨对象的特征
  3. 后期无法实现继承
	 // 分析代码 功能 或者任何事情 面向对象的思维
      // 属性
      // 行为
	 function createPerson(name, age, height){
          return {
              username:name,
              age:age,
              height:height
          }
      }
    //   创建对象
    const obj6 = createPerson('悟空', 18, 150);
    const obj7 = createPerson('八戒', 20, 140);
    const obj8 = createPerson('龙马', 30, 190);
    console.log(obj6);
    console.log(obj7);
    console.log(obj8);

    // 优点 容易维护 
      // name => username 

      // 弊端 无法实现 继承的作用!

构造函数

  1. 可以方便的创建对象
  2. 拥有血缘关系
  3. 还有后续更多的优势
		 // 专业术语
      // 会把 createPerson 叫做 构造函数
      // 会把 createPerson 创建出来的对象 叫做 实例

      // 1 声明一个函数
      function createPerson(name, age) {
        // 2 给this赋值
        this.name = name;
        this.age = age;
        // this.color="red";
      }

      // 2 通过new的方式来创建对象
      const obj1 = new createPerson('悟空', 10);
      console.log(obj1);
      console.log(obj1.name);
      console.log(obj1.age);

构造函数的工作原理:

  1. 开辟空间
  2. 将新的创建的对象对象构造函数中的this
  3. 为对象赋值
  4. 将创建好的对象的地址返回

构造函数的弊端

同一个 say 方法占据了两份内存

bd2bde82-2e7a-4fe2-8cfd-c4a7499e67e1.png

性能问题

      function say() {
        console.log('这个是say方法');
      }

      // 构造函数的首字母大写  -  行内编码规范
      function CreatePerson(name) {
        this.name = name; // 创建了name属性
        // this.say = function () {
        //   console.log('这个是say方法');
        // };
        this.say = say; // say函数引用类型, 构造函数中的say 其实和外面的say内存地址一致的  同一个say方法
      }

      const obj1 = new CreatePerson('悟空');
      const obj2 = new CreatePerson('八戒');
      // obj1.say();
      // obj2.say();
      // 两个say的判断比较 是false 说明 两个say是在不同的内存空间上
      // 两个say 占用了两个内存空间
      // console.log(obj1.say === obj2.say); // false
      console.log(obj1.say === obj2.say); // true

      // 对于基本类型来说,=  就是复制了一份值
      // let num=100;
      // let num2=num;// 复制值  num和 num2占两给内存 各自不影响
      // num2=1000;
      // console.log(num);

      // 对于引用类似 =  其实把地址拷贝了一份给新的对象  两个数据 公用一份数据
      // let person1 = { name: '悟空' }; // person1 指向了 一个地址 0x1111 存放悟空
      // let person2 = person1; // person2也指向了person1的地址 0x1111  person1和person2 通用一个数据
      // 修改了person2 person1也会发生改变
      // person2.name = '八戒';
      // console.log(person1);

      // person2 和person1 共同指向了同一个地址
      // console.log(person1 === person2); // true


      // let o={};// 开辟了一个内存
      // let o2={};// 开辟了另外一个内存
      // console.log(o===o2);// false 两个不同的内存地址

      /* 
      构造函数想要解决 性能问题
      1 一定会把方法-函数写在构造函数的外面
      2 再通过this.say 指向外部的函数 
      
       */

提取同一个 say 方法

  1. 解决了浪费内存的弊端
  2. 但是造成了 污染全局变量 的问题

性能问题2

// 构造函数的 方法 都会通过类似的这种方式 来实现 多个实例的方法共享
      function say() {
        console.log('你好');
      }
      function CreatePerson(name) {
        this.nickname = name;
        this.say = say;
      }
      // function say() {
      //   console.log("学生你好");
      // }
      // function createStudent(params) {
      // }

      const obj1 = new CreatePerson('悟空');
      const obj2 = new CreatePerson('八戒');
      console.log(obj1);
      console.log(obj2);

      console.log(obj1.say === obj2.say); // true  这个代码是合理的 优化过
      console.log(obj1.say === obj2.say); // false  不够好 性能不够好  两个say占据了两个内存

      // 函数函数 方法提取出去 这套代码
      // 优点: 方便代码维护、也解决了性能 obj1.say === obj2.say

      // 缺点:  代码不够优雅 污染了全局变量 以后不能写 say方法,很容易就覆盖()

f53e7584-165f-4c45-b8f8-1ca4f9827ecb.png

原型对象

在构造函数的原型上 存放函数

  1. 解决了同一个 say 浪费 内存的问题
  2. 解决了污染全局变量的问题
// 原型对象 是任何构造函数对存在的一个对象 prototype
      // 作用: 构造函数看是人, 原型对象 就是人的DNA
      // 如果我们修改了DNA,那么通过构造函数创建实例都会一起发生修改
      // 如果我们在DNA上新增了一些东西,对应实例一样会被新增

      function CreatePerson(name) {
        this.name = name;
      }
      // 原型对象
      // console.log(CreatePerson.prototype);
      // 在DNA上新增 东西
      CreatePerson.prototype.say = function () {
        console.log('你好');
      };
      const obj1 = new CreatePerson('悟空');
      const obj2 = new CreatePerson('八戒');
      // obj1.say();
      // obj2.say();

      function CreateStudent() {}

      CreateStudent.prototype.say = function () {
        console.log('学生你好');
      };

      // console.log(obj1.say === obj2.say);// 没有性能问题
      // 有污染全局变量的问题吗  没有
      // CreatePerson.prototype.say
      // CreateStudent.prototype.say

      // 小结 原型对象-构造函数 一般情况 
      // 构造函数内只放属性 name 、age、color、height
      // 对应原型上 都是放 方法 = 函数

解释

  • 原型的单词是 prototype, 原型的这个名字是行业内共同认可的名字。
  • 原型本质是一个对象,理解为 JavaScript 自动帮我们添加的
  • 原型是 JavaScript 自动帮我们在定义构造函数的时候添加的
  • 所有构造函数的实例,共享一个原型
  • 原型上一般是挂载函数

a1038274-1c31-4808-ae2b-a1e59b2b9407.png

面向对象的案例

    <button>修改颜色</button>
    <script>
      /* 
      需求:
      1   通过行代码 const divModel = new Div("这个是普通的地址");      页面中出现对应的一个div标签
      2   给按钮绑定点击事件 触发了
           divModel.changeColor("red");  这个div的背景颜色变成红色
           divModel.changeColor("yellow");  这个div的背景颜色变成黄色

      分析:
      1 Div 是一个构造函数
      2 Div 构造函数 在原型上有一个方法 changeColor


      3 const divModel = new Div("这个是普通的地址");  
        这行代码实际的作用 页面中出现一个div
        1 const div=document.createElement("div");
        2 div.innerText="文本内容"
        3 document.body.appendChild(div) 

      4 divModel.changeColor("yellow"); 这个div的背景颜色变成黄色
        ???.style.backgroundColor="yellow"
       */

      function Div(text) {
        const div = document.createElement('div');
        div.innerText = text;
        document.body.appendChild(div);
        this.dom = div;
      }
      Div.prototype.changeColor = function (color) {
        // this.dom = div;
        this.dom.style.backgroundColor = color;
        this.dom.style.fontSize = size;
      };

      const button=document.querySelector("button");
      const divModel1 = new Div('这个是普通的div1');
      button.addEventListener("click",function () {
        divModel1.changeColor("red", "100px");
        // 新功能
        // 希望div中的文字 大小变为100px
   
      })
    </script>
    <SCript>
              /* 
      需求: 通过 new MyImg("图片地址")  页面上就会多一个图片标签 

      1 页面上多一个图片的本质代码
        1 const img = document.createElement("img");
        2 img.src="图片地址"
        3 document.body.appendChild(img); 

      2 new的方式来创建图片 
        new MyImg 做了什么事情  => 调用了一个构造函数 MyImg
       */
      // const img1 = new MyImg('./images/1.png'); // 页面上多一个图片标签

      function MyImg(src) {
        const img = document.createElement('img');
        img.src = src;
        document.body.appendChild(img);
      }
      const img1 = new MyImg('./images/gotop.png');
      const img2 = new MyImg('./images/b_01.jpg');
      const img3 = new MyImg('./images/b_02.jpg');
    </SCript>

面向对象案例

需求

1 const imgModel =new MyImg("图片地址") (不要直接插入到body标签上)

2 imgModel.append("父元素的选择器"); 希望可以在父元素里面插入一个img

3 根据给到的需求

​ 1 先拆分出 代码的结构 要定义构造函数吗 原型上要写什么方法

​ 2 根据给到的代码具体需求 结合 底层js+webAPI的知识 来实现 封装的功能

​ 3 在以前更多的是学习如何直接写代码问题 ,现在也是在写代码 - 直接解决问题,通过封装代码(提高的能力)->解决问题

    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    <script>
      function MyImg(src) {
            const img = document.createElement('img')
            img.src = src
            this.dom = img
        }
        MyImg.prototype.append = function (parentSelector) {
            // 通过 this.dom  获取到 构造函数中创建好的图片img对象
            // 希望可以在父元素里面插入一个img  底层的webAPI代码是什么
            // document.body 父元素
            // 父元素.appendChild(子元素);  // img 创建的元素
            document.querySelector(parentSelector).appendChild(this.dom);
        }
        // 先创建一个图片标签
        const imgModel = new MyImg('../项目-AlloyTeam/images/1.jpg')
        // 指定位置来插入元素
        imgModel.append('li:nth-child(3)')   // 图片插入到指定的父元素中
    </script>

原型-继承-call

call( ) 借调 可以让一个对象 来借用另外一个对象的方法

        const car = {
            name: '装甲车',
            add: function (username, age) {
                // 本质 可以给car添加新的属性
                this.username = username;
                this.age = age;
            },
        };

        // car.add('发动机', 100);
        // console.log(car);

        const obj = {};
        // car.add.call(obj); // obj 想要借用 添加属性的方法
        car.add.call(obj, '发动机', 200); // call 传入参数  add.call(谁要借用,被借用的方法的形参1,被借用的方法形参2)
        console.log(obj); // obj 有没有 username属性(不用管属性值)、有没有age属性(不用属性值)


      // 可以实现 了 一个空对象 obj 通过call的使用 来实现了 借别人的方法add 来给obj添加新的属性

借调演示

        function person (name, age, height){
            this.username = name;
            this.age = age
            this.height = height
        }
        function Student(name, age, height, color){
            // 借调call
            person.call(this,name,age,height)
            this.color = color
        }

        const s1 = new Student('迪迦', 2000, 100, 'yellow')
        console.log(s1);

方法的继承

      // 对象 两个大的特点
      // 属性 - 属性的继承
      // 行为-方法
      // 让儿子也具有父亲的行为  =>  把父亲的DNA 给到儿子即可
      // 给儿子添加行为 儿子的原型上添加
      // 父亲的行为 存放在父亲原型上

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

      Person.prototype.say = function () {
        console.log(this.username, this.age); // 我是谁 我的年龄是
      };
      Person.prototype.show = function () {
        console.log(this.username + ' 秀肌肉');
      };

      // const p1=new Person("父亲",50);
      // p1.say();

      function Student(name, age, color) {
        // name age person里面已经有定义
        // 借用一下
        Person.call(this, name, age);

        // color person里面没有的 需要自己来定义
        this.color = color;
      }
      const s1 = new Student('三好学生', 10, 'yellow');

      // 把父亲的行为 也设置到儿子上
      Student.prototype.say = Person.prototype.say;
      Student.prototype.show = Person.prototype.show;

      s1.say();
      s1.show();

      /* 
      总结
      想要实现继承的功能 分两个部分
      1 属性的继承
        Person.call(this,其他参数)
      2 方法的继承
        儿子的原型.say =  父亲的原型.say 
       */

继承案例的重复演示

    <script>
      /* 
      封装 代码 实现以下的功能
      1 父亲   Element
        1 属性 dom   this.dom
        2 行为 实例.append(父元素标签的选择器)
      2 儿子1  ElementDouble  div
        1 继承父亲 Element  
          属性 dom
          行为 append
      3 儿子2  ElementSingle  img 
          属性 dom
          行为 append

      4 当执行以下代码时 出现对应效果
        1 const divModel = new ElementDouble("div","div内的文字")
        2 divModel.append("body") ;  body标签可以出现 一个 div 

        1 const imgModel = new ElementSingle("img","图片地址")
        2 imgModel.append("body");  body标签可以出现 一个图片 
       */

      //  父亲
      function Element() {
        this.dom = null;
      }
      //  父亲
      Element.prototype.append = function () {};

      // 儿子1
      function ElementDouble(tagName, content) {
        const dom = document.createElement(tagName);
        dom.innerText = content;
        this.dom = dom;
      }
      ElementDouble.prototype.append = function (parentSelector) {
        // 真正的dom父元素.appendChild(子元素)
        document.querySelector(parentSelector).appendChild(this.dom);
      };

      // 儿子2
      function ElementSingle(tagName, src) {
        const dom = document.createElement(tagName); // 图片
        dom.src = src;
        this.dom = dom;
      }
      ElementSingle.prototype.append = function (parentSelector) {
        document.querySelector(parentSelector).appendChild(this.dom);
      };

      const divModel = new ElementDouble('div', 'div内的文字');
      divModel.append('body');

      const imgModel = new ElementSingle('img', './images/b_01.jpg');
      imgModel.append('body');

      // 如果代码重复实现了 很有可能就是我们要封装的时候
      // 以前的封装 仅仅是封装一个小小的函数而已
      // 现在的封装, 面相对象的思维来封装
      // 封装 属性  父亲
      // 封装 方法  父亲
    </script>