面向对象编程一

156 阅读12分钟

早吃撑、午吃撑、晚吃撑天天就是吃啊造啊吃完吐泡泡啊,每天都吃,也不胖这找谁说理去啊,最近虽然吃的频率比较少,但是真心吃了好几家环境不错又好吃的地方,主要我自己找能实力不太允许,大佬就不一样喽,不出手则已,一出手就是吃撑,哈哈哈哈哈。最近反正就是木空抽空也得写东西,因为我感觉我的js真的超烂,对于原生理解真的很浅,所以感觉自己触碰到了核心,那就忍不住就想一直触碰核心的内容,需要赶紧消化理解,如果不能消化总感觉自己有什么事情没有完成,这种感觉可是很不好吃饭不香,睡觉不香,干啥啥不香,哈哈哈哈反正就掌握了就很香啊。

1. 理解对象

  • 构造函数
      var person = new Object();
      person.name = "Nichplas" ;
      person.age = 29;
      person.job  = ""Software Engineer";
      person.sayName = function (){
          alert(this.name);
      }
    
  • 字变量语法
    var person = {
        name: "Nichplas",
        age:29,
        job:"Software Engineer",
        sayName:function(){
            alert(this.name);
        }
    }
    

2. 属性类别

  • 数据属性 数据属性包含了一个数据的值位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性
    • [[ Configurable ]]:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
    • [[ Enumerable ]]:表示能否通过for-in 循环返回属性。
    • [[ Writable ]]:表示能都修改属性的值
    • [[ Value ]]:包含这个属性的数据值。读取属性值的时候,从这位置读;写入属性值的时候,把新值保存在这个位置。默认为undefined。 Object.defineProperty()接受三个参数
   //Object.defineProperty(属性所在对象,属性名称,{})
   // 后边对象的属性一定是configurable、enumerable、writable、value设置其中一个或者多个的值
   var person = {};
   Object.definePeoperty(person,"name",{
       writable:false,
       value:"Nicholas"
   });
   alert(person.name); // Nicholas
   person.name = "lihua";
   alert(person.name); // Nicholas
  • 访问器属性 访问其属性不包含属性值;他们包含一个getter和setter函数(非必须)Vue的数据响应式其中就是使用了getter和setter函数,在读取访问器属性时,会调用getter函数返回有效的值;在写入访问器属性时,会调用setter函数并传入新值
    • [[ Configurable ]]:同数据属性的Configurable
    • [[ Enumberable ]]:同数据属性的Enumberable
    • [[ Get ]]:在读去属性时调用的函数。默认值为undfined.
    • [[ Set ]]:在写入属性时调用的函数 Object.defineProperty()定义访问器属性
var book = {
    _year :2020,
    edition:1
}
Object.defineProperty(book,"year",{
    get:function(){
        return this._year;
    },
    set:function(newValue){
        if(newValue>2020){
            this._year = newValue;
            this.edition + = newValue -2020;
        }
    }
})
book.year =2022; //触发set方法
alert(book.edition) // 触发get方法  3 
  • 定义多个属性 Object.defineProperties(),接受两个参数:第一个是添加和修改属性的对象,第二个是对象的属性与第一个对象中要添加或修改的属性一一对应
var book = {};
Object.defineProperties(book,{
    _year:{
        writable:true,
        value:2020
    },
    edition:{
        writable:true,
        value:1
    },
    year:{
        get:function(){
            return this._year
        },
        set:function(){
            if(newValue>2020){
                this._year =newValue;
                this.edition+ =  newValue -2020;
            }
        }
    }
})
  • 读取属性特性 Object.getOwnPropertyDescriptor(),接受两个参数:第一个是:属性所在的对象和要读取其描述符的名称
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value) ;  // 2020
alert(descriptor.configurable) ; //false

3. 创建对象

使用字变量或者new对象的方式虽然都可以创建对象,但明显有缺点,使用同一个借口创建很多对象,会产生大量的重复的代码

  • 工厂模式:把实现同一件事情的相同代码放到同一个函数中,想实现这个功能只要执行这个函数即可,这就是工厂模式,也叫做“函数的封装",这也是”低耦合,高内聚“,从而达到减少页面冗余码,提高代码重复利用率的作用. ```javascript function createPerson(name,age){ var person =new Object(); //创建一个新对象 //原料 person.name = name; person.age = age; //加工 person.showName = function(){ console.log("姓名:"+this.name); } person.showAge = function(){ console.log("年龄:"+this.age); } //出厂 return person; }
var p1 = createPerson("张三",45);
var p2 = createPerson("李四",20);

p1.showName();
p2.showName();
console.log(p1.showName==p2.showName); //false
```

缺点:一般我们创建对象是通过new来创建,比如new Date(),这里使用的是方法创建。(var p1=createPerson("张三",45))使用new来创建可以简化一些代码,也带来一些新的特性。 每个对象都有一套自己的方法,浪费资源 console.log(p1.showName==p2.showName); false 说明两个方法不一样 ,因为创建function()的时候其本质是通过new Function()来创建的,会诞生一个新的函数对象,造成资源浪费。

  • 构造函数模式:Array、Object、Function等这些都是内置类,所有的数组都是内置类Array的实例,所以的对象都是内置类Object的实例,所以函数都是Function的实例,而构造函数就是自定义一个类。
    • 构造函数写法
      function CreatePerson (name,age){  //首先构造函数 开头首字母大写
          this.name =name;
          this.age = age;
          this.showName = function (){
              console.log("姓名:"+ this.name);
          } ;
          this.showAge = function (){
              console.log("姓名:"+this.age);
          }
          //不用返回对象啦!!!
      }
    
      var p1 = new CreatePerson("张三",45);
      var p2 = new CreatePerson("李四",20);
      p1.showName();
      p2.showName();
      console.log(p1.showName==p2.showName); //false
      console.log(p1.constructor == CreatePerson) // true
      console.log(p2.constructor == CreatePerson) // true
      //1.我们看到这里使用了new方法来创建对象.
      //2.但是console.log(p1.showName==p2.showName); //false 依旧是false  说明还是存在浪费资源的问题.
    
    • 将构造函数当作对象 构造函数和其他函数的唯一的区别,就在于调用它们的方式不同。不过构造函数毕竟也是函数,不存在定义构造函数的特殊语法。
    // 构造函数
    var person =new CreatePerson("Nicholas",29);
    person.showName();
    // 作为普通函数
    CreatePerson("Greg",27);
    window.showName();
    // 在另一个对象的作用域中调用
    var o = new Object();
    Person.call(o,"kasare",25);
    o.showName();
    
    • 构造函数的问题 构造函数虽然很好用但是并不是没有缺点,使用构造函数主要问题就是每个方法都要在每个实例上重新创建一遍,上边的p1.showName和p2.showName不相等。创建两个完成同样任务的Function实例确实没有什么必要;况且有this对象在,根本不用在执行代码前就把函数对象绑定到特定对象上边
    function Person(name,age){
        this.name = name;
        this.age= age;
        this.showName = showName;
    }
    funtion showName(){
        alert(this.name);
    }
    
    这样由于showName包含的是只想函数的指针,因此实例对象就共享了全局作用域中的同一个showName函数,但是存在一个新的问题,全局作用域的函数,却只能被某个对象调用,这在全局作用域下有点不太优雅,还有就是万一有很多的方法,那就需要在全局作用域下定义很多方法。所以就有更好的方法来啦
  • 原型模式 javascript规定每个函数都有一个prototype(原型属性),这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。也就是说prototype就是通过调用构造函数而创建的那个对象实例的原型对象。说了半天也没明白是啥 哈哈哈 看看代码吧
    • 弥补构造函数的缺点
         function Person(){
      
         }
         Person.prototype.name = "Nicholas";
         Person.prototype.age = 29;
         Person.prototype.sayName =function(){
             alert(this.name);
         }
         var person1 = new Person();
         person1.sayName();  // "Nicholas"
         var person2 = new Person();
         person2.sayName();  // "Nicholas"
         alert(person1.sayName == person2.sayName); // true
      
    • 理解原型对象
      1. 只要创建了一个新的函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。所有的原型对象都会自动获得一个constructor(指向构造函数)的属性,就拿前边的栗子来说Person.prototype.constructor指向Person。 只要通过调用构造函数创建一个实例后,该实例的内部将包含一个指针(内部的属性),指向构造函数的原型对象。这个指针叫[[ Prototype ]] (protp)。明确最重要的一点就是这个链接存在于实例和构造函数的原型对象之间。来看一个关系图
      2. isPrototypeOf():[[ Prototype ]]在现实中都无法访问到,但是通过isPrototypeof()来确定对象之间是否存在这种关系。
        Person.prototype.isPrototypeof(person1);  // true
        Person.prototype.isPrototypeof(person2);  // true
        
      3. getPrototypeOf():Object.getPrototypeOf()返回[[ Prototype ]]的值。
      4. 如何进行搜索某个属性: 首先:从实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;没有找到进行下一步 然后:继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回属性的值。 当实例属性和原型对象的属性相同时,只会返回这个实例中的值
      5. hasOwnProperty():用于检测一个属性是存在于实例中还是存在于原型中,只有在给定的属性存在于对象实例中时,才会返回true。
        function Person(){};
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.sayName =function(){
            alert(this.name);
        }
        var person1 = new Person();
        var person2 = new Person();
        alert(person1.hasOwnProperty("name")) // false
        person1.name ="Greg"
        console.log(person1.name);
        alert(person1.hasOwnProperty("name")) //true
        
      6. 原型与in操作符 由于hasOwnProperty(),只能确定在实例中存在的时候才返回true,但是在没有该属性或者在原型中存在的时候则否返回false,那么如何判定属性在原型中呢?in操作符理解你的困扰,in操作符号可以判断属性无论在实例中还是在原型中都返回true的。
        // 通过定义这个函数就能判定属性存在与原型中
        function hasPrototypeProperty (object,name){
            return !object.hasOwnProperty(name)&&(name in object)
        }
        
      7. Object.keys():该方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
        function Person(){
        
        }
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.sayName =function(){
            alert(this.name);
        }
        var keys = Object.keys(Person.prototype);
        alert(keys);  // "name,age,job,sayName"
        var person1 = new Person();
        person1.name = "Rob";
        person1.age = 31;
        var p1keys =Object.keys(person1);
        alert(p1keys);  // "name,age"
        
      8. 更简单的原型语法 大家可能注意到了,上边的栗子中,每添加一个属性就要敲一遍Person.prototype。那肯定有更简单的写法吧,但是可以观察到下边的写法,Person.prototype 等于一个新的对象。造成了constructor不再指向Person了,本质上相当于重写了默认的prototype对象,如果constructor很重要,那就手动设置一下。
        function Person(){};
        Person.prototype ={
            constructor:Person, // 手动设置一下
            name:"Nicholas",
            age:29,
            sayName:function(){
                alert(this.name);
            }
        }
        
      9. 原型的动态性 由于在原型中查找过程是一次一次的查找,因此我们在原型中修改的内容立马会在实例中体现出来--即使先创建了实例后修改了原型
        var friend = new Person();
        Person.prototype.sayHi=function(){
            alert("hi");
        }
        friend.sayHi();  // "hi" 不会报错
        
        尽管可以随时为原型添加属性和方法,但是重写原型对象就不同了,当我们调用构造该函数时会为实例添加一个最初原型的[[ Prototype ]]指针,如果完全修改了原型那就切断了两者的联系
      10. 原生对象的原型 原生引用类型(Object、Array、String...)都在其构造函数的原型上定义了方法。比如Array.prototype中可以找到sort方法(我们现在所有直接调用的方法,其实都写在原型对象中);
      11. 原型对象也有问题 可以翻一翻我们上边的构造函数,小船别翻啊哈哈哈,都可以在函数中进行参数的传递,但是原型对象中并不能进行参数的传递。第二个重大的问题是,原型中的属性被很多实例共享,这种共享对于函数非常合适,但是那些包含引用类型值的属性来说,问题就比较大了。下面的例子要是我想有个自己全部属性怎么办? javascript function Person(){}; Person.prototype ={ constructor:Person, name:"Nicholas", age:29, friends:["Shelby","Count"], sayName:function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); // "Shelby,Count,Van" alert(person2.friends); // "Shelby,Count,Van"
  • 组合使用构造函数模式和原型模式 解决单独使用原型模式,而导致的共享的属性,不能拥有自己的实例属性副本
      function Person(name,age){
          this.name = name;
          this.age= age; // 想要独有的属性都放在构造函数里边 
          this.showName = showName;
      }
      Person.prototype ={
          constructor:Person, 
          sayName:function(){
              alert(this.name);
          }
      }
      var person1 = new Person();
      var person2 = new Person();
      person1.friends.push("Van");
      alert(person1.friends); // "Shelby,Count,Van"
      alert(person2.friends); // "Shelby,Count"
    
  • 动态原型模式 动态原型模式致力于吧所有信息封装在构造函数里边,而通过构造函数中初始化原型,又保持了同时使用构造函数和原型的优点,换句话说就是,可以通过检查某个应该存在 的方法是否有效,来决定是否需要初始化原型。
    function (name,age){
      this.name = name;
      this.age= age; 
      if(typeof this.sayName!="function"){
          Person.prototype.sayName = function(){
              alert(this.name);
          }
      }
    }
    
  • 寄生构造函数模式 这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。下面是一个例子。除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的
      function Person(name, age, job){
          var o = new Object();
          o.name = name;
          o.age = age;
          o.job = job;
          o.sayName = function(){
              alert(this.name);
          };    
          return o;
      }
      
      var friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName();  //"Nicholas"
    
    这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。
      function SpecialArray(){
          //创建数组
          var values = new Array();
      
          //添加值
          values.push.apply(values, arguments);
      
          //添加方法
          values.toPipedString = function(){
              return this.join("|");
          };
      
          //返回数组
          return values;
      }
      var colors = new SpecialArray("red", "blue", "green");
      alert(colors.toPipedString()); //"red|blue|green"
    
  • 稳妥构造函数模式 在了解稳妥构造函数模式之前,先了解下稳妥对象这个概念。稳妥对象指没有公共属性,而且其他方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者防止数据被其他应用程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有亮点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。如:
    function Persion(name, age, job) {
      // 创建要返回的对象
      var o = new Object();
      // 添加方法
      o.sayName = function() {
          alert(name);
      }
      return o;
    }
    var p1 = Persion('bill', 23, 'FE');
    p1.sayName() // bill;
    
    以上代码变量p1中保存的是一个稳妥对象,而除了调用sayName()外,没有别的方式可以访问其他数据成员。