jQuery源码分析之 $.isEmptyObject和$.isPlainObject的区别以及自己的一些看法

最近一段时间一直在努力的去看jQuery的源码,这是一个非常富有挑战的过程,在这个过程中也收获颇多,就想把自己在看jQuery源码的时候遇到的一些困惑和读出的一些心得体会总结出来弄成一个jQuery源码分析系列分享给大家,希望对大家也能有所帮助。对自己而言也是一份读完之后的总结,归纳。

jquery“类”和jquery实例的扩展

在看jquery源码的时候,我们必须先要知道,jquery中有很多方法是扩展在jquery这个“类”中的,而有些方法是扩展在jquery这个实例对象上的。这两个是完全不一样的。

扩展在类上面的比如今天所说的这两个函数,再比如$.type() $.isNumeric()等等方法,这些方法是直接从jquery这个"类"中去取出来使用的。

还有另一种是扩展在jquery实例上的方法,比如$(ele).addClass(),$(ele).removeClass() $(ele).eq()...等等这些方法是需要从jquery的返回实例上去调用这些方法的。

那么这两种分别是怎么扩展的呢。?

在jquery中,如果是从jquery“类”中扩展的方法都会写成: jQuery.extend({ })

而如果是在jquery实例上扩展的方法则是写在jQuery.fn = jQuery.prototype = { }

我们来看下jquery源码:




通过上两幅图中jquery的源码部分我们可以很清楚的看到是分别在jquery.fn和jquery.extend来实现的。

那今天就先来说说jQuery中的$.isPlainObject()和$.isEmptyObject()两个方法的源码分析。


$.isEmptyObject()

首先先说比较简单的$.isEmptyObject()这个方法,他的功能是检测一个对象是否是一个空对象

废话不多说,先上源码:

isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

我们可以看到其实关于isEmptyObject方法的源码很简单,其实就是in运算符。

首先会将传入的参数用in运算符去遍历传入对象的属性,如果遍历出这个参数对象中有属性的话,那自然就不会是一个空对象了,但是这儿需要注意的有两点。

第一点是in运算符是可以一直遍历对象上原型链上的属性,(与hasOwnProperty()方法只能遍历自身属性不同),所以即便本身对象上没有属性,但是如果原型链上的对象有属性值的话,那么isEmptyObject()这个方法返回的是false而不是true。

第二点如果传入的参数不是一个对象,那么首先会将传入的参数转化为对象,比如你传入的是$.isEmptyObject('name')这么个字符串的话,那么会首先将传入的基本数据类型字符串用基本包装类型String()包装(即所谓的“装箱”过程)。

装箱:将number string boolea这三种基本数据类型调用各自的基本包装方法 Number()、String()、Boolean()的过程称之为“装箱”

下面用两个demo来说明上面写的两点:

 var parent = {
        age:27
    }

    var son = Object.create(parent)
    console.log($.isEmptyObject(son))     //false
    console.log($.isEmptyObject('name'))  //false

我们可以看到这两个放回的都是false,因为传如的‘name’会被装箱之后遍历,in自然是能遍历到属性的,所以返回是false.

我们看一下son是用Object.create()函数返回的一个对象,那么son._proto_原型对象就会指向parent.。如下图:


我们刚刚在上面也说明了,in运算符不仅能遍历对象本身的属性,还能沿着原型链去遍历所有[[Enumerable]]值为true的属性。

所以我们在控制台上看到son自己本身虽然是没有任何属性的,看起来像是一个‘空对象’但是他的原型对象parent上是有age属性的,所以他的返回值也是false。

上面所说的两点基本包含了大部分用到$.isEmptyObject()这个方法的场景了。

之所以说是“大部分”的确还有两个地方,是比较特殊的地方。那就是当闯入的参数是undefined 和null的时候。for in是不会去运行的。直接返回true了,那前后的逻辑就会变得非常奇怪了,也就是说当你$.isEmptyObject(null)或者$.isEmptyObject(undefined)的时候总是返回true,可是null和undefined压根就不是一个对象,既然都不是一个对象,那怎么能说他们是一个空的“对象呢”。(null在js中被认为是一个对象是一个历史悠久的bug,null本身就是一个基本类型,并不是对象。)

我原本以为for in在遇到null 和undefined的时候会有其他的机制,可是我查阅了很多资料,最终在在javascript 高级程序设计(第三版)第三章第六节 3.6.5 这一小节中发现关于for in 遇到null 和undefined的描述如下:

如果要迭代的对象变量是null 或undefined的话,for-in语句会抛出错误,ECMAScript5更正了这一行为,对这种情况不在抛出错误,而是不执行循环体。为了保证最大限度的兼容性,建议在使用for-in循环之前,先检测确认该对象的值不是null或undefined.

上述这段话是高程3的原话。那这就有意思了。如果用户在使用$.isEmptyObject()这个方法的时候。传入的是null或者是undefined,呵呵哒,那就压根不会运行for-in,而是直接返回true了。我自己觉得这是jquery中没考虑周全的部分。应当判断一下传入的参数是否是undefined和null的情况。至于怎么检测,那这儿还有个小坑,不能用type of null去检测,(浏览器总是返回object)这是历史悠久的bug,可以用jquery内部方法$.type()去判断,关于$.type()的方法我们后面也会写一篇文章讲述,这儿我们只需要知道使用$.type()会返回准确的数据类型就可以了。写到这儿我有点激动了,因为发现了一个jquery不合理的地方,至少我觉得是有待提高的地方。哈哈。这也是探索jquery源码的乐趣之一吧。


$.isPlainObject()

下面我们来看下另一个$.isPlainObject()这个方法,这个方法比上面的方法要略复杂些,他的用处是用来判断一个对象是否是一个“纯粹”的对象,那么什么是“纯粹”呢,就是用{}或者new Object() 在加上一个特例就是用Object.create(null)来构造出的对象。

关于这部分的源码如下:

isPlainObject: function( obj ) { 
		var proto, Ctor;
		// Detect obvious negatives
		// Use toString instead of jQuery.type to catch host objects
		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
			return false;
		}

		proto = getProto( obj );

		// Objects with no prototype (e.g., `Object.create( null )`) are plain
		if ( !proto ) {
			return true;
		}
		
		// Objects with prototype are plain iff they were constructed by a global Object function
		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
	},

这就是$.isPlainObject()的源码部分了。这儿有几个全局变量 

var getProto Object.getPrototypeOf;

var class2type = {};

var toString class2type.toString;

var hasOwn class2type.hasOwnProperty;

这而的getProto其实就是Object中的getPrototyoeOf的方法,用来获得传入参数对象的原型对象。

hasOwn就是一个对象上的hasOwnProperty()这个方法,用来获取对象本身上面是否具有某个属性与in 不同,hasOwnProperty会忽略原型链上的属性。

toString就是一个对象上调用toString()这个方法,转化为字符串。

我们首先看第一个if判断

if ( !obj || toString.call( obj ) !== "[object Object]" ) {
			return false;
		}

这个判断传入的参数可以过滤掉非对象的情况,因为要要判断一个对象是否是“纯粹”的对象,的前提需要他是对象。

proto = getProto( obj );
这句代码就是获取传入参数对象的原型对象。如果我们没有给一个对象设置他的原型对象,那么proto就会等于全局对象下的Object,因为所有的对象都继承Object这个对象。(除了object.create(null)之外)

// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
   return true;
}

这句话是处理那些没有原型对象的对象,在这儿你可能会诧异一点,不是所有的对象都会继承Object么,怎么会有对象没有原型对象呢,有的,就是当我们使用Object.create(null)的方式去建立一个对象的时候,所建立出的对象是没有原型对象的,因为一般情况下,Object.create(param)所创建的对象的原型对象都是param,而此时param是null的话,就自然没有原型对象了。

所以,这句代码就是用来处理这种情况的,当我们使用Object.create(null)创建出的对象时,就返回true,实际上使用Object.create(null)所创建出的对象,恐怕是最“干净”,最“纯粹”的。因为如上所说,所有对象默认情况下都会继承Object这个对象,而使用这种方式创造出的对象是Object都不会继承的。

Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;

这一句代码略长,但是也不难,就是先判断proto(传入参数的原型对象)是否含有constructor这个属性,实际上这个属性对应的就是构造函数。如果有的话,就取出proto中的构造函数然后吧这个函数的引用赋值给Ctor。

return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;

我们得到了proto的构造函数之后,就用这个构造函数来判断这个对象是否是一个纯粹的对象,因为我们使用{}或者new Object()的方式去构造一个对象的话,构造出的对象都会继承Object这个全局对象,这个对象中的constructor属性是一个内部的构造方法。var fnToString hasOwn.toString;

var ObjectFunctionString fnToString.callObject );

所以这句代码其实就是判断我们传入参数对象的原型对象中的构造函数是否和全局变量中Object中的构造函数完全一样,如果完全一样,那么就返回true,反之则返回false.

下面看几个demo来说明上面所说的内容:

 var a = $.isPlainObject({})
    var b = $.isPlainObject(new Object())
    var c = $.isPlainObject(Object.create(null))

    function Foo() {

    }

    var d = $.isPlainObject(new Foo())

    console.log(a,b,c,d)  //true true true false

我们可以看到a,b,c都分别是使用{}, new Object() 和Object.create(null)来构造出的对象,所以返回值是true.即被认为是一个“纯粹”的对象,而d这个实例对象则是通过构造函数Foo()生成的,因为在通过new 调用构造函数的时候,这个返回的实例对象的原型对象指向的是构造函数,而构造函数中的constructor是指向自己的,并不等于全局对象Object中的构造函数,所以返回的结果是false。

小结:

  • $.isEmptyObject():用于判断一个对象是否是“空”对象,(不仅自己空,自己的原型上也不能有可被for-in遍历到的属性),用之前最好先用$.type()去兼容下null和undefined的两种情况(jQuery源码的“小坑”)。
  • $.isPlainObject():用于判断一个对象是否“纯粹”(即用 "{}", "new Object()", "Object.create(null)")创建出来的。

两个方法从名字上看好像差不多,实际上所做的事情是完全不一样的。

好了,关于$.isEmptyObject()和$.isPlainObject()的源码就分析完了,如果大家有不同的见解,欢迎一起沟通交流。



猜你喜欢

转载自blog.csdn.net/weixin_38080573/article/details/79448590