Search code examples
javascriptprototypesymbolsinstanceof

Why is it not possible to define a [Symbol.hasInstance] static method using simple property assignment?


There are two code snippets to show the problem I got.

demo1.js:

// using ES6+ class syntax

class MyObject {
  constructor() {
    this.myProp1 = 'myProp1';
  }

  get [Symbol.toStringTag]() {
    console.log('[Custom Symbol.toStringTag]]');
    return 'CustomTag';
  }

  static [Symbol.hasInstance](instance) {
    console.log('[Custom Symbol.hasInstance]');
    return false;
  }
}

const myObj = new MyObject();

console.log(Object.prototype.toString.call(myObj));  // works correctly, implicitly called custom getter Symbol.toStringTag
// first log print [Custom Symbol.toStringTag]], then log print [object CustomTag], since custom getter Symbol.toStringTag return 'CustomTag'

console.log(myObj instanceof MyObject);  // works correctly, implicitly called custom static method Symbol.hasInstance
// first log print [Custom Symbol.hasInstance], then log print false, since custom static method Symbol.hasInstance return false

demo2.js:

// using classic prototype syntax

function MyObject() {
  this.myProp1 = 'myProp1';
}

Object.defineProperty(MyObject.prototype, Symbol.toStringTag, {
  configurable: true,
  enumerable: true,
  get() {
    console.log('[Custom Symbol.toStringTag]]');
    return 'CustomTag';
  }
})

// is this a correct way to define a static method to MyObject by using classic prototype syntax?
MyObject[Symbol.hasInstance] = function (instance) {
  console.log('[Custom Symbol.hasInstance]');
  return false;
}

const myObj = new MyObject();

console.log(Object.prototype.toString.call(myObj));  // works correctly, implicitly called custom getter Symbol.toStringTag
// first log print [Custom Symbol.toStringTag]], then log print [object CustomTag], since custom getter Symbol.toStringTag return 'CustomTag'

console.log(myObj instanceof MyObject);  // does not works correctly, does not implicitly called custom static method Symbol.hasInstance
// just log print true

My Question:

  1. in demo1.js, everything works fine as expected.
  2. in demo2.js, does MyObject[Symbol.hasInstance] = function (instance) { ... } is a correct way to define a static method by using classic prototype syntax?
  3. in demo2.js, if MyObject[Symbol.hasInstance] = function (instance) { ... } is correct way to define a static method, why the js operator instanceof does not implicitly call custom static method Symbol.hasInstance?

btw, about the well-know Symbol Symbol.hasInstance, pls refer to MDN web page


Solution

  • The problem in your code is that you can't overwrite the Symbol.hasInstance of a function with a simple assignment.

    That is a special case:

    Unlike most methods, the Function.prototype[Symbol.hasInstance]() property is non-configurable and non-writable. This is a security feature to prevent the underlying target function of a bound function from being obtainable. See this Stack Overflow answer for an example.

    MDN Function.prototype[Symbol.hasInstance]() Description

    Here is some evaluation:

    function f(v) {
      return v === 3;
    }
    
    const A = {
      [Symbol.hasInstance]: f
    }
    
    console.log("2 instanceof A:", 2 instanceof A); // false
    console.log("3 instanceof A:", 3 instanceof A); // true
    console.log("A[Symbol.hasInstance] === f:", A[Symbol.hasInstance] === f); // true
    
    const B = {};
    
    B[Symbol.hasInstance] = f;
    
    console.log("2 instanceof B:", 2 instanceof B); // false
    console.log("3 instanceof B:", 3 instanceof B); // true
    console.log("B[Symbol.hasInstance] === f:", B[Symbol.hasInstance] === f); // true
    
    function C() {}
    
    C[Symbol.hasInstance] = f;
    C.otherMethod = f;
    
    console.log("2 instanceof C:", 2 instanceof C); // false
    console.log("3 instanceof C:", 3 instanceof C); // false
    console.log("C.otherMethod(3):", C.otherMethod(3)); // true
    console.log("C[Symbol.hasInstance] === f:", C[Symbol.hasInstance] === f); // false
    console.log("C.otherMethod === f:", C.otherMethod === f); // true

    To overwrite the method, you can use Object.defineProperty:

    function f(v) {
      return v === 3;
    }
    
    function D() {}
    
    Object.defineProperty(D, Symbol.hasInstance, { value: f});
    
    console.log("2 instanceof D:", 2 instanceof D); // false
    console.log("3 instanceof D:", 3 instanceof D); // true
    console.log("D[Symbol.hasInstance] === f:", D[Symbol.hasInstance] === f); // true