JavaScript 是一种弱类型、基于原型的编程语言。原型是 JavaScript 的一项非常重要的特性,也是面试中经常考察的知识点。JS 原型链是基于原型的面向对象编程的基石,掌握这一知识点对于我们理解 JavaScript 的对象模型非常重要。本文将带你从多个方面深入剖析JavaScript原型链面试题。
一、JS原型链基本概念
在 JavaScript 中,每个函数都有一个 prototype 属性,也就是原型对象。原型对象是一个普通的对象,包含属性和方法。每个实例对象都通过 __proto__ 属性引用其构造函数的原型对象 prototype。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.sayHello = function() { console.log(`Hello, I'm ${this.name}, I'm ${this.age} years old.`); } const person = new Person('Tom', 18);
在以上代码中,Person 函数有一个 prototype 属性,它是一个普通的对象,并且包含一个方法 sayHello。person 是使用 Person 函数构造出来的实例对象, 它通过 __proto__ 属性引用了 Person 的原型对象 Person.prototype,因此它可以使用原型对象中的方法 sayHello。
二、JS原型链的构建方式
原型链是由每个对象的 __proto__ 属性构成的。我们可以通过以下方式构建原型链:
1. 新建一个构造器函数
2. 扩展构造器的原型对象
3. 使用 new 操作符创建子对象
4. 构造器原型对象的 __proto__ 属性指向构造器父级的原型对象
5. 子对象的 __proto__ 属性指向了构造器的原型对象,也就是形成了原型链
function Animal(name) { this.name = name; } Animal.prototype.sayName = function () { console.log(this.name); } function Dog(name, age) { this.age = age; Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); // Dog 继承 Animal Dog.prototype.constructor = Dog; // constructor 指向 Dog Dog.prototype.sayAge = function () { console.log(this.age); } const dog = new Dog('Lily', 1);
在以上代码中,Dog 继承了 Animal 的方法以及属性。通过 Dog.prototype = Object.create(Animal.prototype),Dog 的原型指向了 Animal 的原型,因此它可以使用 Animal 的原型中的方法 sayName,在 Dog 的原型中添加 sayAge 方法。
三、JS原型链的查找顺序
当我们在一个实例对象上调用一个属性或者方法时,JavaScript 引擎会按照如下的顺序查找属性或方法:
1. 首先查找实例对象本身是否有该属性或方法
2. 如果没有,则查找实例对象的原型对象是否有该属性或方法
3. 如果还没有,则查找实例对象原型对象的原型对象是否有该属性或方法
4. 重复上述步骤,直到查找到 Object 的原型对象,即可结束查找
function Animal(name) { this.name = name; } Animal.prototype.sayName = function () { console.log(this.name); } function Dog(name, age) { this.age = age; Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.sayAge = function () { console.log(this.age); } const dog = new Dog('Lily', 1); console.log(dog.__proto__ === Dog.prototype); // true console.log(Dog.prototype.__proto__ === Animal.prototype); // true console.log(Animal.prototype.__proto__ === Object.prototype); // true
在以上代码中,dog 是 Dog 的实例对象。当我们在 dog 上调用 sayAge 方法时,首先查找 dog 本身是否有该方法,发现没有。然后查找 Dog.prototype 是否有该方法,发现有。因此调用了该方法并输出了 dog 的年龄。在这个查找的过程中,JS 引擎按照 __proto__ 属性指向的原型对象继续查找,直到最后找到 Object 的原型对象 Object.prototype 为止。
四、JS原型链的继承方式
在 JavaScript 中,有很多实现继承的方法。下面分别介绍一下常用的几种继承方式:
1. 构造函数继承
构造函数继承是一种常用的继承方式。其基本思想是在子类的构造函数中调用父类的构造函数。
function Parent(age) { this.age = age; } function Child(age) { Parent.call(this, age); } const child = new Child(18); console.log(child.age); // 18
在以上代码中,Child 函数的构造函数中调用了 Parent 的构造函数并传入参数。Child 实例可以访问到 Parent 实例中的属性 age。
2. 原型链继承
原型链继承的基本思想是通过将子类的原型对象指向父类的实例对象来实现继承。
function Parent(age) { this.age = age; } Parent.prototype.sayAge = function () { console.log(this.age); }; function Child(age) {} Child.prototype = new Parent(18); const child = new Child(); console.log(child.age); // 18 child.sayAge(); // 18
以上代码中,Child 的原型指向了 Parent 的实例,并且 Child 的实例可以访问到 Parent 中的方法和属性。
3. 组合继承
组合继承是上述两种继承方式的结合。其基本思想是通过构造函数继承实现属性的继承,通过原型链继承实现方法的继承。
function Parent(age) { this.age = age; } Parent.prototype.sayAge = function () { console.log(this.age); }; function Child(age) { Parent.call(this, age); } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; const child = new Child(18); console.log(child.age); // 18 child.sayAge(); // 18
以上代码中,Child 函数的构造函数中调用了 Parent 的构造函数并传入参数,实现了属性的继承。Child 的原型对象指向了 Parent 的原型对象,并且将 constructor 指向了 Child 函数,实现了方法的继承。
结语
JavaScript 原型链是一种非常重要的特性。理解原型链能够帮助我们更好地理解 JavaScript 的对象模型和继承机制。掌握 JavaScript 原型链的实现方式以及查找顺序可以帮助我们更好地回答面试题。在实际项目中,可以根据练习的需求选择不同的继承方式以实现需求。