The preliminaries language

This article is a complement to JavaScript Function Features and Prototype Chains, so if you haven’t already read that article, you can browse it.

Typeof a function returns “function”. This time I’ll explain how Typeof looks at functions, hopefully to help you and to complement the previous article.

Review the data types of JS

Let’s start by reviewing the eight data types of the latest ECMAScript standard:

There are seven primitive types (or primitives) : Boolean, Null, Undefined, Number, BigInt, String, Symbol, and Object.

A primitive type (primitive value, primitive data type) is a type of data that is neither an object nor a method. In JavaScript, there are seven basic types: String, number, Bigint, Boolean, NULL, undefined, and symbol (new in ECMAScript 2016).

In most cases, primitive types directly represent the lowest level of language implementation.

Values of all primitive types are immutable. Note, however, the difference between a primitive type itself and a variable assigned to a primitive type. Variables are assigned a new value, and the original value cannot be changed like arrays, objects, and functions.

What is immutable

var bar = "baz";
bar[0] = "a";
console.log(bar[0]); // "b"
console.log(bar);// "baz"
Copy the code

Wrapper object of basic type

Primitive types other than null and undefined have corresponding wrapper objects:

  • String is the basic type of a String.
  • Number indicates the basic numeric type.
  • BigInt is the basic type of a large integer.
  • Boolean Indicates the basic Boolean type.
  • Symbol is the basic literal type.

The valueOf() method on this wrap object returns a primitive type value.

var s_prim = "foo";
var s_obj = new String(s_prim);

console.log(typeof s_prim); // "string"
console.log(typeof s_obj);  // "object"
console.log(typeof s_obj.valueOf());  // "string"
Copy the code

Typeof possible table of return values

The following table summarizes possible typeof return values:

type The results of
Undefined “undefined”
Null “Object” (see below)
Boolean “boolean”
Number “number”
BigInt “bigint”
String “string”
Symbol (new in ECMAScript 2015) “symbol”
Host object (provided by the JS environment) It depends on the implementation
Function object (implemented according to ecMA-262 specification [[Call]]) “function”
Any other object “object”

Analyze the pitfalls of typeof

I used to analyze Typeof functions and objects in this way:

Take the previous String as an example:

var s_obj = new String(s_prim);
console.log(typeof String); // "function"
// When typeof is used, the Function is called as a Function object, and all Function objects are constructed by Function. String.__proto__ === function. prototype, so return "Function"

console.log(typeof s_obj);  // "object"
// 1. S_obj is an object constructed from String, so: s_obj.__proto__ === string.prototype;
String. Prototype. __proto__ === object. prototype;
// 3. So s_obj returns "object";
Copy the code

If you read my article “JavaScript Function features and Prototype chains” then you can understand why I’m doing this, because prototype chains look like this.

But the question is, does typeof terminate the function’s prototype chain ahead of time?

/ / because
Function.prototype.__proto__ === Object.prototype;
Copy the code

The result of typeof should return “object” if the prototype chain is not terminated early.

A little JS history

With that in mind, let’s look at a little JavaScript history.

We all know about the “typeof null” bug, which dates back to the first version of JavaScript. In this version, values are stored in 32-bit units, including a small type token (1-3 bits) and the actual data for the value. Type tokens are stored lower down in the cell. There are 5 types:

  • 000: object: indicates that the data is a reference to an object.
  • 1: int, indicating that the data is a 31-bit signed integer.
  • 010: double, indicating that the data is a reference to a double floating-point number.
  • 100: string, indicating that the data is a reference to a string.
  • 110: Boolean: indicates that the data is a Boolean value.

Two values are special:

  • The value of undefined (JSVAL_VOID) is a −2^30 integer (a number outside the integer range).

  • Null (JSVAL_NULL) is the machine code null pointer. Or: an object type marker with a reference to zero. Refer to the source code below for details.

It should now be clear why Typeof considers NULL to be an object: it checks the type token and the object represented by the type token.

Source code for Mozilla typeof

After reviewing the history of the first version, let’s take a look at the source code for Mozilla’s Typeof in 1998 (if you’re not interested in the source code, you can skip it, and I’ll add another conclusion to the source code) :

// https://dxr.mozilla.org/classic/source/js/src/jspubtd.h#52

/* Result of typeof operator enumeration. */
typedef enum JSType {
    JSTYPE_VOID,                /* undefined */
    JSTYPE_OBJECT,              /* object */
    JSTYPE_FUNCTION,            /* function */
    JSTYPE_STRING,              /* string */
    JSTYPE_NUMBER,              /* number */
    JSTYPE_BOOLEAN,             /* boolean */
    JSTYPE_LIMIT
} JSType;

// https://dxr.mozilla.org/classic/source/js/src/jsapi.h

#define JS_BIT(n)       ((JSUint32)1 << (n))
#define JS_BITMASK(n)   (JS_BIT(n) - 1)

#define JSVAL_OBJECT            0x0     /* untagged reference to object */
#define JSVAL_INT               0x1     /* tagged 31-bit integer value */

#define JSVAL_TAGBITS           3
#define JSVAL_TAGMASK           JS_BITMASK(JSVAL_TAGBITS)
#define JSVAL_TAG(v)            ((v) & JSVAL_TAGMASK)

#define JSVAL_IS_OBJECT(v)      (JSVAL_TAG(v) == JSVAL_OBJECT)
#define JSVAL_IS_NULL(v)        ((v) == JSVAL_NULL)

#define JSVAL_IS_VOID(v)        ((v) == JSVAL_VOID)

#define OBJECT_TO_JSVAL(obj)    ((jsval)(obj))
#define JSVAL_NULL              OBJECT_TO_JSVAL(0)

#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
#define INT_TO_JSVAL(i)         (((jsval)(i) << 1) | JSVAL_INT)

#define JSVAL_INT_POW2(n)       ((jsval)1 << (n))

// https://dxr.mozilla.org/classic/source/js/src/jsfun.c#1172
JSClass js_FunctionClass = {
    "Function",
    JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
    JS_PropertyStub,  fun_delProperty,
    fun_getProperty,  fun_setProperty,
    fun_enumProperty, (JSResolveOp)fun_resolve,
    fun_convert,      fun_finalize,
    NULL.NULL.NULL.NULL,
    fun_xdrObject,    fun_hasInstance
};

// This is the typeof source section
// https://dxr.mozilla.org/classic/source/js/src/jsapi.c#333
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    // Check whether it is undefined.
    If (v) == JSVAL_VOID)
    Jsval_pow2 (30);
    JSVAL_INT_POW2(30) = ((jsval)1 << (n)); So, 0 - JSVAL_INT_POW2(30) = -2^30
    / / 4, INT_TO_JSVAL is (((jsval) (I) < < 1) | JSVAL_INT) is to: - 2147483647-2 ^ 31 this value.
    // 5, so if a value is passed in equal to JSVAL_VOID, that value represents undefined.
    // PS: Please point out any problems with this analysis
    if (JSVAL_IS_VOID(v)) {
        type = JSTYPE_VOID;
    } 
    // Determine if it is an object based on the type marker
    // JSVAL_IS_OBJECT(v)
    JSVAL_TAG(v) == JSVAL_OBJECT; JSVAL_TAG(v) == JSVAL_OBJECT;
    // 2, JSVAL_TAG(v) is :((v) & JSVAL_TAGMASK);
    // 3, JSVAL_TAGMASK is: JS_BITMASK(JSVAL_TAGBITS);
    // 4, JSVAL_TAGBITS = 1;
    // 5, JS_BITMASK(n) = (JS_BIT(n) -1)
    // 6, JS_BIT(n) = ((JSUint32)1 << (n));
    // it is an object
    // 8, how to determine null:
    // 9, because JSVAL_NULL is: OBJECT_TO_JSVAL(0); OBJECT_TO_JSVAL (obj) : ((jsval) (obj))
    JSVAL_TAG(JSVAL_NULL) = 0x0; JSVAL_TAG(JSVAL_NULL) = 0
    else if (JSVAL_IS_OBJECT(v)) {
        // JSVAL_TO_OBJECT(v) is :((JSObject *)JSVAL_TO_GCTHING(v)), which converts the passed value to the type of the internal JSObject structure.
        obj = JSVAL_TO_OBJECT(v);
        // This string of judgments is very complicated, I can only analyze the general:
        Js_ObjectOps = js_ObjectOps = js_ObjectOps = js_ObjectOps = js_ObjectOps
        // If not, then see if it is js_FunctionClass; If not, then see if call is not 0;
        Return "function" if the object has call or FunctionClass or if call is not 0, otherwise return "object".
        if (obj &&
            (ops = obj->map->ops, ops == &js_ObjectOps ? (clasp = OBJ_GET_CLASS(cx, obj), clasp->call || clasp == &js_FunctionClass) : ops->call ! =0)) {
            type = JSTYPE_FUNCTION;
        } else{ type = JSTYPE_OBJECT; }}else if (JSVAL_IS_NUMBER(v)) {
        type = JSTYPE_NUMBER;
    } else if (JSVAL_IS_STRING(v)) {
        type = JSTYPE_STRING;
    } else if (JSVAL_IS_BOOLEAN(v)) {
        type = JSTYPE_BOOLEAN;
    }
    return type;
}
Copy the code

Summary:

1, typeof null returns “object” because JS internally defines a token with a low value of 0x0 as an object type, and null evaluates to a value with a low value of an object type, so “object” is returned.

2, Typeof returns “function”, as long as an object has a non-0 call attribute/method, or is of type js_FunctionClass, i.e. the object has a “function” flag in it. All other cases return “object”;

How is ES6 explained

Let’s take a look at how ES6 typeof now treats functions and object types:

  1. If an Object does not implement the [[Call]] internal method, it returns Object
  2. If an Object implements the [[Call]] internal method, it returns function

This is similar to whether call exists in the source code.

What is [[Call]]

Executes the code associated with this object. Called by a function call expression. The arguments to the inner method are a this value and a list of arguments passed to the function by the calling expression. The object that implements this internal method is callable.

Simply put, an object that supports an internal [[Call]] method can be called and becomes a function, so it is called a function object.

Correspondingly, if a function object supports an internal [[Construct]] method, it can be called using either new or super. Then we call the function object a constructor.

Typeof principle is roughly analyzed here, I have a clearer understanding of function objects and constructors, with you to learn progress, if there is wrong in the article, please correct.

References:

  • Developer.mozilla.org/zh-CN/docs/…
  • Developer.mozilla.org/zh-CN/docs/…
  • Dxr.mozilla.org/classic/sou…
  • 2 ality.com/2013/10/typ…
  • www.ecma-international.org/ecma-262/6….
  • www.ecma-international.org/ecma-262/6….

update

12-16 Added source code and analysis, added source summary.