]> source.dussan.org Git - jquery.git/commitdiff
Core: Simplify isPlainObject
authorRichard Gibson <richard.gibson@gmail.com>
Mon, 14 Mar 2016 20:46:51 +0000 (16:46 -0400)
committerTimmy Willison <timmywillisn@gmail.com>
Mon, 4 Apr 2016 16:02:13 +0000 (12:02 -0400)
Fixes gh-2986
Close gh-2998

src/core.js
src/var/ObjectFunctionString.js [new file with mode: 0644]
src/var/fnToString.js [new file with mode: 0644]
src/var/getProto.js [new file with mode: 0644]
test/unit/core.js

index a9898e51d3f571e03e9b9ac3731efa71b9885e93..7c2433529945f2ded83f310df0dde5a77b742924 100644 (file)
@@ -1,6 +1,7 @@
 define( [
        "./var/arr",
        "./var/document",
+       "./var/getProto",
        "./var/slice",
        "./var/concat",
        "./var/push",
@@ -8,10 +9,13 @@ define( [
        "./var/class2type",
        "./var/toString",
        "./var/hasOwn",
+       "./var/fnToString",
+       "./var/ObjectFunctionString",
        "./var/support",
        "./core/DOMEval"
-], function( arr, document, slice, concat,
-       push, indexOf, class2type, toString, hasOwn, support, DOMEval ) {
+], function( arr, document, getProto, slice, concat, push, indexOf,
+       class2type, toString, hasOwn, fnToString, ObjectFunctionString,
+       support, DOMEval ) {
 
 var
        version = "@VERSION",
@@ -225,28 +229,24 @@ jQuery.extend( {
        },
 
        isPlainObject: function( obj ) {
-               var key;
+               var proto, Ctor;
 
-               // Not plain objects:
-               // - Any object or value whose internal [[Class]] property is not "[object Object]"
-               // - DOM nodes
-               // - window
-               if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+               // Detect obvious negatives
+               // Use toString instead of jQuery.type to catch host objects
+               if ( !obj || toString.call( obj ) !== "[object Object]" ) {
                        return false;
                }
 
-               // Not own constructor property must be Object
-               if ( obj.constructor &&
-                               !hasOwn.call( obj, "constructor" ) &&
-                               !hasOwn.call( obj.constructor.prototype || {}, "isPrototypeOf" ) ) {
-                       return false;
-               }
+               proto = getProto( obj );
 
-               // Own properties are enumerated firstly, so to speed up,
-               // if last one is own, then all properties are own
-               for ( key in obj ) {}
+               // Objects with no prototype (e.g., `Object.create( null )`) are plain
+               if ( !proto ) {
+                       return true;
+               }
 
-               return key === undefined || hasOwn.call( obj, key );
+               // 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;
        },
 
        isEmptyObject: function( obj ) {
diff --git a/src/var/ObjectFunctionString.js b/src/var/ObjectFunctionString.js
new file mode 100644 (file)
index 0000000..5701979
--- /dev/null
@@ -0,0 +1,5 @@
+define( [
+       "./fnToString"
+], function( fnToString ) {
+       return fnToString.call( Object );
+} );
diff --git a/src/var/fnToString.js b/src/var/fnToString.js
new file mode 100644 (file)
index 0000000..a92e6eb
--- /dev/null
@@ -0,0 +1,5 @@
+define( [
+       "./hasOwn"
+], function( hasOwn ) {
+       return hasOwn.toString;
+} );
diff --git a/src/var/getProto.js b/src/var/getProto.js
new file mode 100644 (file)
index 0000000..dccf304
--- /dev/null
@@ -0,0 +1,3 @@
+define( function() {
+       return Object.getPrototypeOf;
+} );
index 5bf3ab1d1898746381b91fa3158a77b48219ec96..f18ad2e82bed76994dab0d1d99649cad89bad4f6 100644 (file)
@@ -287,7 +287,7 @@ QUnit.test( "type for `Symbol`", function( assert ) {
 
 QUnit.asyncTest( "isPlainObject", function( assert ) {
 
-       assert.expect( 22 );
+       assert.expect( 23 );
 
        var pass, iframe, doc, parentObj, childObj, deep,
                fn = function() {};
@@ -300,12 +300,13 @@ QUnit.asyncTest( "isPlainObject", function( assert ) {
        assert.ok( jQuery.isPlainObject( { constructor: "foo" } ),
                "plain object with primitive constructor property" );
 
-       parentObj = { foo: "bar" };
+       parentObj = {};
        childObj = Object.create( parentObj );
-
-       assert.ok( !jQuery.isPlainObject( childObj ), "isPlainObject(Object.create({}))" );
+       assert.ok( !jQuery.isPlainObject( childObj ), "Object.create({})" );
+       parentObj.foo = "bar";
+       assert.ok( !jQuery.isPlainObject( childObj ), "Object.create({...})" );
        childObj.bar = "foo";
-       assert.ok( !jQuery.isPlainObject( childObj ), "isPlainObject(Object.create({}))" );
+       assert.ok( !jQuery.isPlainObject( childObj ), "extend(Object.create({...}), ...)" );
 
        // Not objects shouldn't be matched
        assert.ok( !jQuery.isPlainObject( "" ), "string" );