JS面向对象面试题及答案
1.构造函数和普通函数有啥区别?用new调用构造函数时发生了什么?
答案:
核心区别有两点:
调用方式:构造函数必须用new调用,普通函数不用;
this指向:构造函数里this指向新创建的实例,普通函数(非严格模式)this指向window/global,严格模式下是undefined。
new的执行过程(关键!):
functionPerson(name){
this.name=name;
}
constp=newPerson(小明);
//1.创建空对象:constobj={};
//2.绑定原型:obj.__proto__=Person.prototype;
//3.绑定this:Person.call(obj,小明)→obj.name=小明;
//4.返回实例:如果构造函数没返回对象,就返回obj(即p)
注意:如果构造函数主动返回一个对象,new会直接返回这个对象,而非默认实例。
2.实例的__proto__和构造函数的prototype啥关系?原型链有啥用?
答案:
关系:实例的__proto__指向其构造函数的prototype(这是原型链的基础)。
比如constarr=[],arr.__proto__===Array.prototype。
原型链作用:实现“属性继承”和“方法复用”。
当访问一个对象的属性时,先查自身→查__proto__(构造函数原型)→查__proto__.__proto__(原型的原型)→...直到null(比如Object.prototype.__proto__是null)。
例:arr.push(1),arr自身没有push,会去Array.prototype找,这就是原型链的作用。
3.ES5怎么实现继承?比如用Animal做父类,写个Dog子类举例,并说优缺点
答案:
ES5常用“组合继承”(实例属性+原型方法结合),代码如下:
//父类:动物
functionAnimal(type){
this.type=type;//实例属性(比如“哺乳动物”)
this.legs=4;//公共实例属性
}
//父类原型方法(复用,所有实例共享)
Animal.prototype.eat=function(){
console.log(`${this.type}吃食物`);
};
//子类:狗
functionDog(type,name){
//1.继承父类实例属性(call改变this指向)
Animal.call(this,type);
this.name=name;//子类独有的实例属性
}
//2.继承父类原型方法(绑定原型链)
Dog.prototype=newAnimal();
//修正constructor指向(否则Dog实例的constructor会指向Animal)
Dog.prototype.constructor=Dog;
//子类原型方法
Dog.prototype.bark=function(){
console.log(`${this.name}汪汪叫`);
};
//测试
constwangcai=newDog(哺乳动物,旺财);
wangcai.eat();//哺乳动物吃食物(继承父类方法)
wangcai.bark();//旺财汪汪叫(子类方法)
优缺点:
优点:既继承实例属性(如type),又继承原型方法(如eat),是ES5里最常用的继承方式。
缺点:父构造函数Animal会被调用两次(一次newAnimal()绑定原型,一次Animal.call()传属性),导致子类原型上多余父类的实例属性(比如Dog.prototype上会有type和legs,但实例wangcai自身也有,会覆盖)。
4.ES6的class和ES5构造函数有啥区别?class是语法糖吗?
答案:
class本质是“语法糖”,但比ES5构造函数多了些特性,核心区别:
写法更规范:class有constructor、extends、super等关键字,结构更像传统面向对象(如Java),不用手动绑定原型和修正constructor。
例:用class重写上面的继承:
classAnimal{
constructor(type){
this.type=type;
this.legs=4;
}
eat(){//直接写在class