继承就是一个对象可以访问另外一个对象中的属性和方法。最典型的继承方式有基于类的设计和基于原型继承的设计。js仅仅在对象中引入一个原型的属性来实现继承。
1. 原型继承的概念
JS的每个对象都包含一个隐藏属性__proto__,这个隐藏属性称为该对象的原型prototype,__proto__在内存中指向的对象为该对象的原型对象,访问该对象就能直接访问其原型对象的属性和方法。JS查找属性时优先从自身对象查找,如果查找不到,就回去原型上查找,查找的路径称为原型链。
作用域链和原型链的实现方式类似:
- 作用域链是沿着函数的作用域一级一级来查找变量。
- 原型链是沿着对象的原型一级一级查找属性的。
继承就是一个对象可以访问另外一个对象中的属性和方法,在JS中,通过原型和原型链来实现继承。
2. 构造函数创建对象
function Person(name, age){
this.name = name;
this.age = age;
};
let male = new Person('male', 20);
首先创建一个构造函数,然后使用new关键字实例化。当通过new关键字实例化工厂函数时,JS虚拟机会返回一个对象,针对new操作V8模拟代码如下:
let male = new Person('male', 20);
let male = {};
male.__proto__ = Person.prototype;
Person.call(male, 'male', 20);
- 第一步:创建空对象male
- 第二步:给male设置原型对象Person.prototype
- 第三步:调用Person方法,Person中的this指向新创建的对象male,给this赋值
3. 构造函数实现继承
函数的隐藏属性包括:name, code, prototype....
当使用构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的prototype属性。当创建多个对象时,这些对象的原型就都指向了同一个,也就是该函数的prototype属性。所以修改该构造函数的prototye属性,就实现了继承。

4. V8查找变量的方式
V8查找变量是基于作用域链查找的。作用域链就是将一个个作用域串起来,实现变量查找的路径。全局函数有全局作用域,每个函数有自己的作用域。全局作用域在V8启动过程中就创建了,且一直保存在内存中不会被销毁,直至V8退出。函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就会被销毁。
作用域链工作机制
- V8启动时,首先创建全局作用域,全局作用域中包括this, window等变量和接口。
- V8启动后,消息循环系统开始工作,优先解析顶层代码,在解析过程中将顶层定义的变量和声明的函数都添加到全局作用域中。
- 全局作用域创建完成后,V8进入执行状态。继续创建函数作用域。
- 作用域链创建完成后,查找作用域的顺序是按照函数定义时的位置来决定的,而不是函数执行时的顺序。因为这个顺序是在声明函数就确定好了,所以也称为静态作用域,词法作用域。
var type = 'global'
function bar() {
var type = 'function'
function foo() {
console.log(type)
}
foo()
}
bar()
// 作用域链查找 foo => bar => global
// function
var type = 'global'
function bar() {
var type = 'function'
foo()
}
function foo() {
console.log(type)
}
bar()
// 作用域链查找 foo => global
// global
5. 总结
- 当使用构造函数来创建一个新的对象时,新创建对象的原型对象就指向了该函数的prototype属性,使用prototype实现原型链的继承。
- 作用域是存放变量和函数的地方。当在某个函数中使用某个变量时,V8会去这些作用域中查找相关变量,查找的路径就是作用域链。
- 作用域链的路径是按照词法作用域来实现的,也就是函数定义时的位置来决定。