zoukankan      html  css  js  c++  java
  • js创建对象的最佳方式

    1.对象的定义

    ECMAScript中,对象是一个无序属性集,这里的“属性”可以是基本值、对象或者函数

    2.数据属性与访问器属性

    • 数据属性即有值的属性,可以设置属性只读、不可删除、不可枚举等等

    • 访问器属性是用来设置getter和setter的,在属性名前加上”_”(下划线)表示该属性只能通过访问器访问(私有属性),但并不是说添个下划线就把属性变成私有的了,这只是习惯约定的一种命名方式而已。访问器属性没什么用,原因如下:

      var book={
        _year:2004,
        edition:1
      }
      Object.defineProperty(book,"year",{
             get:function(){
                return this._year;
             },
             set:function(newValue){
                if(newValue>2004){
                  this._year=newValue;
                  this.edition+=newValue-2004;
                }
             }
      });
      book.year=2005;
      alert(book.edition);
      /*
      for(var attr in book){
        showLine(attr + ' = ' + book[attr]);
      }
      */
      

      高程中使用了上面的示例代码,原理是book对象的属性中_year是数据属性,而year是访问器属性,利用gettter和setter可以插入读写控制,听起来不错。

      但问题是_year和edition都是可枚举的,也就是说用for...in循环可以看到,而访问器属性year却是不可枚举的。应该公开的访问器不公开,却把应该隐藏的私有属性公开了。

      除此之外,这种定义访问器的方式并不是全浏览器兼容的,[IE9+]才完整支持,当然,也有适用于旧浏览器的方式(__defineGetter__()和__defineSetter__()),还是相当麻烦。

      总之,访问器属性没什么用。

    3.构造函数

    function Fun(){} var fun = new Fun();其中Fun是构造函数,与普通函数没有任何声明方式上的区别,只是调用方式不同(用new操作符调用)而已

    构造函数可以用来定义自定义类型,例如:

    function MyClass(){
      this.attr = value;//成员变量
      this.fun = function(){...}//成员函数
    }
    

    与Java的类声明有些相似,但也有一些差异,比如this.fun只是一个函数指针,因此完全可以让它指向可访问的其它函数(如全局函数),但这样做会破坏自定义对象的封装性

    4.函数与原型prototype

    1. 声明函数的同时也创建了一个原型对象,函数名持有该原型对象的引用(fun.prototype)

    2. 可以给原型对象添加属性,也可以给实例对象添加属性,区别是原型对象的属性是该类型所有实例所共享的,而实例对象的属性是非共享的

    3. 访问实例对象的属性时,先在该实例对象的作用域中查找,找不到才在原型对象的作用域中查找,因此实例的属性可以屏蔽原型对象的同名属性

    4. 原型对象的constructor属性是一个函数指针,指向函数声明

    5. 通过原型可以给原生引用类型(Object,Array,String等)添加自定义方法,例如给String添加Chrome不支持但FF支持的startsWith方法:

      var str = 'this is script';
      //alert(str.startsWith('this'));//Chrome中报错
      String.prototype.startsWith = function(strTarget){
        return this.indexOf(strTarget) === 0;
      }
      alert(str.startsWith('this'));//不报错了
      

      注意:不建议给原生对象添加原型属性,因为这样可能会意外重写原生方法,影响其它原生代码(调用了该方法的原生代码)

    6. 通过原型可以实现继承,思路是让子类的prototype属性指向父类的实例,以增加子类可访问的属性,所以用原型链连接之后

      子类可访问的属性
      = 子类实例属性 + 子类原型属性
      = 子类实例属性 + 父类实例属性 + 父类原型属性
      = 子类实例属性 + 父类实例属性 + 父父类实例属性 + 父父类原型属性
      = ...
      

      最终父父...父类原型属性被替换为Object.prototype指向的原型对象的属性

      具体实现是SubType.prototype = new SuperType();可称之为简单原型链方式的继承

    5.创建自定义类型的最佳方式(构造函数模式 + 原型模式)

    实例属性用构造函数声明,共享属性用原型声明,具体实现如下:

        function MyObject(){
          //实例属性
          this.attr = value;
          this.arr = [value1, value2...];
        }
        MyObject.prototype = {
          constructor: MyObject,//保证子类持有正确的构造器引用,否则子类实例的constructor将指向Object的构造器,因为我们把原型改成匿名对象了
          //共享属性
          fun: function(){...}
        }
    

    6.实现继承的最佳方式(寄生组合式继承)

    function object(obj){//返回原型为obj的没有实例属性的对象
      function Fun(){}
      Fun.prototype = obj;
      return new Fun();
    }
    function inheritPrototype(subType, superType){
      var prototype = object(superType.prototype);//建立原型链,继承父类原型属性,用自定义函数object处理是为了避免作为子类原型的父类实例具有实例属性,简单地说,就是为了切掉除多余的实例属性,可以对比组合继承理解
      prototype.constructor = subType;//保证构造器正确,原型链会改变子类持有的构造器引用,建立原型链后应该再改回来
      subType.prototype = prototype;
    }
    
    function SubType(arg1, arg2...){
      SuperType.call(this, arg1, arg2...);//继承父类实例属性
      this.attr = value;//子类实例属性
    }
    inheritPrototype(SubType, SuperType);
    

    具体解释见代码注释,这种方式避免了SubType.prototype = new SuperType();简单原型链的缺点:

    • 子类实例共享父类实例引用属性的问题(因为原型引用属性是所有实例共享的,建立原型链后父类的实例属性就自然地成了子类的原型属性)。

    • 创建子类实例时无法给父类构造函数传参

    7.实现继承的最常用方式(组合继承)

    把上面的inheritPrototype(SubType, SuperType);语句换成:

    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    

    这种方式的缺点是:由于调用了2次父类的构造方法,会存在一份多余的父类实例属性,具体原因如下:

    第一次SuperType.call(this);语句从父类拷贝了一份父类实例属性给子类作为子类的实例属性,第二次SubType.prototype = new SuperType();创建父类实例作为子类原型,此时这个父类实例就又有了一份实例属性,但这份会被第一次拷贝来的实例属性屏蔽掉,所以多余。

  • 相关阅读:
    makefile中的使用 VAPTH 和“自动变量”指定目标文件的存放目录
    用Temp Table方法 替換遊標cursor
    生成XML、讀取XML
    Update方法的迭代
    串欄位list新招,不用建ufn.
    函數返回一個用分隔符連接起的字符串,其中每個單位的字符串不重復存在
    Select的迭代方法
    行列轉換方法
    行列轉換方法(超詳細方法)
    行列轉換方法(超過3列)
  • 原文地址:https://www.cnblogs.com/faithZZZ/p/7029082.html
Copyright © 2011-2022 走看看