【面试题】对象、原型、原型链与继承 ,你了解多少?,2024中高级前端面试题合集

44 阅读9分钟

最后前端到底应该怎么学才好?

如果你打算靠自己摸索自学,那么你首先要了解学习前端的基本大纲,这是你将要学习的主要内容,理解以及掌握好这些内容,便可以找到一份初级的前端开发工作。你还需要有一套完整的前端学习教程,作为初学者最好的方式就是看视频教程学习,初学者容易理解接受。

不要选择买书学习,这样的方式没有几个人能学会,基本都是看不下去书,也看不懂书。如果喜欢看书的学弟,可以买一些经典的书籍作为辅助即可,主要还是以看教程为主。每天抽出固定几个小时学习,做好长期学习的准备。学习编程并不是每天光看视频,你学习编程最重要的目的是为了编写软件产品,提供给大众使用,所以用手写出代码实现功能才是我们要做的事情。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述 开源分享:docs.qq.com/doc/DSmRnRG… console.log("我会游泳哦,你会吗"); }; 复制代码


* 对象字面量



const person = { running: function () { console.log("你会我也会啊"); }, name: "moment", age: 7, dance: function () { console.log("我还会跳舞呢"); }, }; 复制代码


* 两个实例中,每创建一个对象,都要为其创建一个name,age的属性以及running的方法,那么有没有什么方法可以让我们可以减少一些重复的代码呢,答案是有的。我们可以把方法抽取出来定义成一个函数,再给对象赋值。但是......



construnning = (info) => { console.log(info); }; const person = newObject(); person.name = "moment"; person.age = 18; person.running = running;

const object = { running, name: "moment", age: 7, dance: function () { console.log("我还会跳舞呢"); }, };

// ,似乎只能封装公共的方法,属性无法动态传值,只能是固定的一个值

person.running("我们大家都会游泳哦"); // 我们大家都会游泳哦 object.running("我们大家都会游泳哦"); // 我们大家都会游泳哦复制代码


#### 工厂函数




> 

>  抽象工厂模式(英语:Abstract factory pattern)是一种软件开发设计模式。抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。 

>  


* 按我个人的理解,工厂模式就像是一个模具,你能根据这个模具去生产产品,你只能改变其外观颜色,但是你不能改变其形状和大小。



functioncreateObject(name, age, info) { const obj = newObject(); obj.name = name; obj.age = age;

obj.running = function () { console.log(info); };

return obj; }

const person = createObject("moment", 18, "我会跑步"); const student = createObject("supper", 16, "我会跑步,我比你还年轻呢");

person.running(); // 我会跑步 student.running(); // 我会跑步,我比你还年轻呢复制代码


* 通过工厂模式,我们可以快速创建大量相似对象,没有重复代码。迎面走来走来的又是一个新问题,工厂模式创建的对象属于Object,无法区分对象类型,这也是工厂模式没有广泛使用的原因。


#### 构造函数模式


* 没有显式地创建对象


* 属性和方法直接赋值给了this


* 没有return



functionPerson(name, age, info) { this.name = name; this.age = age;

this.running = function () { console.log(info); }; }

const person = newPerson("moment", 18, "我会跑步"); const student = newPerson("supper", 16, "我会跑步,我比你还年轻呢");

person.running(); // 我会跑步 student.running(); // 我会跑步,我比你还年轻呢复制代码


* 以上代码实际上执行的是这样的操作



person.name = "moment"; person.age = 18; person.running = function () { console.log("我会跑步"); };

student.name = "moment"; student.age = 16; student.running = function () { console.log("我会跑步,我比你还年轻呢"); }; 复制代码


* 要创建Person实例,应使用new操作符。


1. 在内存创建一个新对象。


2. 这个新对象内部的[[Prototype]] (proto)特性被赋值为构造函数的prototype属性, 即 person.proto=Person.prototype。


3. 构造函数内部的this被赋值为这个新对象(即this指向新对象)。


4. 执行构造函数内部的代码. 如 person.proto.name='moment'。


5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。


* new的过程可以参考以下代码



functionmyNew(func, ...args) { // 判断方法体if (typeof func !== "function") { throw"第一个参数必须是方法体"; } // 创建新对象// 这个对象的[[prototype]](隐式原型 proto)指向 func 这个类的原型对象 prototype// 即实例可以访问构造函数原型 obj.constructor === Personconst object = Object.create(func.prototype);

// 构造函数内部的this被赋值为这个新对象const result = func.apply(object, args);

// 如果构造函数返回的结果是引用数据类型,则返回运行后的结果// 否则返回新创建的 objconst isObject = typeof result === "object" && result !== null; const isFunction = typeof result === "function";

return isObject || isFunction ? result : object; } 复制代码


* 通过控制台打印不难发现,前面两个对象都是Object的实例,同时也是Person的实例



// instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上// person和student的隐式原型都指向Person的显式原型;console.log(person.proto === student.proto); //trueconsole.log(person instanceofObject); //trueconsole.log(person instanceofPerson); //trueconsole.log(student instanceofObject); //trueconsole.log(student instanceofPerson); //true// 以上代码的代码实际上执行的该例子console.log(student.proto.proto.constructor.prototype === Object.prototype); console.log(student.proto.constructor.prototype === Person.prototype); 复制代码


* 构造方法虽然有用,但是也存在问题,虽然person和student都有一个方法running,但这两个方法不是同一个Function的实例,指向的内存也各自不同。



console.log(student.running === person.running); //false复制代码


* 都是做着同样的事情,但是每个实例都创建出一个方法,这就造成了没必要的内存损耗,那么有没有什么办法可以很好的解决这个问题呢,这时候,原型模式就出现了。


#### 原型模式


* 每个函数都会创建一个prototype属性,这个属性是一个对象,该对象可以给每个实例共享属性和方法。


* 我们对之前的代码进行改造。



functionPerson(name, age) { this.name = name; this.age = age; }

// 在这里,把方法running定义在了Person的prototype属性上// person和student共享同一个原型方法running,指向的是同一快内存空间Person.prototype.running = function (info) { console.log(info); };

const person = newPerson("moment", 18); const student = newPerson("supper", 16);

// 在构造函数中这里输出的是falseconsole.log(person.running === student.running); //true

person.running("我会跑步"); // 我会跑步 student.running("我会跑步,我比你还年轻呢"); //我会跑步,我比你还年轻呢复制代码


* 在前面的new操作符可以知道,person和student的隐式原型等于Perosn的显式原型


1. 首先person和student现在自己身上查找有没有running方法,没有找到。


2. 去原型里查找,也就是通过person.\_\_proto\_\_或者student.proto,该方法,由于person.proto=Person.prototype,所以调用person.running()实际上调用的是Person.prototype.running()方法





![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/ab8f8cf7ccf84e0d817a396a6912d50f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771314986&x-signature=A9BCXSKABOsUCx89%2FVTmmk7lXS0%3D)



* 具体的内存表现形式如下图所示。





![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/5a9da9e118ec48f98fe81909ed30d8d9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771314986&x-signature=LwqWso1bkXhtmFzIprmCWJgqlJk%3D)



* 通过上图,一下代码的输出就能理解了。



console.log(Person.prototype.constructor === Person); // trueconsole.log(student.proto.constructor === Person); // true复制代码


#### 原型层级


* 在讲之前先祭出两张神图





![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0ddeecca06a8464589a2ba76a0e52616~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771314986&x-signature=mL%2Buy7ZXRiVspOqxrKLtgq9fiSc%3D)






![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6fd02916319e4f44ab64e1630bb672d3~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771314986&x-signature=pvFA2xQCNMHL%2FI7ibjrlnCZMGB8%3D)



* 在讲原型之前,我们先来认识一下认识一下FunctionObject的关系!


1.JavaScript中,每个JavaScript函数实际上都是一个Function对象。


2.JavaScript 中,几乎所有的对象都是 Object 类型的实例,它们都会从 Object.prototype 继承属性和方法。


3. 从原型链上讲,Function继承了Object4. 从构造器上讲,Function构造了Object5. 好了,看到这里,晕了没?


6. 接下来通过代码的展示,应该能更清楚的说明了上面两张图中的所讲的意思了。



functionPerson(name, age) { this.name = name; this.age = age; } // 在这里,把方法running定义在了Person的prototype属性上了Person.prototype.running = function (info) { console.log(info); };

const obj = {};

const person = newPerson("moment", 18); const student = newPerson("supper", 16);

console.log(obj.proto === Object.prototype); // trueconsole.log(Object.constructor === Function); // trueconsole.log(Function.prototype.proto === Object.prototype); // trueconsole.log(Function.constructor === Function); // trueconsole.log(Person.proto.constructor.proto === Function.prototype); // trueconsole.log(student.proto.proto.constructor.proto === Function.prototype); // trueconsole.log(Function.constructor.proto === Function.prototype); // trueconsole.log(Object.constructor === Function.constructor); // trueconsole.log(ObjectinstanceofFunction); // trueconsole.log(FunctioninstanceofObject); // trueconsole.log(Function.prototype.proto === Object.prototype); // true复制代码


* 原型查找机制,首先在实例上查找,有则返回,没有则往上查找,这时候查找到了原型上了,如果能找到有该属性或方法,则返回,如果仍然没找打,就在Object的原型上查找,找到了则返回,没有就返回undefined或者报错。



functionPerson(name, age) { this.name = name; this.age = age; this.sayName = function () { console.log("我是在实例身上的"); }; this.memory = "我是属于实例的"; } // 在这里,把方法running定义在了Person的prototype属性上了Person.prototype.running = function () { console.log("我是原型上的方法"); };

Object.prototype.牛逼 = "这是真的";

Person.prototype.memory = "我是属于原型的";

const person = newPerson("moment", 18);

console.log(person.name); // 来自实例console.log(person.memory); // 来自实例 person.sayName(); // 来自实例 person.running(); // 来自原型console.log(person.牛逼); // 这是真的console.log(person.六六六); // undefined复制代码


#### 原型模式的弊端


* 在继承之前,我们每个类都要给其定义属于它自己的属性和方法,但是如果出现相同的方法,我们都要给他重新定义一遍,这样未免出现过多重复代码。



functionStudent() {} functionTeacher() {}

Student.prototype.running = function () { console.log("学生"); }; Student.prototype.eating = function () { console.log("吃"); }; Student.prototype.study = function () { console.log("学习"); };

Teacher.prototype.running = function () { console.log("老师 }; Teacher.prototype.teach = function () { console.log("吃"); }; 复制代码


#### 原型链继承


* 父类的原型直接赋值给子类的原型


1. 父类和子类共享同一个原型对象,修改了任意一个,另外一个也被修改。


2. 这是一个错误的做法。



functionStudent() {} functionTeacher() {}

Teacher.prototype.running = function () { console.log("老师"); }; Teacher.prototype.teach = function () { console.log("吃"); };

Student.prototype = Teacher.prototype;

const student = newStudent(); student.running(); // 老师

student.proto.running = function () { console.log("我被修改了"); };

const teacher = newTeacher(); teacher.running();// 我被修改了复制代码


* 正确的原型链继承



functionStudent() {} functionTeacher() {}

Teacher.prototype.running = function () { console.log("老师"); };

Teacher.prototype.teach = function () { console.log("教"); };

const teach = newTeacher(); Student.prototype = teach;

const student = newStudent();

student.running = function () { console.log("我被修改了"); };

const smallStudent = newStudent(); smallStudent.running(); // 老师 继承于Teacher原型 student.running(); // 我被修改了 来自于实例本身复制代码


* 通过一张图来展示子类的两个实例和两个构造函数及其对应的原型之间的关系



### 紧跟潮流



大前端和全栈是以后前端的一个趋势,懂后端的前端,懂各端的前端更加具有竞争力,以后可以往这个方向靠拢。

这边整理了一个对标“阿里 50W”年薪企业高级前端工程师成长路线,由于图片太大仅展示一小部分

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://docs.qq.com/doc/DSmRnRGxvUkxTREhO)**

![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/06cba82712224b70995f36be7cf0da80~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771314986&x-signature=TNqhYQe%2BKz%2FQpPXRjP0WrqBsThU%3D)