ECMA_OOP

头像
alogy
    阅读 10 分钟
    1

    对象

    对象数据类型的作用:把描述同一个事物(同一个对象)的属性和方法放在同一个内存空间下,起到了分组的作用。

    效果:不同事物之间的属性即使属性名相同,相互也不会发生冲突。

    单例模式

    单例模式: 把各种属性相同的归类,分组的编程模式

    作用:防止冲突

    // 在单例模式中,把person叫做命名空间
    var person = {
      name: 'ling',
      age: 24
    };

    把描述同一件的事件,属性,方法,放在同一个对象下,放在命名空间中.
    命名空间:开辟堆内存,把属性名和属性值存储起来,地址赋值给变量,然后该变量就是作为明明空间的表示存在的,给开辟的空间起个名字叫做命名空间。

    命名空间的特点:相互独立,而不冲突。

    模块化开发

    利用单例模式做模块化开发。

    模块化开发:对于一个相对来说比较大型项目,需要多人协作的开发,一般情况下会根据当前项目的需求划分成几个功能板块,每个人负责一部分,同时开发,最后把每个人的代码进行合并。

    缺点: 生产效率低下,不能实现批量生产.

    工厂模式

    工厂模式:
    把实现同一件事情的相同代码放到一个函数中. --> 函数的封装 --> 低耦合,高内聚 (作用:减少页面中的冗余代码,提高代码的重复利用率)
    耦合 --> 相同
    内聚 --> 重复利用率

    function Person (name, age) {
        var obj = {};
        obj.name = name;
        obj.age = age;
        obj.writeJS = function() {
           console.log(this.name);
        }
        return obj;
    }

    工厂模式中引出的一些概念:

    继承:子类继承父类中的属性和方法
    
    多态:当前方法的多种形态
    后台语言中,多态包含重载(方法名相同,参数不同,参数不同包含:参数类型和参数个数)和重写 
    JS 不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保存一个。 // 3year,6year  // 5year,8year,10year
    重写:子类重写父类的方法
    
    封装:函数的封装,作用:低耦合,高内聚。

    构造函数模式

    构造函数模式的目的:
    创建一个自定义类,并且创建这个类的实例.

    构造函数模式和工厂模式的区别?

    执行方式不同

    1. 工厂模式普通方式执行 函数名()

    2. 构造函数通过new函数名() 通过new执行后,定义的函数称之为类。而函数执行后的返回值,就是当前类的实例

    3. 在构造函数当中, 类中(函数中)出现的this.xxx = xxx; this指代当前类的一个实例,不同实例之间的方法和属性是不同的。

    注意点:

    1. 类的首字母大写

    2. ES5中所有的class都是数据类型,类和普通函数一样,都形成一个私有作用域,然后形参赋值-->预解析-->代码从上到下执行.

    3. JS中所有的类都是函数数据类型的,它通过new执行变成一个类,但是实际上本身也是一个普通函数,JS中所有的实例都是对象数据类型的.

    构造函数细节

    在构造函数模式中new 函数名()执行,如果函数名()不需要传递参数的话,后面的小括号可以省略.

    this的问题,在类中出现的this.xxx = xxx中this都是当前类的实例,而方法中的this需要看方法执行的时候,前面是否有.点才能知道this是谁.

    function Fn() {
      this.x = 10;
      this.getX = function() {
        // 需要看getX执行时候才知道.
        console.log(this, 'this');
        console.log(this.x);
      }
    }
    
    var f1 = new Fn();
    f1.getX(); // 方法中的this是f1 --> 100
    
    var ss = f1.getX; // 方法中的this是window  --> undefined
    ss();

    类有普通函数的一面,当函数执行的时候,var 变量名其实只是当前形成的私有作用域中的私有变量而已,和实例没有任何关系.

    在构造函数模式中,浏览器会默认的把实例返回(返回的是一个对象数据类型)。如果手动写了return语句:

    1. 返回的是一个基本数据类型的值,当前实例是不变的

    2. 返回的是一个引用数据类型 的值,当前的实例会被自己return的引用数据类型的值.

    检测某一个实例对象是否属于类,使用:instanceof
    检测一个属性是否属于对象,使用:in , 返回true,是当前属性.
    检测一个私有属性是否属于对象,(这个方法检测私有属性)只要有存在私有属性,就返回true。不管是否存在共有属性都返回true.

    console.log(window.hasOwnProperty('alert')); // true

    检测一个对象是否是共有属性:

    function hasPubPrototy(obj, attr) {
      // 保证是它的一个属性并且还不是私有的属性.
      return (attr in obj) && !obj.hasOwnPrototype(attr);
    }

    isPrototypeOf 测试一个对象是否存在于另一个对象的原型链上

    原型链模式

    构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的 称之为实例识别

    基于构造函数模式的原型模式解决了:方法或者属性共有的问题(把实例之间相同的属性和方法提取成公有的属性和方法)

    规则:

    1. 每一个函数数据类型(普通类型,类)都有一个天生自带的属性:prototype, 并且这个属性是一个对象数据类型的值.

    2. 并且在prototype上浏览器天生给它加了一个属性constructor(构造函数),属性值是当前函数(类)本身.

    3. 每一个对象数据类型(普通对象,实例,protptype,内置对象...)也天生自带一个属性:__proto__, 属性值是当前所属类的原型.

    JS中所有的类都是函数数据类型.
    所有的对象类型都是Object类的实例. (对象不知道是哪个类的实例,都属于Object类的实例)

    Object是所有数据类型的基类(最底层的类)
    Object.prototype上没有__proto__这个属性.

    clipboard.png

    原型链:

    通过对象名.属性名的方式获取属性值的时候。

    1. 首先在对象的私有属性中查找,如果私有属性中存在这个属性,则获取的是私有属性值

    2. 如果私有属性没有,则通过__proto__找到所属类的原型。(类的原型上定义的属性和方法都是当前实例的共有的属性和方法)

    3. 如果原型上存在的话,获取的是共有的属性值

    4. 如果原型上也没有的话,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype为止.

    5. 基类上的也不存在,则是null

    批量设置原型上的共有属性和方法

    别名

    function Fn() {
      this.x = 100;
      this.y = 200;
      this.z = 300;
    }    
    var pro = Fn.prototype; // 把原来原型指向的地址赋值给pro变量,操作同一个内存空间
    pro.getX = function() {
      console.log(this.x);
    }
    pro.getY = function() {
      console.log(this.y);
    }
    pro.getZ = function() {
      console.log(this.y);
    }
    
    var f1 = new Fn();

    重构原型对象

    浏览器天生给Fn.prototpye开辟的堆内存里边才有constructor,而如果指向新的 {}对象,没有constructor属性.这样constructor指向就不是Fn而是Object。
    为了和原来浏览器天生的Fn.prototype保持一致,需要手动的添加constructor指向

    clipboard.png

    使用Fn.prototype重新指向的方式增加内置类共有属性

    Array.prototype = {
      construcotr: Array,
      unique: function() {
    
      }
    }
    console.log(Array.prototype);
    // 这种方式会把已经存在原型上的共有方法替换掉,所以使用`修改Fn.prototype指向的方法`修改内置类的话,浏览器是屏蔽掉的

    原型模式中this的情况

    1. 在类中this.xxx = xxx; this指代当前类的实例.

    2. 在某一个方法(私有+共有)中的this,看执行的时候.前面是谁,this就是谁

      确定this是谁,然后把this替换成对应代码,最后按照原型链查找的机制,一步步的查找结果.
      
      
    function Fn() {
      this.x = 100;
      this.y = 200;
      this.getY = function() {
        console.log(this.y);
      }
    }
    
    Fn.prototype = {
      constructor: Fn,
      y: 300,
      getX: function() {
        console.log(this.x);
      },
      getY: function() {
        console.log(this.y);
      }
    }
    
    var f = new Fn();
    f.getX(); // 100
    f.__proto__.getX(); // undefiend
    Fn.prototype.getX(); // undefined
    
    f.getY(); // 200
    f.__proto__.getY(); // 300
    Fn.prototype.getY(); // 300

    数组去重和链式调用

    
    var arr = [123, 12, 2, 34, 231, 324234];
    // 数组去重
    Array.prototype.Unique = function() {
      // this
      var obj = {};
      for (var i=0; i<this.length; i++) {
        var current = this[i];
        if (obj[current] == current) {
          // 去除重复项
          current = this[this.length-1];
          this.length--;
          i--;
          continue;  
        }
        obj[current] = current;
      }
      obj = null;
    }
    
    arr.Unique();
    
    arr.sort(function(a, b) { return a-b; }).reverse().pop();
    // 链式写法:
    // arr为什么可以使用sort方法?
    // 因为sort是Array.prototype上的公有方法,而数组arr是Array这个类的实例,
    // 所有arr可以使用sort方法. --> 数组才能使用我们Array原型上定义的属性和方法

    实现需求:
    (5).plus(10).reduces(2) // 5+10-2 // 13

    Number.prototype.plus = function(n) {
      console.log(this);
      return this + n;
    }    
    Number.prototype.reduces = function(n) {
      return this-n;
    };
    (5).plus(10).reduces(2); // 13

    继承

    可枚举和不可枚举

    for-in 循环只遍历私有属性和自定义共有属性(默认可以把自己私有属性和它所属类的原型上扩展的属性和方法都可以枚举/遍历到),但是一般情况下只需要遍历私有属性。

    方法1:
    prototypeIsEnumerable(); 判断可枚举的私有属性

    Object.prototype.test = function() {}
    var obj = {name: 'ss', age: 10};
    
    for (var key in obj) {
      if (obj.prototypeIsEnumerable(key)) {
        console.log(obj[key]);
      }
    }

    方法2:
    hasOwnPrototype(); 判断是私有属性

    Object.prototype.test = function() {}
    var obj = {name: 'ss', age: 10};
    for (var key in obj) {
      if (obj.hasOwnPrototype(key)) {
        console.log(obj[key]);
      }
    }

    Object.create()

    创建一个拥有指定原型和若干个指定属性的对象.

    Object.create(proObj)

    1. 创建对象

    2. 把第一个参数proObj作为当前对象的原型

    var obj = {
      getX: function() {}
    };
    var obj2 = Object.create(obj);
    obj.getX();
    
    obj.getY = function() {
      console.log(0);
    }
    
    obj2.getY(); // 0

    模拟Object.create();

    // 模拟Object.create();
    function object(obj) {
      function Tmp() {}
      Tmp.prototype = obj;
      return new Tmp();
    }

    使用:

    function object(obj) {
      function Tmp() {}
      Tmp.prototype = obj;
      return new Tmp();
    }
    
    var obj = {
      x: 100
    }
    
    var newObj = object(obj);
    
    function Sum() {}
    Sum.prototype = object(obj);
    Sum.prototype.construcotr = Sum; // 优点:可以操作各个层级的对象,不影响其他对象。

    原型继承

    子类继承父类的所有属性和方法(私有+公有)方法:子类.prototype = new 父类

    特点:把父类中的私有+公有的都继承了子类原型上(子类共有).

    // #div1.__proto__ -> HTMLDivElement.prototype -> HTMLElement.prototype -> Element.prototype -> Node.prototype -> EventTarget.prototype(DOM二级事件) —> Object.prototype
    
    function _Object() {}
    
    _Object.prototype = {
      constructor: Object,
      hasOwnPrototype: function() {},
      toString: function() {}
    }
    
    function _EventTarget() {}
    _EventTarget.prototype = new _Object();
    _EventTarget.prototype.addEventListener = function() {}
    
    function _Node() {}
    _Node.prototype = new _EventTarget();
    _Node.prototype.createElement = function() {}
    
    var node = new _Node();
    
    // 子类的原型 等于 父类的实例

    原型继承,并不是把父类的属性和方法克隆一份一模一样给子类.而是在子类和父类之间增加了原型链的连接.子类需要父类的方法,需要一级级向上查找。

    function A() {
      this.x = 100;
    }
    A.prototype.getX = function() {
      return this.x;
    }
    
    function B() {
      thix.x = 200;
    }
    B.prototype = new A();
    
    var b = new B();

    clipboard.png

    call继承

    特点:call把父类私有属性和私有方法,克隆一份,作为子类的私有属性和私有方法.

    function A() {
      this.x = 100;
    }
    A.prototype.getX = function() {
      console.log(this.x);
    }
    function B() {
      // this -> b
      A.call(this); // A.call(b); 把A函数 执行,让A函数中的this变为b实例. 和父类没有任何关系
    }
    var b = new B();

    冒充对象继承

    /*
      冒充对象继承
      把父类私有+共有克隆一份一模一样给子类私有
    */
    
    function A() {
      this.x = 100;
    }
    A.prototype.getX = function() {
      console.log(this.x);
    }
    
    function B() {
      var tmp = new A();
      for (var key in tmp) {
        if (tmp.prototypeIsEnumerable(key)) {
        }
        this[key] = tmp[key];
      }
      tmp = null; 
    }
    var b = new B();
    

    混合模式继承

    混合模式继承:原型模式+call继承

    function A() {
      this.x = 100;
    }
    A.prototype.getX = function() {
      return this.x;
    }
    
    function B() {
      A.call(this);
    }
    
    B.prototype = new A();
    B.prototype.constructor = B;
    
    var b = new B();

    寄生组合式继承

    父类的原型给了子类的原型

    function A() {
      this.x = 100;
    }
    A.prototype.getX = function() {
      return this.x;
    }
    
    function B() {
      A.call(this);
    }
    B.prototype = Object.create(A.prototype); // 父类的原型给了子类的原型
    B.prototype.constructor = B;
    
    var b = new B();

    中间类继承

    function avgFn() {
      Array.prototype.sort.call(function(a, b) {
        return a - b;
      });
      Array.prototype.pop.call(arguments);
      Array.prototype.shift.call(arguments);
      return (eval(arguments.join('+') / arguments.length)).toFixed(2);
    }
    
    // 中间类继承
    function avgFn() {
      arguments.__proto__ = Array.prototype;  // Array.prototype.slice.call(arguments);
      arguments.sort(function(a, b) {
        return a - b;
      }).pop().shift();
      return (eval(arguments.join('+') / arguments.length)).toFixed(2);
    }

    原型中的this

    原型中的公共方法执行的时候,this是谁

    看方法执行的时候,. 点前面是谁,this就是谁

    function Fn() {
      this.x = 100;
      this.getX = function() {
        console.log(this.x);
      }
    }
    
    Fn.prototype.getX = function() {
      console.log(this.x);
    }
    Fn.prototype.setX = function(n) {
      this.x = n;
    }
    
    var f1 = new Fn();
    var f2 = new Fn();
    
    f1.getX(); // 100
    f1.__proto__.getX(); // this->f1.__proto__, console.log(f1.__proto__.x); // undeinfed
    Fn.prototype.setX(300); // this--> Fn.prototype, Fn.prototype.x = 300 // 在公有增加x属性
    f1.getX(); // 100
    f1.__proto__.getX(); // 300
    f1.setX(500); // 修改私有属性x
    f1.getX(); // 500
    f1.__proto__.getX(); // 300
    
    
    f1.y = 1000; // 给f1本身增加一个私有属性和f2没有关系
    f1.__proto__.y = 2000; //  在原型上增加一个y=1000,f2也可以获取y的值。

    内置类扩展

    内置类的原型扩展方法

    只要在当前实例原型链上,不管那一个类instancofe检测出来的都为true

    Array内置类的原型:

    clipboard.png

    扩展数组去重:

    Array.prototype.unique = function unique() {
      var obj = {};
      for (var i=0; i<this.length; i++) {
        var current = this[i];
        if (obj[current] == current) {
          this[i] = this[this.length-1]; // 12 121
          this.length--;
          i--;
          continue;
        }
        obj[current] = current;
      }
      obj = null;
      return this;
    }

    基于内置类的原型扩展方法,注意,不要冲突,需要加特殊前缀,防止覆盖已经存在的内置方法.

    惰性思想

    作用: 优化经常被调用的函数

    // 惰性思想:第一次在给utils赋值的时候我们就已经把兼容性处理好了,把最后的结果存放在flag变量中,以后再
    // 每个方法中,只要是ie6,7,8,不兼容的,不需要重新的检测, 只需要使用flag的值即可。
    var utils = (function() {
      // 统一通过一个变量来检测 ie 6,7,8.
      var flag = 'getComputedStyle' in window; // flag存储的变量不销毁,存储的是当前浏览器是否兼容getComputedStyle。 false,当前浏览器是ie6,7,8
      return {
        listToArray: function(likeArr) {
          // var arr = [];
          // try {
          //   arr = Array.prototype.slice.call(likeArr, 0);
          // } catch(e) {
          //   for (var i=0; i<linkArr.length; i++) {
          //      arr[arr.legnth]  = linkArr[i];
          //   }
          // }  
          // return arr; 
    
          if (flag) {
            return Array.prototype.slice.call(likeArr, 0);
          }
          var arr = [];
          for (var i=0; i<linkArr.length; i++) {
            arr[arr.legnth]  = linkArr[i];  
          }
          return arr;
        },
        formatJSON: function(jsonStr) {
          if (flag) {
            return eval('(' + jsonStr + ')');
          }
          return JSON.parse(jsonStr);
          // return "JSON" in window ? JSON.parse(jsonStr) : eval('(' + jsonStr + ')');
        }
      }
    })();

    alogy
    1.3k 声望121 粉丝

    // Designer and Developer