面试题答案
一键面试函数构造函数定义
- 内存管理:
- 当定义一个函数构造函数时,JavaScript引擎会在内存中为该函数分配空间。这包括存储函数的代码(字节码等形式)以及函数相关的元数据,如函数名、形参列表等。
- 函数的可执行代码会被解析并编译成内部表示形式,这个过程可能会涉及到一些优化,例如提前解析一些常量表达式等。
- 作用域链处理:
- 函数构造函数在定义时,会创建一个作用域。这个作用域会包含函数定义时所在环境的变量对象(VO)或活动对象(AO,在函数激活时)。
- 函数的作用域链(scope chain)会被初始化,它由函数自身的作用域(包含形参和局部变量)和其外部作用域组成。外部作用域就是函数定义时所在的环境的作用域,这形成了一个链式结构。例如,如果函数
A
在全局作用域中定义,那么函数A
的作用域链的顶端是函数A
自身的作用域,其外部作用域就是全局作用域。
- 原型链构建:
- 每个函数构造函数都有一个
prototype
属性,这个属性指向一个对象,称为原型对象。 - 原型对象默认有一个
constructor
属性,它反过来指向函数构造函数本身。例如,对于构造函数Person
,Person.prototype.constructor === Person
。
- 每个函数构造函数都有一个
实例化对象
- 内存管理:
- 当使用
new
关键字调用函数构造函数进行实例化时,首先会在内存中分配一块新的空间用于存储新创建的对象。 - 新对象内部会包含一个隐藏属性
[[Prototype]]
(在ES6之前没有标准的访问方式,ES6之后可以通过Object.getPrototypeOf()
或__proto__
访问,__proto__
不推荐使用但兼容性较好),这个属性指向构造函数的原型对象。
- 当使用
- 作用域链处理:
- 新实例化的对象本身没有自己独立的作用域,它的属性查找会沿着原型链进行,而原型链与作用域链是不同的概念。但是在属性查找过程中,如果涉及到函数调用等操作,函数的作用域链会起作用。例如,如果实例对象调用了构造函数原型上定义的一个方法,该方法在执行时,会使用其定义时的作用域链。
- 原型链构建:
- 新创建对象的
[[Prototype]]
属性被设置为构造函数的prototype
对象,从而构建起原型链。例如,如果Person
是一个构造函数,let person = new Person()
,那么person.[[Prototype]] === Person.prototype
。属性查找时,先在对象自身查找属性,如果找不到,就会沿着原型链在Person.prototype
及其原型(如果存在)上查找。
- 新创建对象的
调用函数
- 内存管理:
- 当函数被调用时,会为函数创建一个新的执行上下文(execution context)。执行上下文包含变量对象(VO)或活动对象(AO,在函数激活时),用于存储函数的局部变量、形参以及函数调用时的一些内部状态。
- 函数执行完毕后,其执行上下文会从执行栈中弹出,相关的内存(如局部变量占用的内存等)如果不再被引用,就会被垃圾回收机制回收。例如,函数内部定义的局部变量,在函数执行完毕且没有外部引用的情况下,就会被回收。
- 作用域链处理:
- 函数执行上下文的作用域链是在函数调用时确定的。它基于函数定义时的作用域链,并在函数激活时创建一个新的活动对象(AO)作为作用域链的顶端。
- 当函数访问变量时,会从作用域链的顶端(活动对象)开始查找,如果找不到,就沿着作用域链向下查找,直到全局作用域(如果存在)。例如,函数内部访问一个变量,先在函数的活动对象中查找该变量,如果不存在,就去外部作用域查找。
- 原型链构建(与调用相关):
- 如果函数是作为对象的方法被调用(例如
obj.method()
),并且该方法是在对象的原型链上定义的,那么在方法执行过程中,this
会指向调用该方法的对象。例如,person.sayHello()
,如果sayHello
方法定义在Person.prototype
上,在sayHello
方法内部,this
指向person
对象。这与原型链的关系在于,this
对象的属性查找会沿着原型链进行,从而使得方法可以访问到原型链上的属性和方法。
- 如果函数是作为对象的方法被调用(例如