Index: src/wp-includes/category-functions.php
===================================================================
--- src/wp-includes/category-functions.php	(revision 34995)
+++ src/wp-includes/category-functions.php	(working copy)
@@ -319,18 +319,19 @@
  * pass to it. This is one of the features with using pass by reference in PHP.
  *
  * @since 2.3.0
+ * @since 4.4.0 The `$category` parameter now also accepts a WP_Term object.
  * @access private
  *
- * @param array|object $category Category Row object or array
+ * @param array|object|WP_Term $category Category Row object or array
  */
 function _make_cat_compat( &$category ) {
 	if ( is_object( $category ) && ! is_wp_error( $category ) ) {
-		$category->cat_ID = &$category->term_id;
-		$category->category_count = &$category->count;
-		$category->category_description = &$category->description;
-		$category->cat_name = &$category->name;
-		$category->category_nicename = &$category->slug;
-		$category->category_parent = &$category->parent;
+		$category->cat_ID = $category->term_id;
+		$category->category_count = $category->count;
+		$category->category_description = $category->description;
+		$category->cat_name = $category->name;
+		$category->category_nicename = $category->slug;
+		$category->category_parent = $category->parent;
 	} elseif ( is_array( $category ) && isset( $category['term_id'] ) ) {
 		$category['cat_ID'] = &$category['term_id'];
 		$category['category_count'] = &$category['count'];
Index: src/wp-includes/class-wp-term.php
===================================================================
--- src/wp-includes/class-wp-term.php	(revision 0)
+++ src/wp-includes/class-wp-term.php	(working copy)
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Taxonomy API: WP_Term class
+ *
+ * @package WordPress
+ * @subpackage Taxonomy
+ * @since 4.4.0
+ */
+
+/**
+ * Core class used to implement the WP_Term object.
+ *
+ * @since 4.4.0
+ */
+final class WP_Term {
+
+	/**
+	 * Term ID.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var int
+	 */
+	public $term_id;
+
+	/**
+	 * The term's name.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $name = '';
+
+	/**
+	 * The term's slug.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $slug = '';
+
+	/**
+	 * The term's term_group.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $term_group = '';
+
+	/**
+	 * Term Taxonomy ID.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var int
+	 */
+	public $term_taxonomy_id = 0;
+
+	/**
+	 * The term's taxonomy name.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $taxonomy = '';
+
+	/**
+	 * The term's description.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $description = '';
+
+	/**
+	 * ID of a term's parent term.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var int
+	 */
+	public $parent = 0;
+
+	/**
+	 * Cached object count for this term.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var int
+	 */
+	public $count = 0;
+
+	/**
+	 * Stores the term object's sanitization level.
+	 *
+	 * Does not correspond to a database field.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $filter = 'raw';
+
+	/**
+	 * Retrieve WP_Term instance.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @static
+	 *
+	 * @global wpdb $wpdb WordPress database abstraction object.
+	 *
+	 * @param int $term_id Term ID.
+	 * @return WP_Term|false Term object, false otherwise.
+	 */
+	public static function get_instance( $term_id ) {
+		global $wpdb;
+
+		$term_id = (int) $term_id;
+		if ( ! $term_id ) {
+			return false;
+		}
+
+		$_term = wp_cache_get( $term_id, 'terms' );
+
+		// If there isn't a cached version, hit the database.
+		if ( ! $_term ) {
+			$_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id = %d LIMIT 1", $term_id ) );
+			if ( ! $_term ) {
+				return false;
+			}
+
+			$_term = sanitize_term( $_term, $_term->taxonomy, 'raw' );
+			wp_cache_add( $term_id, $_term, 'terms' );
+		} elseif ( empty( $_term->filter ) ) {
+			$_term = sanitize_term( $_term, $_term->taxonomy, 'raw' );
+		}
+
+		return new WP_Term( $_term );
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 *
+	 * @param WP_Term|object $term Term object.
+	 */
+	public function __construct( $term ) {
+		foreach ( get_object_vars( $term ) as $key => $value ) {
+			$this->$key = $value;
+		}
+	}
+
+	/**
+	 * Sanitizes term fields, according to the filter type provided.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 *
+	 * @param string $filter Filter context. Accepts 'edit', 'db', 'display', 'attribute', 'js', 'raw'.
+	 * @return WP_Term Sanitized WP_Term instance.
+	 */
+	public function filter( $filter ) {
+		if ( isset( $this->filter ) && $this->filter === $filter ) {
+			return $this;
+		}
+
+		return sanitize_term( $this, $this->taxonomy, $filter );
+	}
+
+	/**
+	 * Converts an object to array.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 *
+	 * @return array Object as array.
+	 */
+	public function to_array() {
+		return get_object_vars( $this );
+	}
+}
Index: src/wp-includes/taxonomy-functions.php
===================================================================
--- src/wp-includes/taxonomy-functions.php	(revision 34995)
+++ src/wp-includes/taxonomy-functions.php	(working copy)
@@ -708,51 +708,71 @@
  * @todo Better formatting for DocBlock
  *
  * @since 2.3.0
+ * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
  *
  * @global wpdb $wpdb WordPress database abstraction object.
  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
  *
- * @param int|object $term     If integer, will get from database. If object will apply filters and return $term.
+ * @param int|WP_Term|object $term If integer, term data will be fetched from the database, or from the cache if
+ *                                 available. If stdClass object (as in the results of a database query), will apply
+ *                                 filters and return a `WP_Term` object corresponding to the `$term` data. If `WP_Term`,
+ *                                 will return `$term`.
  * @param string     $taxonomy Taxonomy name that $term is part of.
  * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
  * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
- * @return object|array|null|WP_Error Term Row from database. Will return null if $term is empty. If taxonomy does not
- * exist then WP_Error will be returned.
+ * @return mixed Type corresponding to `$output` on success or null on failure. When `$output` is `OBJECT`,
+ *               a WP_Term instance is returned. If taxonomy does not exist then WP_Error will be returned.
  */
-function get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
-	global $wpdb;
-
+function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
 	if ( empty( $term ) ) {
 		return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
 	}
 
-	if ( ! taxonomy_exists( $taxonomy ) ) {
+	if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
 	}
 
-	if ( is_object($term) && empty($term->filter) ) {
-		wp_cache_add( $term->term_id, $term, $taxonomy );
+	if ( $term instanceof WP_Term ) {
 		$_term = $term;
+	} elseif ( is_object( $term ) ) {
+		if ( empty( $term->filter ) ) {
+			$_term = sanitize_term( $term, $taxonomy, 'raw' );
+			$_term = new WP_Term( $_term );
+		} elseif ( 'raw' == $term->filter ) {
+			$_term = new WP_Term( $term );
+		} else {
+			$_term = WP_Term::get_instance( $term->term_id );
+		}
 	} else {
-		if ( is_object($term) )
-			$term = $term->term_id;
-		if ( !$term = (int) $term )
-			return null;
-		if ( ! $_term = wp_cache_get( $term, $taxonomy ) ) {
-			$_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.term_id = %d LIMIT 1", $taxonomy, $term) );
-			if ( ! $_term )
-				return null;
-			wp_cache_add( $term, $_term, $taxonomy );
+		$_term = WP_Term::get_instance( $term );
+	}
+
+	// If a taxonomy was passed, make sure it matches the taxonomy of the located term.
+	if ( $_term && $taxonomy && $taxonomy !== $_term->taxonomy ) {
+		// If there are two terms with the same ID, split the other one to a new term.
+		$new_term_id = _split_shared_term( $_term->term_id, $_term->term_taxonomy_id );
+
+		// If no split occurred, this is an invalid request.
+		if ( $new_term_id === $_term->term_id ) {
+			return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
+		} else {
+			// Refetch the term, now that it's no longer shared.
+			return get_term( $_term->term_id, $taxonomy, $output, $filter );
 		}
 	}
 
+	if ( ! $_term ) {
+		return null;
+	}
+
 	/**
 	 * Filter a term.
 	 *
 	 * @since 2.3.0
+	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
 	 *
-	 * @param int|object $_term    Term object or ID.
-	 * @param string     $taxonomy The taxonomy slug.
+	 * @param int|object|WP_Term $_term    Term object or ID.
+	 * @param string             $taxonomy The taxonomy slug.
 	 */
 	$_term = apply_filters( 'get_term', $_term, $taxonomy );
 
@@ -763,24 +783,23 @@
 	 * to the taxonomy slug.
 	 *
 	 * @since 2.3.0
+	 * @since 4.4.0 `$_term` can now also be a WP_Term object.
 	 *
-	 * @param int|object $_term    Term object or ID.
-	 * @param string     $taxonomy The taxonomy slug.
+	 * @param int|object|WP_Term $_term    Term object or ID.
+	 * @param string             $taxonomy The taxonomy slug.
 	 */
 	$_term = apply_filters( "get_$taxonomy", $_term, $taxonomy );
-	$_term = sanitize_term($_term, $taxonomy, $filter);
 
-	if ( $output == OBJECT ) {
-		return $_term;
-	} elseif ( $output == ARRAY_A ) {
-		$__term = get_object_vars($_term);
-		return $__term;
+	// Sanitize term, according to the specified filter.
+	$_term = $_term->filter( $filter );
+
+	if ( $output == ARRAY_A ) {
+		return $_term->to_array();
 	} elseif ( $output == ARRAY_N ) {
-		$__term = array_values(get_object_vars($_term));
-		return $__term;
-	} else {
-		return $_term;
+		return array_values( $_term->to_array() );
 	}
+
+	return $_term;
 }
 
 /**
@@ -798,7 +817,8 @@
  * @todo Better formatting for DocBlock.
  *
  * @since 2.3.0
- * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'.
+ * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
+ *              a WP_Term object if `$output` is `OBJECT`.
  *
  * @global wpdb $wpdb WordPress database abstraction object.
  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
@@ -808,8 +828,7 @@
  * @param string     $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
  * @param string     $output   Constant OBJECT, ARRAY_A, or ARRAY_N
  * @param string     $filter   Optional, default is raw or no WordPress defined filter will applied.
- * @return object|array|null|WP_Error|false Term Row from database.
- *                                          Will return false if $taxonomy does not exist or $term was not found.
+ * @return mixed WP_Term instance. Will return false if `$taxonomy` does not exist or `$term` was not found.
  */
 function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
 	global $wpdb;
@@ -822,7 +841,7 @@
 	$tax_clause = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy );
 
 	if ( 'slug' == $field ) {
-		$field = 't.slug';
+		$_field = 't.slug';
 		$value = sanitize_title($value);
 		if ( empty($value) )
 			return false;
@@ -829,10 +848,10 @@
 	} elseif ( 'name' == $field ) {
 		// Assume already escaped
 		$value = wp_unslash($value);
-		$field = 't.name';
+		$_field = 't.name';
 	} elseif ( 'term_taxonomy_id' == $field ) {
 		$value = (int) $value;
-		$field = 'tt.term_taxonomy_id';
+		$_field = 'tt.term_taxonomy_id';
 
 		// No `taxonomy` clause when searching by 'term_taxonomy_id'.
 		$tax_clause = '';
@@ -844,7 +863,7 @@
 		return $term;
 	}
 
-	$term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE $field = %s $tax_clause LIMIT 1", $value ) );
+	$term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE $_field = %s $tax_clause LIMIT 1", $value ) );
 	if ( ! $term )
 		return false;
 
@@ -853,25 +872,9 @@
 		$taxonomy = $term->taxonomy;
 	}
 
-	wp_cache_add( $term->term_id, $term, $taxonomy );
+	wp_cache_add( $term->term_id, $term, 'terms' );
 
-	/** This filter is documented in wp-includes/taxonomy-functions.php */
-	$term = apply_filters( 'get_term', $term, $taxonomy );
-
-	/** This filter is documented in wp-includes/taxonomy-functions.php */
-	$term = apply_filters( "get_$taxonomy", $term, $taxonomy );
-
-	$term = sanitize_term($term, $taxonomy, $filter);
-
-	if ( $output == OBJECT ) {
-		return $term;
-	} elseif ( $output == ARRAY_A ) {
-		return get_object_vars($term);
-	} elseif ( $output == ARRAY_N ) {
-		return array_values(get_object_vars($term));
-	} else {
-		return $term;
-	}
+	return get_term( $term, $taxonomy, $output, $filter );
 }
 
 /**
@@ -987,7 +990,8 @@
  * @since 2.3.0
  * @since 4.2.0 Introduced 'name' and 'childless' parameters.
  * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
- *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters.
+ *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
+ *              a list of WP_Term objects.
  *
  * @global wpdb  $wpdb WordPress database abstraction object.
  * @global array $wp_filter
@@ -1048,8 +1052,8 @@
  *     @type array        $meta_query             Meta query clauses to limit retrieved terms by.
  *                                                See `WP_Meta_Query`. Default empty.
  * }
- * @return array|int|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies
- *                        do not exist.
+ * @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of $taxonomies
+ *                            do not exist.
  */
 function get_terms( $taxonomies, $args = '' ) {
 	global $wpdb;
@@ -1489,6 +1493,8 @@
 		foreach ( $terms as $term ) {
 			$_terms[ $term->term_id ] = $term->slug;
 		}
+	} else {
+		$_terms = array_map( 'get_term', $terms );
 	}
 
 	if ( ! empty( $_terms ) ) {
@@ -3422,7 +3428,7 @@
 		foreach ( (array) $terms as $term ) {
 			$taxonomies[] = $term->taxonomy;
 			$ids[] = $term->term_id;
-			wp_cache_delete($term->term_id, $term->taxonomy);
+			wp_cache_delete( $term->term_id, 'terms' );
 		}
 		$taxonomies = array_unique($taxonomies);
 	} else {
@@ -3429,7 +3435,7 @@
 		$taxonomies = array($taxonomy);
 		foreach ( $taxonomies as $taxonomy ) {
 			foreach ( $ids as $id ) {
-				wp_cache_delete($id, $taxonomy);
+				wp_cache_delete( $id, 'terms' );
 			}
 		}
 	}
@@ -3552,7 +3558,7 @@
 		if ( empty($term_taxonomy) )
 			$term_taxonomy = $term->taxonomy;
 
-		wp_cache_add( $term->term_id, $term, $term_taxonomy );
+		wp_cache_add( $term->term_id, $term, 'terms' );
 	}
 }
 
Index: src/wp-includes/taxonomy.php
===================================================================
--- src/wp-includes/taxonomy.php	(revision 34995)
+++ src/wp-includes/taxonomy.php	(working copy)
@@ -10,5 +10,8 @@
 /** Core taxonomy functionality */
 require_once( ABSPATH . WPINC . '/taxonomy-functions.php' );
 
+/** WP_Term class */
+require_once( ABSPATH . WPINC . '/class-wp-term.php' );
+
 /** WP_Tax_Query class */
 require_once( ABSPATH . WPINC . '/class-wp-tax-query.php' );
Index: tests/phpunit/tests/term/cache.php
===================================================================
--- tests/phpunit/tests/term/cache.php	(revision 34995)
+++ tests/phpunit/tests/term/cache.php	(working copy)
@@ -103,10 +103,10 @@
 		) );
 
 		$term_object = get_term( $term, 'wptests_tax' );
-		wp_cache_delete( $term, 'wptests_tax' );
+		wp_cache_delete( $term, 'terms' );
 
 		// Affirm that the cache is empty.
-		$this->assertEmpty( wp_cache_get( $term, 'wptests_tax' ) );
+		$this->assertEmpty( wp_cache_get( $term, 'terms' ) );
 
 		$num_queries = $wpdb->num_queries;
 
@@ -128,16 +128,16 @@
 			'taxonomy' => 'wptests_tax',
 		) );
 
-		wp_cache_delete( $term, 'wptests_tax' );
+		wp_cache_delete( $term, 'terms' );
 
 		// Affirm that the cache is empty.
-		$this->assertEmpty( wp_cache_get( $term, 'wptests_tax' ) );
+		$this->assertEmpty( wp_cache_get( $term, 'terms' ) );
 
 		$num_queries = $wpdb->num_queries;
 
 		// Prime cache.
 		$term_object = get_term( $term, 'wptests_tax' );
-		$this->assertNotEmpty( wp_cache_get( $term, 'wptests_tax' ) );
+		$this->assertNotEmpty( wp_cache_get( $term, 'terms' ) );
 		$this->assertSame( $num_queries + 1, $wpdb->num_queries );
 
 		$term_object_2 = get_term( $term, 'wptests_tax' );
@@ -155,16 +155,16 @@
 			'taxonomy' => 'wptests_tax',
 		) );
 
-		wp_cache_delete( $term, 'wptests_tax' );
+		wp_cache_delete( $term, 'terms' );
 
 		// Affirm that the cache is empty.
-		$this->assertEmpty( wp_cache_get( $term, 'wptests_tax' ) );
+		$this->assertEmpty( wp_cache_get( $term, 'terms' ) );
 
 		$num_queries = $wpdb->num_queries;
 
 		// Prime cache.
 		$term_object = get_term_by( 'id', $term, 'wptests_tax' );
-		$this->assertNotEmpty( wp_cache_get( $term, 'wptests_tax' ) );
+		$this->assertNotEmpty( wp_cache_get( $term, 'terms' ) );
 		$this->assertSame( $num_queries + 1, $wpdb->num_queries );
 
 		$term_object_2 = get_term( $term, 'wptests_tax' );
Index: tests/phpunit/tests/term/getTerm.php
===================================================================
--- tests/phpunit/tests/term/getTerm.php	(revision 34995)
+++ tests/phpunit/tests/term/getTerm.php	(working copy)
@@ -35,19 +35,6 @@
 		$this->assertSame( $num_queries, $wpdb->num_queries );
 	}
 
-	public function test_passing_term_object_should_not_skip_database_query_when_filter_property_is_set() {
-		global $wpdb;
-
-		$term = $this->factory->term->create_and_get( array( 'taxonomy' => 'wptests_tax' ) );
-		clean_term_cache( $term->term_id, 'wptests_tax' );
-
-		$num_queries = $wpdb->num_queries;
-
-		$term_a = get_term( $term, 'wptests_tax' );
-
-		$this->assertSame( $num_queries + 1, $wpdb->num_queries );
-	}
-
 	public function test_passing_term_string_that_casts_to_int_0_should_return_null() {
 		$this->assertSame( null, get_term( 'abc', 'wptests_tax' ) );
 	}
