面试怎么回答:JS中的构造函数、原型和实例对象!

175 阅读7分钟

想到学了JS构造函数、原型和实例对象,那面试官可能这么提问?于是先让AI帮我生成对应题目,我再进行解答以及对应知识点讲解。如果面题那块表述有误,还请大佬指正

面题

1. 解释什么是JavaScript中的构造函数?

回答: 构造函数用于初始化创建的对象,通常用首字母大写,来区别其他类型函数。当用new关键字调用构造函数时,会创建一个新的对象,并且绑定到函数内的this关键字,然后初始化属性和方法等。

2. JavaScript中的class关键字是什么?它是如何工作的?

回答class关键字是es6中引入,提供更简洁方式定义对象构造器,也称为“类”。class是基于原型继承模型,但是提供更加面向对象编程的语言结构。通过class定义的类可以包含构造函数(使用constructor关键字定义)、实例方法、静态方法static等。使用extends关键字实现类之间的继承,而super关键字则调用父类构造函数或是方法。

3. 在JavaScript中,原型对象是如何工作的?

回答:每个JavaScript都要这样一个prototype属性,这个属性也是一个对象,包含所有实例共享属性和方法。当构造函数new一个新对象时,新对象内部会有一个指向构造函数原型对象的链接(在非严格模式下,这个链接通过对象__proto__属性访问)。这很明显的优势在于所有同一个构造函数创建的对象都是可以访问相同的原型上的属性和方法,这有助于节省内存并且支持继承。

4. 什么是原型链?它是如何帮助实现继承的?

回答:原型链是JavaScript中实现继承的一种机制。每个对象都有一个内部属性 [[Prototype]],通常通过 __proto__ 访问,指向其原型对象。当访问对象的属性或方法时,JS引擎首先在对象本身查找,若未找到,则沿着 [[Prototype]] 链向上查找,直至到达原型链的顶端 Object.prototype。如果仍未找到,则返回 undefined。通过这种机制,对象可以继承原型链上任意位置的属性和方法。

5. 如何理解JavaScript中的this关键字?

回答this关键字指向取决于函数被调用方式。在构造函数,指向新创建的对象;在对象方法在,通常指向该方法的对象;在普通函数。指向全局对象(浏览器环境下是window对象,在node.js环境下是global对象)。此外,使用call()apply()bind()方法可以显式地指定this的值。(2)

6. 如何在JavaScript中实现对象的继承?

回答:在JavaScript中,对象继承主要通过原型链或ES6的classextends实现。前者通过将子类构造函数的prototype设为父类实例来继承属性和方法;后者则以更面向对象的方式定义类及其继承关系。核心在于使子类既能访问父类特性,也能扩展自身功能。

知识点深入

1. 构造函数与实例对象

构造函数是一种特殊的函数,主要用于创建特定类型的对象。构造函数通常首字母大写,以区别于普通函数。当使用new关键字调用构造函数时,JavaScript会自动创建一个新的空对象,并将这个新对象绑定到函数内的this关键字,接着执行构造函数体内的代码来初始化这个对象。

function Person(name, age) {
    console.log(this); // 输出:Person {}
    this.name = name;
    this.age = age;
}

let person1 = new Person('动漫', 11);
let person2 = new Person('柯南', 17);

在这个例子中,每次调用new Person(...)时,都会创建一个新的Person实例,并将传入的参数分别赋值给实例的nameage属性。

2. 原型对象与属性查找机制

每个函数都有一个prototype属性,这个属性是一个对象,包含了所有实例共享的属性和方法。当通过构造函数创建一个新对象时,这个新对象内部会有一个指向构造函数原型对象的链接(在非严格模式下,这个链接可以通过对象的__proto__属性访问)。

Person.prototype.name = '红砖';
Person.prototype.hometown = '北京';

console.log(person1.name, person1.hometown, person2.name); // 输出:动漫 北京 柯南

这里,person1person2两个实例都没有自己的hometown属性,因此当访问这两个属性时,JavaScript会沿着原型链向上查找,最终在Person.prototype上找到了hometown属性。

3. 对象字面量与类

除了通过构造函数创建对象外,JavaScript还提供了对象字面量和类两种方式来创建对象。

  • 对象字面量:这是一种非常直接且灵活的方式,但不适合需要大量创建相似对象的场景。
let cao = {
    name: '小超'
};
  • :类是ES6引入的新特性,它提供了一种更简洁的方式来定义对象构造器。类内部可以包含构造函数、实例方法、静态方法等。
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    eat() {
        console.log('吃饭');
    }
}
let one1 = new Person('何伟', 10);

4. 原型链与继承

原型链是JavaScript中实现继承的主要方式。当尝试访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,JavaScript引擎就会沿着这个对象的原型链向上查找,直到找到该属性或方法为止。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.eat(); // 方法  实例对象上没有  去原型链上找
}
Person.prototype.eat = function () {
    console.log('吃饭');
};
const wen = new Person('wen'); // age 为 undefined
console.log(wen); // 输出:Person { name: 'wen', age: undefined }

在这个例子中,虽然wen实例自身没有eat方法,但由于Person.prototype上有这个方法,所以调用wen.eat()时,JavaScript会在原型链上找到并执行该方法。

5. this关键字的绑定

在JavaScript中,this关键字的值取决于函数被调用的方式。在构造函数中,this指向新创建的对象;在对象的方法中,this通常指向调用该方法的对象;如果是作为普通函数调用,this通常指向全局对象(在浏览器环境中是window对象,在Node.js环境中则是global对象)。

const da1 = {
    name: '哈市',
    play: function () {
        console.log(this); // 输出:{ name: '动漫', age: 11 }
        console.log('haha');
    }
};

Person.prototype = da1;

const ds = new Person('动漫', 11);
ds.play(); // 输出:{ name: '动漫', age: 11 } 和 haha

在这个例子中,ds.play()调用时,this指向的是ds对象,因为play方法是在ds对象上调用的。

使用call()apply()bind()方法可以显式地指定this的值

1. 使用 call()

假设我们有两个对象,一个表示人,另一个表示动物,都有一个 speak 方法,但我们想让动物发出人的声音:

let person = {
    name: "Alice",
    speak: function(greeting) {
        console.log(`${greeting}, my name is ${this.name}`);
    }
};

let animal = {
    name: "Dog"
};

// 使用 call 方法让 animal 发出 person 的声音
person.speak.call(animal, "Hello"); // 输出: Hello, my name is Dog

2. 使用 apply()

假设我们有一个方法需要接收一个数组形式的参数:

let calculator = {
    add: function(a, b) {
        return a + b;
    }
};

let numbers = [5, 7];
console.log(calculator.add.apply(null, numbers)); // 输出: 12

这里,apply 方法的第一个参数是 null,因为我们不关心 add 方法中的 this 值,第二个参数是一个数组,包含要传递给 add 方法的参数。

3. 使用 bind()

bind 方法用于创建一个新的函数,该函数的 this 值被永久绑定到指定的对象上:

let user = {
    name: "Bob",
    greet: function(timeOfDay) {
        console.log(`Good ${timeOfDay}, my name is ${this.name}`);
    }
};

let morningGreeter = user.greet.bind(user, "morning");
morningGreeter(); // 输出: Good morning, my name is Bob

在这个例子中,bind 创建了一个新的函数 morningGreeter,无论何时调用它,都会像调用 user.greet("morning") 一样执行,且 this 指向 user 对象。

总结

通过上述分析,我们可以看到构造函数、原型对象与实例对象之间存在着紧密的联系。构造函数用于创建对象,原型对象用于存储所有实例共享的属性和方法,而实例对象则是具体的存在,每个实例都通过原型链连接到其构造函数的原型对象。

这种设计不仅节省了内存,还支持了继承机制,让人高呼“JS面向对象如此强大”