JavaScript中如何监听变量被调用次数,前提是不能够修改这个变量的调用者。

昨天跟群里的朋友讨论bind的polyfill的时候,说到了instanceof操作符的作用机制,然后讨论到深夜。

function F(){}
var v = new F();
v instanceof F;

上面这段代码中,instanceof那段语句执行时是怎么运作的?我的理解如下:

执行: v instanceof F;
1、   v.__proto__;//这里并非真的能直接访问__proto__,只是便于表述与说明,实例找到原型
2、   F.prototype;//F通过prototype属性找到它创建的实例的原型
3、   v.__proto__ === F.prototype ? true : false;//最后两边比对是否相等
图示: v---->v.__proto__---->check<----F.prototype<----F

然而,那位朋友不这么认为,我们昨天以下面的代码讨论了很久:

function A(){};
function B(){};
B.prototype = A.prototype;
a = new A();
b = new B();
a instanceof B;//true,#1 做个编码标记,后面好引用说明
a instanceof A;//true,#2
b instanceof B;//true,#3
b instanceof A;//true,#4

他认为,这段代码因为B.prototypeA.prototype覆盖了,所以在各自创建实例前,原型实际上是一个了(这点我认同),所以实例a和实例b的原型都是同一个,也就是A.prototype(这点我也认同)。所以在instanceof的时候,#2和#4标记的代码会返回true(这点我认同,没问题),#1和#3标记的代码也会返回true(这就有问题了,这里不是指输出结果有误,而是指a找到原型再找到Bprototype的这种理解有误,为防止误解,故此修改)。
我理解的他的理解是这样的:

∵图示:              ________________________
a--->a.__proto__--->|A.prototype/           |
b--->b.__protp__--->|          /B.prototype |
                    —————————————————————————
∴得出#1#2#3#4都是true

我觉得在这个逻辑中有个问题:A.prototypeB.prototype以及a.__protp__b.__protp__都只是指针,指向的一个内存中实际存在的无名对象,光是知道大家都能指向这个对象,难道经过这个无名对象就能找到大家么(所以原型链中才有“儿子知道爸爸和爷爷,但是爸爸只知道爷爷不知道儿子,爷爷不知道爸爸和儿子”,也就是单项链不是双向的)?这就好比,PersonA、PersonB都有个无名氏的电话号码,但是光凭这一点就能证明这个无名氏有PersonA、PersonB的电话号码么?
我想反驳他的就是实际上通过实例a的原型属性__proto__是找不到构造函数B的属性prototype的,更加不能找到B,因为那个(A.prototypeB.prototype都指向的)无名对象里存的constructor是指向的A这个构造函数。
刚开始,我还说除非a通过a.__proto__找到A.prototypeB.prototype,然后转为字符串截取出来B,还是说得通。可今天仔细想想,不对!还是我开始理解的那种instanceof两边的操作数指向各自的一个对象,再判断各自指向的对象是否是同一个。

问题

抱歉,“前情概要”啰嗦了很多,敢问sf大神,在不改动变量访问者的前提下,有否方法监听变量被访问的次数?
我想通过这个方法来检测到instanceof判断实例时,B.prototypeB.prototype.constructor有没有被访问,以此作为依据来证实我的猜测/理解。

致歉

如果以上问题让各位有点绕懵了,非常抱歉!也许是我有点钻牛角尖,但是我始终没有证据证明我的理解,非常困惑,自己解决不了,才来sf求助的。感谢回答的各位。

阅读 5.4k
4 个回答

所以你的意思是1和3应该返回false?


下面是ECMA里面对instanceof的说明:

11.8.6 The instanceof operator

  1. Let lref be the result of evaluating RelationalExpression.

  2. Let lval be GetValue(lref).

  3. Let rref be the result of evaluating ShiftExpression.

  4. Let rval be GetValue(rref).

  5. If Type(rval) is not Object, throw a TypeError exception.

  6. If rval does not have a [[HasInstance]] internal method, throw a TypeError exception.

  7. Return the result of calling the [[HasInstance]] internal method of rval with argument lval.

前面都是表达式计算和取值,关键是第7步,x instanceof Y最后会调用内部方法:Y.HasInstance(x)。下面来看看这个方法。

15.3.5.3 [[HasInstance]] (V)

  1. If V is not an object, return false.

  2. Let O be the result of calling the [[Get]] internal method of F with property name "prototype".

  3. If Type(O) is not Object, throw a TypeError exception.

  4. Repeat

4.1. Let V be the value of the [[Prototype]] internal property of V.
4.2. If V is null, return false.
4.3. If O and V refer to the same object, return true.

省掉那些检查代码,概括起来,Y.HasInstance(x)会执行以下步骤:

  1. Y = Y.prototype

  2. Repeat:

2.1 x = x.__proto__
2.2 if (x == null) return false
2.3 if (x === Y) return true

注意判断是递归的,直到x的原型链到达顶端为止。

结论:你的理解是对的。

a instanceof B;//true,#1 instanceof操作符并不检查a是否由B构造函数创建的,而是检查B.prototype是否在a对象的原型链中.a通过A构造函数创建,而在之前B.prototype = A.prototype;导致的结果为B.prototype和A.prototype都指向同一个对象,a.__proto__指向的对象为A.prototype指向的对象;这个对象和B.prototype指向的对象相同。那么通过检查a的原型链就能发现B.prototype指向的对象能够被找到(A.prototype指向的对象),输出结果为true.
同理,下面的代码,#5输出true,#6输出false。比较的实际的对象;和构造函数和名字无关,#7和#8输出的差别说明了这一点

function C(){};
function D(){};
function E(){};
C.prototype = A.prototype;
E.prototype.constructor=A;
c=new C();
e=new E();
c instanceof A;//true #5
c instanceof D;//false #6
e instanceof E;//true #7
e instanceof A;//false #8

补充:
原型链中有记录所有原型对应的构造函数的prototype属性的索引么?原型连接是通过prototype(构造函数)/__proto__(实例对象)属性将不同的对象串联起来的。原型也是对象,没有什么特别的~~
就像变量赋值

var a={name:'a'};
var b=a;
a=null;
console.log(b);//此时的b还是指向{name:'a'};a被设置为null,b的指向依旧在

看看原型链是怎么回事,如下:

function A(){};
function B(){};
function E(){};
A.prototype=new B();//A.prototype指向B的实例对象
E.prototype=new A();//E.prototype指向A的实例对象
E.prototype.constructor=E;//[0]恢复E原型对象的构造函数为E,没有这句不影响最终的输出,原因上面有提到
var e=new E();//[1]
e instanceof A;//true [2]
e instanceof B;//true [3]
e instanceof E;//true [4]

[0] 执行完毕后,E.prototype指向A的实例对象instanceA,这个实例对象instanceA.__proto__指向A.prototypeA.prototype指向B的实例对象isntanceB,这个实例对象instanceB.__proto__指向B.prototype;B.prototype指向的也是一个实例对象instance_UNKNOWN,这个实例对象instance_UNKNOWN.__proto__指向Object.prototype,而Objet.prototype指向为null;原型连接结束

E.prototype-->instanceA.__proto__-->A.prototype-->instanceB.__proto__-->B.prototype-->instance_UNKNOWN.__proto__-->Object.prototype-->null

明白了这个链条,我们就好理解为什么[1][2][3][4]都输出true了

不明白你的问题的问题在哪.

实际上通过实例a的原型属性__proto__是找不到构造函数B的属性prototype的

B.prototype.attr = 1;
a.__proto__ // => { attr: 1 }

更加不能找到B

为什么需要从B.prototype找到B? 如果不能会怎样?

instanceof 运算符一种实现:

function _instanceof(L, R) {
  var L = L.__proto__;
  while (true) {
    if (L === R.prototype) {
      return true;
    }
    if (L === null){
      return false
    }
    L = L.__proto__;
  }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题