js 面向对象总结

8,005 阅读7分钟

面向对象的程序设计(Object-Oriented OO)

最近感觉,不知道该学点什么,老是觉的自己什么都不会,又感觉是会点什么,说是也知道面向对象,但是让系统的说一下这里面的东西,感觉连不上线,成不了太系统的了解,所以就看了一下这的知识点,当自己的搬运工,假装是发了一篇文章,等自己查看方便;

创建对象

1. 工厂模式:用函数来封装,以特定接口创建对象的细节

  1. 解决了创建多个相似对象的问题,
  2. 多用于创建多个含有相同属性,和方法的对象,避免代码的重复编写;
  3. 没有解决对象识别的问题(即怎么知道一个对象的类型)(它的 instanceof 只能是Object)
   function creatPerson(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 p1 = creatPerson("hh",23,"worker");
    var p2 = creatPerson("xx",26,"worker1");
    p1.__proto__===p2.__proto__===Object.prototype;

    对象的constructor 属性最早是用来描述队形类型的,检测对象类型还是 instanceof 更可靠
    工厂模式创建的对象,constructor只有Object,这样的实例没有特定的类型;

2. 构造函数模式:

  1. 解决对象识别的问题(即怎么知道一个对象的类型)
  2. 缺点:每个方法都在每个实例上重新创建了一次,下面栗子中p1.sayName!=p2.sayName
  3. 解决方式:提出构造函数,在全局写一个函数申明;(这种解决方式的缺点:全局函数只是局部调用,方法过多就得创建多个全局函数,没什么封装性可言);
    function Person(name,age,job){
        this.age = age;
        this.name = name;
        this.job = job;
        this.sayName = function(){
            alert(this.name)
        }
        //this.sayName = sayName;   //解决方式
    }
    var p1 = new Person("hh",23,"worker");
    var p2 = new Person("xx",26,"worker1");
        
        //function sayName(){alert(this.name)}  //解决方式


    new的执行:
        {
            var obj ={};
            obj.__proto__ = Person.prototype;
            Person.call(obj);
            return obj
        }
    p1 instanceof Person //true;
    p2 instanceof Person //true;
    p1 instanceof Object //true;
    p2 instanceof Object //true;
    这样是得p1和p2实例有了特定的类型, Person;

3. 原型模式

  1. 优点:他省略了构造函数初始化参数这个环节,原型中所有属性都被很多实例共享,共享对函数非常合适,基本属性也还行 通过在实例上添加同名属性,可隐藏原型中的对应属性值;

  2. 缺点: 他的共享属性,对于包含引用类型值的属性 如果实例重新赋值没什么影响,和基本类型一样,如果是操作修改 就有些问题了,会使得所有实例获取到的该属性都被修改, 所以也不单独使用

        function Person(){}
        Person.prototype={
            constructor:Person,
            name:"ss",
            friends:["s1","s2"],
            sayName:function(){alert(this.name)}
        }
        var p1= new Person();
        var p2= new Person();
        p1.name = "p1"
        console.log(p1.name) //p1
        console.log(p2.name) //ss
        p1.friends.push("s3");
        console.log(p1.friends) //["s1","s2","s3"]
        console.log(p2.friends) //["s1","s2","s3"]
    
  3. 用法:

    1. 一般用法:
          function Person(){}
          Person.prototype.name="ceshi"
          Person.prototype.age =12;
          Person.prototype.sayName = function(){
              alert(this.name)
          }
          //这样就是实现了代码的共享
      
      
    2. 简单写法
      
          更简单的写法:
              Person.prototype={
                  //constructor:Person,
                  name:"test",
                  age:12,
                  sayName:function(){alert(this.name)}
              }
          var friend = new Person();
          friend instanceof Object //true
          friend instanceof Person //true
          friend.constructor==Person //false
          friend.constructor==Object //true
      
          这种简单的写法constructor 属性就不在指向Person,而是指向Object构造函数;
          此时可以添加一个属性constructor( 如上面注释):但是此时的constructor 变成了可枚举的属性,原生是不可枚举的,可以考虑用Object.defineProperty()
      
      
    3. 原型动态:
          var p1 = new Person();
          <!-- 1 -->
              Person.prototype.hh="ss";
              p1.hh//ss 是可以先创建实例在改边原型在访问的;
              
          <!-- 2 -->
              Person.prototype={
                  name:"ss"
              }
              p1.__proto__!=Person.prototype;
              p1.name // undefined 
      
      
    4. 原生对象的原型:
      1. 可以给原生引用类型(Object、Array、String。。。)添加修改方法
        String.prototype.strarWith=function(tesx){return this.indexOf(text)==0}

4. 组合使用构造函数模式和原型模式:(定义引用类型的一种默认模式)

  1. 构造函数模式用于定义实例属性; 每个属性在每个实例上都重新创建了一次;即使引用类型的修改也不会影响其他实例
  2. 原型模式用于定义方法和共享属性;
    function Person(age,name){
        this.name = name;
        this.age = age;
        this.friends=[1,2,3]
    }
    Person.prototype={
        constructor:Person,
        sayName:function(){
            alert(this.name)
        }
    }
    var p1 = new Person("ss",12);
    var p2 = new Person("ss3",123);
    p1.friends.push(2);
    console.log(p1.friends) // [1,2,3,2]
    console.log(p2.friends)// [1,2,3]

5. 动态原型模式:

  1. 原型和狗仔函数独立开,感觉和其他语言的OO不一样,动态原型就是把所有的信息都封装在构造函数中;
  2. 在构造函数中检查某个应该从在的方法是否有效,来觉得是不是初始化原型,其实就是初始化一次原型

    function Person(name,age){
        this.name = name;
        this.age = age;
        if(typeof this.sayName !="function"){//sayName没初始化 这里对一个方法判断就可以,然后初始化所有的,没必要都每个方法都判断

            Person.prototype.sayName=function(){alert(this.name)}
            Person.prototype.sayAge=function(){alert(this.age)};
            .....
        }
  ****注意****:此处不能使用对象字面量形式重写原型, 因为这中写法是先创建的实例,然后在修改的原型,要是用对象字面量重写原型,会切断现有实例和新原型之间的联系, 导致方法实例上无此方法;
    }

6. 寄生构造函数模式:

  1. 和工厂模式差不多,只是用了new 初始化实例;
  2. 它返回的对象与构造函数和构造函数的原型没有任何关系;
    function Person(name,age){
        var o = new Object();
        o.name=name;
        o.age=age;
        o.sayName=function(){
            alert(this.name)
        };
        return o;
    }
    var friends = new Person("xiaoming",12)
    friends.asyName()  // xiaoming

7. 稳妥构造函数模式:

稳妥对象: 没有公共属性,并且其方法中不引用this的对象;

function Person(name,age){
        var o = new Object();
        o.sayName=function(){
            alert(name)
        };
        return o;
    }
    var friends = new Person("xiaoming",12)
    friends.asyName()  // xiaoming

继承 :

  1. 继承一般包括: 接口继承:继承方法和签名; 实现继承:继承实际的方法;ECMAScript 只支持实现继承

  2. js主要通过原型链实现继承,原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的

继承方式

1. 原型链(很少单独使用)
  1. 问题:操作引用类型的数据(重新赋值不影响,相当于子类添加隐藏父类),会被所有实例共享受
  2. 问题er: 创建子类时候不能向父类的构造函数传递参数;???
    Son.prototype = new Father();
    
    //此时Son.constructor 被改写为了 Father;
    Son.prototype.constructor = Son;
    
    //给子类修改或者是添加方法的时候,要放在替换语句,改版子类原型语句之后, 
    //原型继承时,不能使用字面量方式创建原型方法,这样就重新写了原型链了
    Son.prototype.getName=function(){alert(this.name)};

2. 构造函数绑定: call ,apply 继承:(很少单独使用)
  1. 问题: 父函数原型(prototype)中的方法,子类是不可以见的
  2. 问题兔:方法属性都是在构造函数中定义的,每个实例和方法都重新创建一次,没有复用之说z
    function Son(){
        Father.call(this,arguments)
    }
3. 组合继承:(常用继承模式)
  1. 这样可以使得实例分别拥有各自的属性(包括引用类型的,实例间互补影响),又可以使用相同的方法;
  2. 缺陷: 无论什么情况下,都会调用两次父类的构造函数;
    function Father(){
        this.name ="ss";
        this.friends=[1,2,3,4]
    }
    function Son(){
        Father.call(this);
    }
    Son.prototype = new Father(); // Son 原型获得Father上的属性,name和friends


    var son1 = new Son(); // 此时调用Son构造函数为son1 实例上添加了属性(name和friends), 这些属性就屏蔽了原型中的同名属性;
    // 调用两次Father构造函数的结果就是有两组属性,一组在实例上,一组在原型上;

4. 原型式继承:
  1. 在没必要兴师动众创建构造函数,而是只想让一个对象与另一个对象保持类似的情况,可以使用, 类似钱拷贝;
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    // 和 Object.creat() 传递一个参数时候相同
5. 寄生式继承:
  1. 继承origin 的属性和方法;
  2. 同时还有自己的方法;
    function creat(origin){
        var clone = object(origin); // 可以是任何返回新对象的函数
        clone.sayName(){alert("name")}; //这里的函数每次都创建,不存在复用一说,
        return clone;
    }
6. 寄生组合式:(理想的继承实现方式)
  1. 解决组合继承中的缺陷,生成两组属性,只在实例上生成原型;
  2. 通过构造函数来继承属性,通过原型链的混成形式来继承方法(就是给子类原型,指定一个父类的副本即可)
    function inherit(son,father){

        var prototype = object(father.prototype); 
            // 上句话,创建父类原型的副本,prototype.__proto__ = Father.prototype;
            // prototype 继承constructor prototype.constructor 取的是原型链上,原型的Father.prototype.constructor, 为 Father();即:prototype.constructor == prototype.__proto__.constructor  // true
            // prototype.hasOwnPrototyoe("constructor") //false
        

        prototype.constructor = son; // 弥补重写原型造成的默认属性的修改;
            //此时是给prototype 添加了constructor 属性,赋值为son, 屏蔽了原型上的constructor属性
            // prototype.hasOwnPrototype("constructor") // true
            // prototype.constructor = son;
            // prototype.__proto__.constructor = father
        son.prototype  = prototype;
            // 给子类原型赋值为父类原型的副本;
    }

    //使用:
    function Father(){
        this.name="11";
        this.age=12;
    }
    Father.prototype.sayName=function(){
        alert(this.name);
    }
    function Son(){
        Father.call(this);
        this.age=23;
    }
    inherit(Son,Father);
    Son.prototype.sayAge=function(){
        alert(this.age)
    }