美文网首页
JS 面向对象补充 构造函数 原型 对象原型 函数原型

JS 面向对象补充 构造函数 原型 对象原型 函数原型

作者: 咸鱼不咸_123 | 来源:发表于2022-04-29 10:49 被阅读0次

    1.面向对象

    1.1.定义多个属性描述符

    var obj={
      _age:18,//私有属性(js里面是没有严格意义的私有属性)
    };
    
    Object.defineProperties(obj,{
      name:{
        enumerable:true,
        configurable:true,
        writable:true,
        value:"wjy"
      },
      age:{
        enumerable:true,
        configurable:true,
        get(value){
          return this._age;
        },
        set(value){
          this._age=value
        }
      }
    
    })
    console.log(obj);//* 将所有的属性打印,除了不可枚举的属性
    
    1.1.1 set和get的新型写法
    • 这种的configurable为true,enumerable也为true
    var obj={
      _age:18,//私有属性(js里面是没有严格意义的私有属性)
      set age(value){
        this._age=value
      },
      get age(){
        return this._age
      }
    };
    
    

    等价于下面的这个

    var obj={
      _age:18,//私有属性(js里面是没有严格意义的私有属性
    };
    
    Object.defineProperties(obj,{
      age:{
        enumerable:true,
        configurable:true,
        get(value){
          return this._age;
        },
        set(value){
          this._age=value
        }
      }
    
    })
    console.log(obj);//* 将所有的属性打印,除了不可枚举的属性
    
    1.1.2 私有属性

    一般私有属性是以下划线开头,例如:_name

    但这种在外部还是可以访问到的,因为js没有严格意义上的私有属性

    1.2 对象方法补充

    1.2.1 获取对象的属性描述符
    • 获取对象的属性描述符

      • getOwnPropertyDescriptor

        • 语法

          Object.getOwnPropertyDescriptor(obj,prop)
          
        • 参数

          • obj:要获取的是哪个对象的属性的属性描述符
          • prop:对象的哪个属性
        • 返回值:返回的是一个属性描述符对象

      • getOwnPropertyDescriptors

        • 语法

          Object.getOwnPropertyDescriptors(obj)
          
        • 参数

          • obj:要获取哪个对象的所有属性描述符
        • 返回值

          • 返回的是所有属性描述符对象
    var obj={
      _age:18,//私有属性(js里面是没有严格意义的私有属性)
    };
    Object.defineProperties(obj,{
      name:{
        enumerable:true,
        configurable:true,
        writable:true,
        value:"wjy"
      },
      age:{
        enumerable:true,
        configurable:true,
        get(value){
          return this._age;
        },
        set(value){
          this._age=value
        }
      }
    
    })
    
    // * 获取特定属性的属性描述符
    console.log(Object.getOwnPropertyDescriptor(obj,"name"));
    console.log(Object.getOwnPropertyDescriptor(obj,"age"));
    
    //* 获取对象所有属性的属性描述符
    console.log(Object.getOwnPropertyDescriptors(obj));
    
    1.2.2 阻止对象添加新的属性
    Objet,preventExtensions(obj)
    

    示例代码如下:

    var obj={
      name:"wjy",
      age:18
    }
    //* 禁止对象添加新的属性
    Object.preventExtensions(obj)
    obj.height=1.99
    obj.address="怀化市"
    console.log(obj);//* { name: 'wjy', age: 18 }
    
    1.2.3 禁止对象配置或删除里面的属性(Object.seal(obj))

    Obect.seal()会将对象的所有属性变的不可配置

    var obj={
      name:"wjy",
      age:18
    }
    
    //* 禁止对象配置或删除里面的属性
    for(var key in obj){
      Object.defineProperty(obj,key,{
        configurable:false,
        enumerable:true,
        writable:true
      })
    }
    console.log(obj);
    

    可以转化为下面的:

    var obj={
      name:"wjy",
      age:18
    }
    Object.seal(obj)
    delete obj.name
    console.log(obj.name);//* 没有删除成功,设置对象的所有属性为不可配置
    
    1.2.4 让属性不可以修改(Object.freeze(obj))

    让每个属性都不可以被修改其值,相当于给每个属性的属性描述符添加了这个 writable:false

    我们可以有两种方式修改每个属性都不可以修改:

    • 遍历每个属性

      • 因为是在对象上直接定义的属性,这些属性的configurable、enumerable等都为true
      var obj={
        name:"wjy",
        age:18
      }
      //* 不允许对象的属性被修改其值
      for(let key in obj){
        Object.defineProperty(obj,key,{
          writable:false,//* 在这里只定义一个,但只是会覆盖之前定义的writable的值,但是enumerable、configurable等等还是之前设置的值
        })
      }
      obj.name="hyz"
      console.log(obj.name);
      delete obj.name;//因为configurable为true,所以是可以删除这个属性的
      console.log(obj.name);
      
      
    • 使用Object.freeze(obj)

      • 对象属性的值不能修改
      • 不能往这个对象添加新的属性
      • 不能删除对象的现有属性
      • 不能修改已有属性的可配置、可枚举、可写性
    var obj={
      name:"wjy",
      age:18
    }
    Object.freeze(obj)
    obj.name="hyz"
    console.log(obj.name);//wjy
    delete obj.name;
    console.log(obj.name); //wjy
    
    

    2.创建多个对象的方案

    • 如果我们现在希望创建一系列的对象:比如Person对象
      • 包括张三、李四、王五、李雷等等,他们的信息各部相同
      • 那么采用什么方式创建比较好

    2.1 对象字面量

    你会发现使用对象字面量创建同样类似的东西,代码会比较重复冗余,而且代码行数也在不断增加

    var p1={
      name:"张三",
      age:18,
      height:1.88,
      address:"广州",
      eating:function(){
        console.log(this.name+"在吃东西~");
      },
      running:function(){
        console.log(this.name+"在跑步~");
      }
    }
    var p2={
      name:"王五",
      age:20,
      height:1.92,
      address:"怀化",
      eating:function(){
        console.log(this.name+"在吃东西~");
      },
      running:function(){
        console.log(this.name+"在跑步~");
      }
    }
    ....
    

    2.2 工厂模式

    我们可以想到的一种创建对象的方式:工厂模式

    • 工厂模式其实是一种常见的设计模式
    • 通常我们会有一个工厂方法,通过该工厂方法我们可以产生想要的对象
    function createPerson(name,age,height,address){
      return{
        name,
        age,
        height,
        address,
        eating(){
          console.log(this.name+"在吃东西");
        },
        running(){
          console.log(this.name="在跑步");
        }
      }
    }
    var p1=createPerson("张三",18,1.81,"广州市")
    var p2=createPerson("王五",20,1.92,"怀化市")
    var p3=createPerson("李四",40,1.63,"长沙市")
    console.log(p1,p2,p3);
    
    2.2.1 缺点
    • 没有对应的类型,都是对象字面量,都是Object类型,获取不到对象最真实的类型

    2.3 认识构造函数

    工厂方法有一个比较大的问题:在我们打印这个对象时,对象的类型是Object类型

    • 但是从某些角度来说,这些对象应该有一个他们共同的类型:
    • 下面我们来看一下另外一种模式:构造函数的方式
    2,3,1 什么是构造函数
    • 构造函数也称为为构造器(constructor),通常是我们在创建对象时会调用的函数
    • 在其他面向对象的编程语言种,构造函数是存在于类中的一个方法,称之外构造方法
    • 但是javascript种的构造函数有点不太一样

    javascript的构造函数是怎么样的?

    • 构造函数也是一个普通的函数,从表现形式来说和千千万万个普通的函数没有任何区别
    • 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数被称之为是一个构造函数
    function foo(){
      console.log("foo");
    }
    // * foo是一个普通的函数
    foo()
    //*  如果通过new关键字调用这个函数,这个函数被称之为是一个i构造函数
    new foo();//* 如果不需要传参,这个小括号其实也是可以省略的
    new foo
    

    那么通过new关键字调用函数,和普通调用有什么区别呢?

    2.3.2 new操作符调用的作用

    如果一个函数被new操作符调用了,那么它会执行如下操作:

    1. 在内存中创建一个新的对象(空对象)
    2. 这个对象内部的[[prototype]]属性会被赋值为该函数的prototype属性
    3. 构造函数内部的this,会指向创建出来的新对象
    4. 执行函数的内部代码 (函数体代码)
    5. 如果构造函数没有返回其他对象,则返回创建出来的新对象
    /**
     * 如果一个函数被new操作符进行调用,它会执行如下操作
     * * 1 在内存中创建一个新的对象(对象)
     * * 2 将这个新对象的prototype属性赋值为这个函数的prototype属性
     * * 3 构造函数内部的this,指向这个创建的新对象
     * * 4 执行函数内部的代码
     * * 5 如果构造函数没有返回非空对象,则将这个新对象返回
     */
    function Person(name,age,address){
      this.name=name;
      this.age=age;
      this.address=address;
      this.eating=function(){
        console.log(this.name+"在吃东西.");
      }
      this.running=function(){
        console.log(this.name+"在跑步。");
      }
    }
    var p1=new Person('张三',19,"怀化");
    var p2=new Person('李四',22,'北京');
    console.log(p1);
    console.log(p2);
    console.log(p1==p2);
    // Person {
    //   name: '张三',
    //   age: 19,
    //   address: '怀化',
    //   eating: [Function],
    //   running: [Function]
    // }
    // Person {
    //   name: '李四',
    //   age: 22,
    //   address: '北京',
    //   eating: [Function],
    //   running: [Function]
    // }
    
    2.3.3 缺点

    比较浪费空间,对于一些共同使用的函数没必要每创建一个对象,就创建一个新的函数的空间

    2.4 构造函数和原型组合

    我们在上一个构造函数的方式创建对象时,有一个弊端:会创建重复的函数,比如running、eating这些函数

    • 那么有没有一种办法让所有的对象去共享这些函数呢?
    • 可以,将这些函数放入到构造函数的prototype的对象上即可。
    function Person(name,age,address){
      this.name=name;
      this.age=age;
      this.address=address;
    }
    Person.prototype.eating=function(){
      console.log(this.name+"在吃东西");
    }
    Person.prototype.running=function(){
      console.log(this.name+"在跑步。");
    }
    var p1=new Person("wjy",20,"怀化")
    var p2=new Person("hyz",20,"怀化")
    p1.eating()
    p2.eating()
    

    3.原型

    3.1认识对象的原型

    javascript当中的每个对象都有一个特殊的内置属性[[prototype]]属性,这个特殊的对象可以指向另外一个对象,它就是对象的原型(隐式原型)

    • 不能直接看到
    • 不能直接修改
    • 不能直接用到

    早期的ECMA是没有规范如何去查看 [[prototype]]

    浏览器给对象提供了一个属性,它可以去查看对象的[[prototype]]

    __proto__
    

    es5之后,提供了新的方法可以获取对象的[[prototype]]

    Object.getPrototypeOf(obj)
    

    其实获取对象的某个属性的值,会触发[[get]],然后会执行以下两个操作

    • 会从当前对象查找有没有这个属性,如果有,则返回
    • 没有的话则去对象的[[prototype]]查找,如果没有,则返回一个undefined
    // 每一个对象都有一个[[prototype]],这个属性可以称之为对象的原型(隐式原型)
    //* ①这个原型看不到  ②以后我们也不会改它 ③我们也不会直接用它
    var obj={name:'wjy'} //[[prototype]]
    var info={} //[[prototype]]
    
    // * 早期的ECMA是没有规范如何查看 [[prototype]]
    
    // * 给对象中提供了一个属性,可以让我们查看一下这个原型对象(浏览器提供)
    //* 这个属性是  __proto__
    
    console.log(obj.__proto__); // {}
    console.log(info.__proto__); //{}
    
    // * es5之后提供了 getPrototypeOf 获取原型对象 ,但是测试不方便
    console.log(Object.getPrototypeOf(obj));//{}
    
    
    //* 2.原型有什么用呢
    // * 当我们从一个对象中获取某一个属性时,它会触发 [[get]]操作
    // * 会做以下两个操作:(1)会从当前对象中查找对应的属性,如果还没找到 会从它的[[prototype]]查找
    obj.__proto__.like="hyz"
    console.log(obj.like);
    

    3.2 函数的原型理解

    function foo(){
    
    }
    //* 函数作为对象,也有[[prototype]](隐式原型的)
    // * __proto跟new Function()有关
    console.log(foo.__proto__); //[Function]
    
    //* 函数它因为是一个函数,所以它还多出来一个显式原型属性:prototype
    //* 在new操作符调用函数的第2个步骤:
    //这个新创建的对象的[[prototype]]指向构造函数的prototype
    // this.__proto__=foo.prototype
    console.log(foo.prototype);
    var f1=new foo();
    var f2=new foo();
    console.log(f1.__proto__==foo.prototype);//true
    console.log(f2.__proto__==foo.prototype);//true
    

    3.2 创建内存中的表现

    [图片上传失败...(image-d9c2a7-1651200561805)]

    3.4 constructor属性

    原型对象上其实有一个constructor属性,它指向的是当前这个函数,因为它是不可枚举的,所以直接打印或遍历或使用Object.keys都是无法返回该属性的

    function Person(){
    
    }
    console.log(Person.prototype);//Person {},它是指向这个函数
    console.log(Object.getOwnPropertyDescriptors(Person.prototype));//打印Person.prototype所有属性描述符
    

    [图片上传失败...(image-3e3e59-1651200561805)]

    console.log(Person.prototype.constructor); //[Function: Person]
    

    3.5重写整个原型对象

    如果我们需要在原型上定义很多个属性或方法,其实可以去重写原型对象

    3.5.1 未重写原型对象前
    function Person(){
     
    }
    
    Person.prototype.name="wjy"
    Person.prototype.age=28
    Person.prototype.address="怀化市"
    Person.prototype.eating=function(){
      console.log(this.name+"在吃东西。");
    }
    Person.prototype.running=function(){
      console.log(this.name+"在跑步。");
    }
    
    3.5.2 重写原型对象后
    • 每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获取constructor属性
    • 而我们这里相当于给prototype重新赋值了一个对象,那么这个新对象的prototype属性会指向Object构造函数,而不是Person构造函数
    console.log(Person.prototype.constructor); //[Function: Object]function Person(){
     
    }
    Person.prototype={
      name:"wjy",
      age:28,
      address:"怀化市",
      eating:function(){
        console.log(this.name+"在吃东西、");
      },
      running:function(){
        console.log(this.name+"在跑步。");
      }
    }
    console.log(Person.prototype.constructor); //[Function: Object]
    

    所以我们需要修改这个原型对象的constructor属性

    //* 给原型对象,添加constructor属性,它的值是这个函数,不可枚举、可配置、可写
    Object.defineProperty(Person.prototype,"constructor",{
      configurable:true,
      enumerable:false,
      writable:true,
      value:Person
    })
    

    4.总结

    对象补充、原型、函数原型、创建对象.png

    相关文章

      网友评论

          本文标题:JS 面向对象补充 构造函数 原型 对象原型 函数原型

          本文链接:https://www.haomeiwen.com/subject/krcsyrtx.html