Interview icon indicating copy to clipboard operation
Interview copied to clipboard

第168题(2020-03-09):instanceOf原理,手写一个instanceOf?(快手)

Open qappleh opened this issue 5 years ago • 4 comments

qappleh avatar Mar 09 '20 08:03 qappleh

根据 ECMAScript 语言规范,我梳理了一下大概的思路,然后整理了一段代码如下

function new_instance_of(leftVaule, rightVaule) { 
    let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
    leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
    while (true) {
    	if (leftVaule === null) {
            return false;	
        }
        if (leftVaule === rightProto) {
            return true;	
        } 
        leftVaule = leftVaule.__proto__ 
    }
}

其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。 看几个很有趣的例子

function Foo() {
}
Object instanceof Object // true
Function instanceof Function // true
Function instanceof Object // true
Foo instanceof Foo // false
Foo instanceof Object // true
Foo instanceof Function // true

要想全部理解 instanceof 的原理,除了我们刚刚提到的实现原理,我们还需要知道 JavaScript 的原型继承原理。

关于原型继承的原理,我简单用一张图来表示 image 我们知道每个 JavaScript 对象均有一个隐式的 proto 原型属性,而显式的原型属性是 prototype,只有 Object.prototype.proto 属性在未修改的情况下为 null 值。根据图上的原理,我们来梳理上面提到的几个有趣的 instanceof 使用的例子。

  • Object instanceof Object

由图可知,Object 的 prototype 属性是 Object.prototype, 而由于 Object 本身是一个函数,由 Function 所创建,所以 Object.proto 的值是 Function.prototype,而 Function.prototype 的 proto 属性是 Object.prototype,所以我们可以判断出,Object instanceof Object 的结果是 true 。用代码简单的表示一下

leftValue = Object.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判断
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判断
leftValue === rightValue
// 返回 true

Function instanceof Function 和 Function instanceof Object 的运行过程与 Object instanceof Object 类似,故不再详说。

  • Foo instanceof Foo Foo 函数的 prototype 属性是 Foo.prototype,而 Foo 的 proto 属性是 Function.prototype,由图可知,Foo 的原型链上并没有 Foo.prototype ,因此 Foo instanceof Foo 也就返回 false 。
leftValue = Foo, rightValue = Foo
leftValue = Foo.__proto = Function.prototype
rightValue = Foo.prototype
// 第一次判断
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判断
leftValue != rightValue
leftValue = Object.prototype = null
// 第三次判断
leftValue === null
// 返回 false

  • Foo instanceof Object
leftValue = Foo, rightValue = Object
leftValue = Foo.__proto__ = Function.prototype
rightValue = Object.prototype
// 第一次判断
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判断
leftValue === rightValue
// 返回 true 

  • Foo instanceof Function
leftValue = Foo, rightValue = Function
leftValue = Foo.__proto__ = Function.prototype
rightValue = Function.prototype
// 第一次判断
leftValue === rightValue
// 返回 true 

手写instanceof 的实现:

instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置。

实现思路: 首先 instanceof 左侧必须是对象, 才能找到它的原型链

instanceof 右侧必须是函数, 函数才会prototype属性

迭代 , 左侧对象的原型不等于右侧的 prototype时, 沿着原型链重新赋值左侧

// [1,2,3] instanceof Array ---- true

// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){    
    // 验证如果为基本数据类型,就直接返回false
    const baseType = ['string', 'number','boolean','undefined','symbol']
    if(baseType.includes(typeof(L))) { return false }
    
    let RP  = R.prototype;  //取 R 的显示原型
    L = L.__proto__;       //取 L 的隐式原型
    while(true){           // 无线循环的写法(也可以使 for(;;) )
        if(L === null){    //找到最顶层
            return false;
        }
        if(L === RP){       //严格相等
            return true;
        }
        L = L.__proto__;  //没找到继续向上一层原型链查找
    }
}

总结

简单来说,我们使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,他可以被 instanceof 判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call 方法。

qappleh avatar Jul 03 '20 06:07 qappleh

判读基本类型,用if (typeof left !== 'object' || left === null) 更为准确

johanazhu avatar Mar 23 '21 13:03 johanazhu

对于 right 的值也可以过滤下

// https://github.com/qappleh/Interview/issues/170

/**
 * 实现一个 instanceOf
 * @example myInstanceOf([1, 2, 3], Array) => true
 * @param value 
 * @param target 
 */
function myInstanceOf(value, target) {
  // 排除基本数据类型
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  // 非法的 target 值
  if (!target.prototype) {
    return false;
  }

  value = value.__proto__;
  while (true) {
    if (value === null) {
      return false;
    }

    if (value === target.prototype) {
      return true;
    }

    // 没找到继续向上一层原型链查找
    value = value.__proto__;
  }
}

elvinn avatar Jul 07 '22 01:07 elvinn

对于 right 的值也可以过滤下

// https://github.com/qappleh/Interview/issues/170

/**
 * 实现一个 instanceOf
 * @example myInstanceOf([1, 2, 3], Array) => true
 * @param value 
 * @param target 
 */
function myInstanceOf(value, target) {
  // 排除基本数据类型
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  // 非法的 target 值
  if (!target.prototype) {
    return false;
  }

  value = value.__proto__;
  while (true) {
    if (value === null) {
      return false;
    }

    if (value === target.prototype) {
      return true;
    }

    // 没找到继续向上一层原型链查找
    value = value.__proto__;
  }
} > ```

这种用法要排除掉 Function 测试下 Function instanceof Object

shufangu avatar Nov 22 '22 03:11 shufangu