Sandra310/blog

一JS面试题引发的关于原型链、继承、this上下文的思考。

Opened this issue · 4 comments

某天浏览网页,看到了一个面试题,考察方面很全,我看到时是很懵逼的。若是你们很轻松的得到结果,那本文就无需再看啦~
话不多说,题目如下!

  function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = { demo: 5 };
    this.show = function () {
      console.log(this.a , this.b , this.c.demo);
    }
  }
  function Child() {
    this.a = 2;
    this.change = function () {
      this.b.push(this.a);
      this.a = this.b.length;
      this.c.demo = this.a++;
    }
  }
  Child.prototype = new Parent();
  var parent = new Parent();
  var child1 = new Child();
  var child2 = new Child();
  child1.a = 11;
  child2.a = 12;
  parent.show();
  child1.show();
  child2.show();

  child1.change();
  child2.change();
  parent.show();
  child1.show();
  child2.show();

我们来运行一下这段代码,得到了如下结果

image

下面我们来一点一点梳理

首先呢,是变量在内存中的存储方式
众所周知,js的数据类型分为两种,基本类型(String Number Boolean Null Undefined) 与引用类型(Array)等,借用一张图:很明确的告诉我们数据类型与引用类型的差别。

image

我们在此进行扩展,如何检测一个变量是不是基本类型呢?
typeof是最佳工具!

typeof "Nicholas"  //string
typeof true //boolean
typeof 22 //number
typeof null //object
typeof new Object() //object

在检测引用类型的时候,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。
因此可以使用 instanceof person instanceof Object //true
如果用instanceof来检测基本数据类型的话,都返回false,因为基本类型不是对象。

回来,介绍我们的执行环境与作用域:
每个函数都有自己的执行环境,当执行流 进入一个函数时,函数的环境会被推入一个叫环境栈的地方,在执行过后,栈将其弹出。把控制权交还给之前的执行环境。当关闭网页或浏览器时,全局执行环境才销毁。
当代码在一个环境中运行,会创建变量对象的一个作用域链。如果这个环境是函数,那么将其活动对象作为变量对象,初始时只包含arguments变量。作用域链的下一个变量对象来自包含环境,以此类推扩大。

上面那张图还可以帮助我们理解JS内存回收机制。
在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。此时一定有同学想问,垃圾收集器的运行周期是多少呢? 在下没查到,只能引用红宝书的一段话:

垃圾收集器都是周期性运行的,而且如果为变量分配的内存数量很客观,那么回收工作量也是相当大的。IE6的垃圾收集器是根据内存分配量运行的,具体一点说就是256个变量、4096个对象(或数组)字面量和数组元素(slot)或者64KB的字符串。达到上述任何一个临界值,垃圾收集器就会运行。这种实现的问题在于,如果一个脚本中包含那么多变量,那么该脚本很可能会在其生命中起一支保持那么多的变量。而这样一来,垃圾收集器就可能不得不频繁的运行。结果,由此引发的严重性能问题初始IE7重写了其垃圾收集例程。
随着IE7的发布,其javascript引擎的垃圾收集例程改变了工作方式:触发垃圾收集的变量分配、字面量和(或)数组元素的临界值被调整为动态修正。IE7中的各项临界值在初始化时与IE6相等。如果例程回收的内存分配量低于15%,则变量 、字面量和(或)数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界重置会默认值。这一看似简单的调整,极大地提升了IE在运行包含大量javascript的页面时的性能。

参考文章:
前端基础进阶系列
从 []==![] 为 true 来剖析 JavaScript 各种蛋疼的类型转换

不太明白最后输出的this.b为何是[1,2,1,11,12],烦请指点

抱歉才看到您的提问,因为this始终是指向自身的对象,那么child1与child2在执行change方法时需要调用b属性,发现自身没有,那么去找原型链上一层,即new Parent()出来的实例。child1与child2的[[prototype]]原型都指向的是同一个实例,共享同一个b,所以执行两次change会分别把自身的属性a追加到b上。
可以打印 alert(child1.b === child2.b)看看结果,指向的是同一块地址

@Sandra310
我又梳理了一遍,明白了~

这道题目不错,最后两个很容易出错。