首頁 >web前端 >js教程 >jQuery 3.0 的 setter和getter 模式詳解

jQuery 3.0 的 setter和getter 模式詳解

高洛峰
高洛峰原創
2017-01-03 17:13:331142瀏覽

jQuery 的 setter/getter 共用一個函數,透過是否傳參來顯示它是何種意義。簡單說傳參它是 setter,不傳它是 getter。

一個函數具有多種意義在程式語言中並不罕見,例如函數重載:一組具有相同函數名,不同參數列表的函數,這組函數被稱為重載函數。重載的好處是減少了函數名的數量,避免了名字空間的污染,對於程式的可讀性也大有裨益。

函數重載主要體現的兩個方面,一是參數的類型、相同個數的參數類型不同可稱為函數重載;二是參數的個數,個數不同也稱為函數重載。注意,重載與函數的回傳值並無關係。

由於 JS 弱類型的特徵,想模擬函數重載就只能用第二種方式:參數的個數來實現。因此函數內的 arguments 物件就顯得非常重要。

以下是一個範例

function doAdd() {
var argsLength = arguments.length
if (argsLength === 0) {
return 0
} else if (argsLength === 1) {
return arguments[0] + 10
} else if (argsLength === 2) {
return arguments[0] + arguments[1]
}
}
doAdd() // 0
doAdd(5) // 15
doAdd(5, 20) // 25

doAdd 透過判斷函數的參數個數重載實現了三種意義,argsLength 為0 時,直接傳回0; argsLength 為1 時,此參數與10 相加;argsLength 為2 時兩個參數相加。

利用函數重載特性可以實現 setter/getter

function text() {
var elem = this.elem
var argsLength = arguments.length
if (argsLength === 0) {
return elem.innerText
} else if (argsLength === 1) {
elem.innerText = arguments[0]
}
}

以上簡單的解釋了函數重載及利用它實現 setter/getter。即"取值器"與"賦值器"合一。到底是取值還是賦值,由函數的參數決定。 jQuery 的許多 API 設計大量使用了這種模式。

下圖匯總了jQuery 中採用這種模式的所有API,共14 個函數

jQuery 3.0 的 setter和getter 模式详解

所有這些函數內部都依賴另一個函數access, 毫不誇張的說access 是所有這些函數的核心,是實現setter/getter 的核心。下面是這個函數的原始碼,它是一個私有的函數,外部是呼叫不到它的。

jQuery 3.0 的 setter和getter 模式详解

access 的源碼如下

// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
  var i = 0,
    len = elems.length,
    bulk = key == null;
  // Sets many values
  if ( jQuery.type( key ) === "object" ) {
    chainable = true;
    for ( i in key ) {
      access( elems, fn, i, key[ i ], true, emptyGet, raw );
    }
  // Sets one value
  } else if ( value !== undefined ) {
    chainable = true;
    if ( !jQuery.isFunction( value ) ) {
      raw = true;
    }
    if ( bulk ) {
      // Bulk operations run against the entire set
      if ( raw ) {
        fn.call( elems, value );
        fn = null;
      // ...except when executing function values
      } else {
        bulk = fn;
        fn = function( elem, key, value ) {
          return bulk.call( jQuery( elem ), value );
        };
      }
    }
    if ( fn ) {
      for ( ; i < len; i++ ) {
        fn(
          elems[ i ], key, raw ?
          value :
          value.call( elems[ i ], i, fn( elems[ i ], key ) )
        );
      }
    }
  }
  return chainable ?
    elems :
    // Gets
    bulk ?
      fn.call( elems ) :
      len ? fn( elems[ 0 ], key ) : emptyGet;
};

該函數的註解提到:這是一個多功能的函數,用來取得和設定一個集合元素的屬性和值。 value 可以是一個可執行的函數。這個函數一共不到 60 行程式碼。從上往下讀,第一個 if 是設定多個 value 值,是一個遞歸呼叫。刨去這個遞歸調用,設定單一值的程式碼也就不到 50 行了。寫的非常簡練、耐讀。

為了理解access 函數,我畫了兩個圖

access 內部兩個主要分支

jQuery 3.0 的 setter和getter 模式详解

access 內部的執行流程

jQuery 3.0 的 setter和getter 模式详解

access 內部的執行流程

形.elems 元素集合,實際呼叫時傳的都是this,這裡的this 是jQuery 對象,我們知道jQuery 物件本身就是一個集合,具有length 屬性和索引。必傳。

2.fn 實作 setter/getter 的函數,就是說這個函數裡需要有條件能判斷哪一部分是 setter,哪部分是 getter。必傳。

3.key 例如 attr 和 prop 方法要傳,設定或取得哪個 key 的值。有的則不用傳,但為了佔位用以 null 替代,例如 text、html 方法。可選。

4.value 只有在 setter 時要傳,即 value 為 undefined 時是 getter,否則是 setter。可選。

5.chainable 當為 true 時,進入 setter 模式,會傳回 jQuery 物件。 false 則進入 getter模式。呼叫時透過 arguments.length 或 arguments.length>1 傳入。

6.emptyGet 當 jQuery 物件為空時,傳回的結果,預設不傳為 undefined,data 方法呼叫時傳的是 null。

7.raw 當 value 為函數型別時 raw 為 false,否則為 true。

上面提到了 access 是 jQuery 所有 setter/getter 函數的核心,換句話說所有 14 個函數 setter/getter 函數內部都會呼叫 access。這也是為什麼 access 有 7 個參數,裡面分支眾多。因為它要處理的各種條件就很多呢。但所有這些 setter/getter 有很多類同的程式碼,最後還是提取一個公共函數。

為了方便理解,我把 access 的調用分類以下,以便於我們理解。

1. 呼叫 access 時,第三個參數 key 傳值為 null,分別是 text/html 方法

text: function( value ) {
  return access( this, function( value ) {
    return value === undefined ?
      jQuery.text( this ) :
      this.empty().each( function() {
        if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
          this.textContent = value;
        }
      } );
  }, null, value, arguments.length );
},
html: function( value ) {
  return access( this, function( value ) {
    var elem = this[ 0 ] || {},
      i = 0,
      l = this.length;
    if ( value === undefined && elem.nodeType === 1 ) {
      return elem.innerHTML;
    }
    // See if we can take a shortcut and just use innerHTML
    if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
      !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
      value = jQuery.htmlPrefilter( value );
      try {
        for ( ; i < l; i++ ) {
          elem = this[ i ] || {};
          // Remove element nodes and prevent memory leaks
          if ( elem.nodeType === 1 ) {
            jQuery.cleanData( getAll( elem, false ) );
            elem.innerHTML = value;
          }
        }
        elem = 0;
      // If using innerHTML throws an exception, use the fallback method
      } catch ( e ) {}
    }
    if ( elem ) {
      this.empty().append( value );
    }
  }, null, value, arguments.length );
},

圖示這兩個方​​法在 access 內部執行處

jQuery 3.0 的 setter和getter 模式详解

为什么 key 传 null,因为 DOM API 已经提供了。text 方法使用 el.innerText 设置或获取;html 方法使用 innerHTML 设置或获取(这里简单说,实际还有一些异常处理)。

2. 与第一种情况相反,调用 access 时 key 值传了且不为 null。除了 text/html 外的其它 setter 都是如此

attr: function( name, value ) {
  return access( this, jQuery.attr, name, value, arguments.length > 1 );
},
prop: function( name, value ) {
  return access( this, jQuery.prop, name, value, arguments.length > 1 );
},
// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
  var top = "pageYOffset" === prop;
  jQuery.fn[ method ] = function( val ) {
    return access( this, function( elem, method, val ) {
      var win = getWindow( elem );
      if ( val === undefined ) {
        return win ? win[ prop ] : elem[ method ];
      }
      if ( win ) {
        win.scrollTo(
          !top ? val : win.pageXOffset,
          top ? val : win.pageYOffset
        );
      } else {
        elem[ method ] = val;
      }
    }, method, val, arguments.length );
  };
} );
css: function( name, value ) {
  return access( this, function( elem, name, value ) {
    var styles, len,
      map = {},
      i = 0;
    if ( jQuery.isArray( name ) ) {
      styles = getStyles( elem );
      len = name.length;
      for ( ; i < len; i++ ) {
        map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
      }
      return map;
    }
    return value !== undefined ?
      jQuery.style( elem, name, value ) :
      jQuery.css( elem, name );
  }, name, value, arguments.length > 1 );
}
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
  jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
    function( defaultExtra, funcName ) {
    // Margin is only for outerHeight, outerWidth
    jQuery.fn[ funcName ] = function( margin, value ) {
      var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
        extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
      return access( this, function( elem, type, value ) {
        var doc;
        if ( jQuery.isWindow( elem ) ) {
          // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
          return funcName.indexOf( "outer" ) === 0 ?
            elem[ "inner" + name ] :
            elem.document.documentElement[ "client" + name ];
        }
        // Get document width or height
        if ( elem.nodeType === 9 ) {
          doc = elem.documentElement;
          // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
          // whichever is greatest
          return Math.max(
            elem.body[ "scroll" + name ], doc[ "scroll" + name ],
            elem.body[ "offset" + name ], doc[ "offset" + name ],
            doc[ "client" + name ]
          );
        }
        return value === undefined ?
          // Get width or height on the element, requesting but not forcing parseFloat
          jQuery.css( elem, type, extra ) :
          // Set width or height on the element
          jQuery.style( elem, type, value, extra );
      }, type, chainable ? margin : undefined, chainable );
    };
  } );
} );
data: function( key, value ) {
  var i, name, data,
    elem = this[ 0 ],
    attrs = elem && elem.attributes;
  // Gets all values
  if ( key === undefined ) {
    if ( this.length ) {
      data = dataUser.get( elem );
      if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
        i = attrs.length;
        while ( i-- ) {
          // Support: IE 11 only
          // The attrs elements can be null (#14894)
          if ( attrs[ i ] ) {
            name = attrs[ i ].name;
            if ( name.indexOf( "data-" ) === 0 ) {
              name = jQuery.camelCase( name.slice( 5 ) );
              dataAttr( elem, name, data[ name ] );
            }
          }
        }
        dataPriv.set( elem, "hasDataAttrs", true );
      }
    }
    return data;
  }
  // Sets multiple values
  if ( typeof key === "object" ) {
    return this.each( function() {
      dataUser.set( this, key );
    } );
  }
  return access( this, function( value ) {
    var data;
    // The calling jQuery object (element matches) is not empty
    // (and therefore has an element appears at this[ 0 ]) and the
    // `value` parameter was not undefined. An empty jQuery object
    // will result in `undefined` for elem = this[ 0 ] which will
    // throw an exception if an attempt to read a data cache is made.
    if ( elem && value === undefined ) {
      // Attempt to get data from the cache
      // The key will always be camelCased in Data
      data = dataUser.get( elem, key );
      if ( data !== undefined ) {
        return data;
      }
      // Attempt to "discover" the data in
      // HTML5 custom data-* attrs
      data = dataAttr( elem, key );
      if ( data !== undefined ) {
        return data;
      }
      // We tried really hard, but the data doesn&#39;t exist.
      return;
    }
    // Set the data...
    this.each( function() {
      // We always store the camelCased key
      dataUser.set( this, key, value );
    } );
  }, null, value, arguments.length > 1, null, true );
},

图示这些方法在 access 内部执行处

jQuery 3.0 的 setter和getter 模式详解

更多jQuery 3.0 的 setter和getter 模式详解相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn