js高级之原型对象

127 阅读5分钟

需求:创建3个人对象 (姓名name,年龄age,性别sex)

常规写法是创建三个对象,示例代码如下:

  let person1 = {
         name:'小张',
         age:30,
         sex:'男'
      }

  let person2 = {
          name:'小王',
          age:20,
          sex:'男'
       }

   let person3 = {
           name:'小红',
           age:18,
           sex:'女'
       }

弊端是:(1)代码冗余 (2)维护不便

这个时候我们就可以尝试使用函数创建对象 : 解决创建多个对象代码冗余

工厂函数:用于批量创建对象的函数

示例代码如下:

function createPerson (name, age, sex) {
        //(1)创建空对象
        let p = {}
        //(2)对象赋值
        p.name = name
        p.age = age
        p.sex = sex
        //(3)返回创建好的对象
        return p
      }

      let p1 = createPerson('小张', 30, '男')
      let p2 = createPerson('小王', 20, '男')
      let p3 = createPerson('小红', 18, '女')
      console.log(p1, p2, p3)

构造函数 : 使用new关键字调用一个函数

  • 构造函数作用与工厂函数一致,都是创建对象。但是构造函数代码更加简洁

  • 构造函数的四个流程

    • (1)创建空对象

    • (2)this指向这个对象

    • (3)对象赋值

    • (4)返回对象 示例代码如下:

 function Person (name, age, sex) {
        //(1)创建空对象  {}
        //(2)将this指向这个对象  this = {}
        //(3)对象赋值
        this.name = name
        this.age = age
        this.sex = sex
        //(4)返回这个对象  return this
      }

      let person1 = new Person('小张', 32, '男')
      let person2 = new Person('小王', 38, '男')
      let person3 = new Person('小李', 28, '男')
      console.log(person1, person2, person3)

但是如果构造函数中有方法,每一次调用都会新生成一个方法,每一个对象的方法都不是同一个

示例代码如下:

  function Person(name,age){
        //(1)创建空对象  {}
        //(2)this指向这个对象 this = {}
        //(3)执行赋值代码
        //(4)返回这个对象  return this
        this.name = name;
        this.age = age;
        this.sayHi = function(){
        console.log('我喜欢敲代码');
        }
       };

      let p1 =  new Person('小李',28);
      console.log(p1);
      p1.sayHi();

      let p2 = new Person('小花',18);
      p2.sayHi();
      //每一个对象的方法都不是同一个
      console.log(p1.sayHi == p2.sayHi);//false

所以构造函数中也存在 弊端 : 浪费内存资源

全局函数 :解决内存资源浪费问题

也许有人会说这还不简单,为了解决内存资源浪费问题,我们可以使用全局函数呀

示例代码如下:

       function fn(){
           console.log('我喜欢敲代码');
        };

       function eat(){
            console.log('今天吃什么呢'); 
        };

       function Person(name,age){
            //(1)创建空对象  {}
            //(2)this指向这个对象 this = {}
            //(3)执行赋值代码
            //(4)返回这个对象  return this
            this.name = name;
            this.age = age;
            this.sayHi = fn;
            this.eat = eat;
         };

        let p1 =  new Person('小张',28);
        console.log(p1);
        p1.sayHi();

        let p2 = new Person('小花',18);
        p2.sayHi();
     
        console.log(p1.sayHi == p2.sayHi);//true

但是我们又会发现弊端 : 全局变量污染的问题

对象 (1)解决内存资源浪费 (2)全局变量污染

那又有人说使用对象 : (1)解决内存资源浪费 (2)全局变量污染

示例代码如下:

let obj = {
           fn:function(){
                 console.log('我喜欢敲代码');
            },
           eat:function(){
                console.log('今天吃什么呢'); 
             }
         }
        

         function Person(name,age){
            //(1)创建空对象  {}
            //(2)this指向这个对象 this = {}
            //(3)执行赋值代码
            //(4)返回这个对象  return this
            this.name = name;
            this.age = age;
            this.sayHi = obj.fn;
            this.eat = obj.eat;
         };

         let p1 =  new Person('小张',28);
         console.log(p1);
         p1.sayHi();

         let p2 = new Person('小花',18);
         p2.sayHi();
     
         console.log(p1.sayHi == p2.sayHi);//true

这个时候又有新的弊端 : 对象自身还是全局的,造成新的全局变量污染

所以这几种方式都不是最佳的,我们可以使用原型

原型对象

定义

原型对象 : 当声明一个函数的时候,编译器会自动帮你创建一个与之对应的对象,称之为原型对象

image.png

作用

原型对象作用 : 解决构造函数内存资源浪费 + 全局变量污染

用法

谁可以访问原型对象中的成员(属性和方法)

  • 构造函数自身:构造函数名.prototype
  • 构造函数实例化的每一个对象:点语法直接访问

示例代码如下:

       /* 1 原型 : 每一个构造函数在声明的时候,系统会自动的创建一个与之对应的对象,
        称之为原型对象
        */
        function Person(name,age){
            this.name = name;
            this.age = age;
        };

        /*2 如何获取原型对象 
            每一个函数都有一个 prototype 属性,指向这个原型对象
        */
       console.log(Person.prototype);

       /* 
       3 既然原型是一个对象 : 用于存储数据
       */
      Person.prototype.sayHi = function(){
          console.log('我喜欢敲代码');
          
      };

      /* 
       4 谁可以访问 原型中的成员(属性+方法)
        a. 构造函数自身 : 构造函数名.prototype
        b. 这个构造函数所创建(实例化)的每一个对象
      */
     // 实例化对象
     let p1 = new Person('小张',18);
     p1.sayHi();

     //实例化对象
     let p2 = new Person('小花',16);
     p2.sayHi();

     console.log(p1.sayHi === p2.sayHi);//true

__proto__属性

  • 属于实例对象,指向原型对象

  • proto属性不是W3C的标准属性,所以实际开发中一般不会使用它来访问原型

  • 作用: 可以让实例对象访问原型中的成员

prototype属性

  • 属于构造函数,指向原型对象

  • 作用: 解决构造函数资源浪费+变量污染

constructor属性

  • 属于原型对象,指向构造函数

  • 作用: 可以让实例对象 知道自己被哪一个构造函数创建的

构造函数、原型对象、实例对象三角关系

image.png

示例代码如下:

    //1.构造函数
      function Person (name, age) {
        this.name = name
        this.age = age
      }

      //2.原型对象
      Person.prototype.type = '好无聊'
      Person.prototype.eat = function () {
        console.log('今天吃什么')
      }

      //3.实例对象
      let p1 = new Person('小花', 16)

      //检查原型 : (1)先通过实例对象找构造函数
      console.log(p1.__proto__.constructor) //Person
      //(2)构造函数和原型对象一一对应关系
      console.log(p1.__proto__ === Person.prototype)//true

静态成员和实例成员

  • 静态成员 : 属于函数对象的成员

  • 实例成员: 属于实例化对象的成员

示例代码如下:

   //构造函数
         function Person(name,age){
            this.name = name;
            this.age = age;
        };

        Person.study = '我爱敲代码';
        console.log(Person.study);//静态成员
        

        //实例化对象
        let p1 = new Person('张三',20);

        console.log(p1.name);//实例成员
        console.log(p1.age);//实例成员