# 原型、原型链面试题答案解析
关于答案解析
- 原型、原型链面试题答案解析过程是根据自身项目最佳实践以及查阅官方文档等最终的得出结论。
- 仅供学习参考,评论区感谢补充和纠错 !
# 1、如何理解原型和原型链 ?(腾讯、货拉拉、字节、招银、阿里、小米)
答案解析
- JavaScript 是一门基于原型的语言,在软件设计模式中,有一种模式叫做原型模式
- JavaScript 正是利用这种模式而被创建出来。
关于原型和原型链,我们需要了解以下几个方面的内容:
问题 | 相关知识点 |
---|---|
什么是原型和原型特点 | 构造函数、对象实例、prototype、__proto__ 、constructor |
什么是原型链 | 绘制实例对象原型链、数组原型链、原型链的终点 |
原型链查找 | 原型链查找、属性遮蔽 |
重写原型带来的问题 | 切断现有实例与新原型之间的联系、 会导致原型对象的 constructor 属性指向 Object |
Object.create() 方法 | 手写 create 方法、用 create 方法实现继承 |
instanceof 操作符 | 用法和实现原理 |
in 和 hasOwnProperty 操作符 | in 与 hasOwnProperty 的对比、如何判断一个属性是原型上属性 |
for ...in | for...in 的用法、可枚举对象的哪些属性? |
扩展知识点:
方法 | 说明 |
---|---|
Object.keys() | Object.keys()方法可以将一个对象作为参数,然后把这个对象[key,value] 对中的 key 值以数组的形式遍历出来。 |
Object.values() | Object.values()方法可以将一个对象作为参数,然后把这个对象[key,value] 对中的 value 值以数组的形式遍历出来。 |
Object.entries() | Object.entries()方法可以将对象作为参数,返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性) |
getOwnPropertyNames() | 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。 |
# ① 原型和原型特点
- 我们创建的每一个函数都有一个prototype(原型)属性,被称为显示原型,这个属性是一个指针,指向一个对象(原型对象)。
- 这个对象的好处是,在它上面定义的属性和方法可以由特定类型的所有实例共享。
- 原型对象默认拥有一个 constructor 属性,指向它的构造函数
代码解析
<script>
/*Person 构造函数*/
function Person(name, age) {
this.name = name;
this.age = age;
}
/*构造函数的原型上添加方法*/
Person.prototype.sayHello = function () {
console.log(`大家好,我是${this.name}今年${this.age}岁了`);
};
/*构造函数的原型上添加方法*/
Person.prototype.study = function () {
console.log(`我${this.name}要学习了`);
};
let p1 = new Person("小明", 23); // p1为 构造函数Person new出来的实例对象
/*实例对象上的属性会屏蔽(遮蔽)原型上同名的属性*/
p1.study = function () {
console.log(`我${this.name}正在学习web前端开发课程`);
};
</script>
原型结构图:
- 原型对象默认拥有一个 constructor 属性,指向它的构造函数
console.log(Person.prototype.constructor === Person); // true
- 每个
对象实例
都有一个隐藏的属性__proto__
,被称为隐式原型
,指向它的构造函数的原型
console.log(p1.__proto__ === Person.prototype); // true
- 对象实例可以共享原型上面的所有属性和方法
p1.sayHello(); // 大家好,我是小明今年23岁了
- 实例自身的属性会
屏蔽(遮蔽)
原型上同名的属性,实例上没有的属性就会去原型上去找
p1.study(); // 我小明正在学习web前端开发课程
# ② 原型链
- JavaScript 中所有的对象都是由它的原型对象继承而来。
- 而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链
以下代码的原型链结构图:
<script>
/*Person 构造函数*/
function Person(name, age) {
this.name = name;
this.age = age;
}
/*构造函数的原型上添加方法*/
Person.prototype.sayHello = function () {
console.log(`大家好,我是${this.name}今年${this.age}岁了`);
};
/*构造函数的原型上添加方法*/
Person.prototype.study = function () {
console.log(`我${this.name}要学习了`);
};
let p1 = new Person("小明", 23); // p1为 构造函数Person new出来的实例对象
/*实例对象上的属性会屏蔽(遮蔽)原型上同名的属性*/
p1.study = function () {
console.log(`我${this.name}正在学习web前端开发课程`);
};
console.log(p1.__proto__ === Person.prototype); //true
console.log(Person.prototype.constructor === Person); //true
console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Object.prototype.constructor === Object); //true
console.log(Object.prototype.__proto__); //null
</script>
原型链结构图:
- 所有原型链的终点都是 Object.prototype
Objec.prototype
指向的原型对象同样拥有原型Object.prototype.__proto__,不过它的原型是 null
,而null
则没有原型
# ③ 原型链查找
详细解读
- 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果这个对象本身没有这个属性时,它就会去他的
__proto__隐式原型
上去找(即它的构造函数的 prototype)。 - 如果还找不到,就去原型的原型(
即构造函数的prototype的__proto__
)上去找,....一直找到最顶层(Object.prototype
)为止。 - 如果还没有找到,则返回 undefined。
点击查看源代码
<script>
Object.prototype.sayHello = function () {
console.log(`大家好,我是${this.name}`);
};
class People {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
console.log(`${this.name}正在吃饭`);
}
}
//Student类继承People类
class Student extends People {
constructor(name, age, id) {
super(name, age);
this.id = id;
}
eat() {
console.log(`${this.name}正在吃肉肉`);
}
study() {
console.log(`${this.name}正在学习`);
}
}
const s1 = new Student("小明", 15, "0001");
s1.run = function () {
console.log(`${this.name}每天都要跑步`);
};
s1.run(); //小明每天都要跑步 自身找到,以自身为主
s1.study(); //小明正在学习 自身没有,沿原型链查找,在Student.prototype中找到
s1.eat(); //小明正在吃肉肉 自身没有,沿原型链查找,在Student.prototype中找到,就不再向上找了。
s1.sayHello(); //大家好,我是小明 自身没有,沿原型链查找,在Object.prototype中找到
</script>
# ④ 重写原型带来的问题
- 在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的联系
- 如果要重写原型,一定要在重写原型后,再创建实例。
<script>
function Person(name) {
this.name = name;
}
let p1 = new Person("小明");
Person.prototype.eat = function () {
console.log(`${this.name}在吃饭`);
};
// 重写原型
Person.prototype = {
name: "小明",
sayHello() {
console.log(`大家好,我是${this.name}`);
},
};
p1.eat(); // 小明在吃饭
p1.sayHello(); // p1.sayHello is not a function
</script>
- 在这个例子中,Person 的实例在重写原型对象之前创建的,在调用 p1.eat()时会输入正确的信息。
- 但是在调用 p1.sayHello 时候,会报错。是因为 p1 指向的原型还是最初的原型,而这个最初的原型上并没有 sayHello 这个方法,而且 eat 这个方法。
- 重写原型对象,会导致原型对象的
constructor
属性指向Object
,导致原型链关系混乱,所以我们应该在重写原型对象的时候指定constructor
( 指定后instanceof
仍然会返回正确的值)
function Person() {}
Person.prototype = {}; //重写原型,{}是一个对象实例,对象实例的原型指向的是Object.prototype,而Object.prototype中的constructor指向的是Object
console.log(Person.prototype.constructor === Object); //true
重写原型对象时,单独指定 constructor
<script>
function Person() {}
//重写原型,在prototype中需要重新指定constructor的值
Person.prototype = {
constructor: Person,
};
console.log(Person.prototype.constructor === Person); //true
</script>
# ⑤ Object.create
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
语法:
Object.create(proto[,propertiesObject])
- proto 新创建对象的原型对象
- propertiesObject 可选。需要传入一个对象,将为新创建的对象添加指定的属性值和对应的属性描述符。
手写 create 方法
<script>
Object.create = function (o) {
function Fn() {}
Fn.prototype = o;
return new Fn();
};
</script>
用 Object.create 实现类式继承
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info("Shape moved.");
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
# ⑥ instanceof 操作符
- instanceof 用来判断两个对象是否属于实例关系,通过这种关系来判断对象是否属于某一类型。(但不能判断对象具体属于哪种类型)
- instanceof 可以准确判断引用数据类型,它的原理是检测构造函数的 prototype 属性 是否在某个 实例对象的原型链上
<script>
function Person() {}
let p1 = new Person();
console.log(p1 instanceof Person); //true
console.log(p1 instanceof Object); //true
</script>
<script>
let arr = [1, 2, 3];
console.log(arr instanceof Array);
let date = new Date();
console.log(date instanceof Date);
</script>
# ⑦ in 操作符
用来判断该属性是否在实例或原型上,不管该属性是在实例上还是原型上,只要能找到就返回 true
<script>
function Person() {}
Person.prototype.name = "张三";
let p1 = new Person();
p1.age = 23;
console.log("name" in p1); //true 在原型Person.prototype
console.log("age" in p1); // true 来自实例
console.log("toString" in p1); //在原型Object的.prototype上
</script>
# ⑧ hasOwnProperty
- 用来判断某个属性是否是实例自身的属性,如果是返回 true,不是则返回 false
- hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数
对象实例.hasOwnProperty(属性名);
<script>
function Person() {}
Person.prototype.name = "张三";
let p1 = new Person();
p1.age = 23;
console.log(p1.hasOwnProperty("name")); //false 在原型上
console.log(p1.hasOwnProperty("age")); // true 来自实例
console.log(p1.hasOwnProperty("toString")); //false 在原型上
</script>
案例:如何判断某个属性是否是原型上的属性
- 只要属性在原型或实例身上,in 操作符就会返回 true。而 hasOwnProperty()只有属性存在于实例上时才返回 true。
- 因此,只要 in 操作符返回 true,同是 hasOwnProperty 返回 false,就说明该 属性就是一个原型属性。
function hasPrototypeProperty(obj, name) {
return name in obj && !obj.hasOwnProPerty(name);
}
// hasOwnProperty 是 Object 原型上的方法
console.log(Object.prototype.hasOwnProperty("hasOwnProperty")); // true
# ⑨ for...in
以任意顺序遍历一个对象的可枚举属性(除 Symbol 类型的属性)
for (let key in object) {
// key 是一个变量,每次循环时会将object的一个属性的键值给变量key,直到对象中所有属性都遍历完
// 要执行的代码
}
以下对象中的 Symbol 创建的属性不会被枚举出来
<script>
var s = Symbol("不可遍历属性");
let obj = {
name: "张三",
age: 23,
[s]: 2,
};
for (let key in obj) {
console.log(key); // name age
}
</script>
一般情况下,for in 循环只会遍历我们自定义的属性,原型上默认的属性不会遍历出来。
例如:Object.prototype.toString()
、Object.prototype.hasOwnProperty()
是不会被遍历出来的。
但在实际应用中,如果是在原型中新增属性或者方法,for…in 会将原型中新增的属性和方法遍历出来。
<script>
Object.prototype.score = 100;
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log(`大家好`);
};
const p1 = new Person("小明", 23); //自身属性只有name age
for (let key in p1) {
console.log(key); // name age sayHello score
}
</script>
如果不想过滤原型中的属性,可以用 hasOwnProperty 来做判断。
# ⑩ Object.keys() 、Object.values()、Object.entries( )
详细解读
以上三个方法,都只会枚举对象自身可枚举的属性,不会枚举原型上的。同时也不会枚举 Sysmbol 类型属性。
- Object.keys()方法可以将一个对象作为参数,然后把这个对象[key,value]对中的 key 值以数组的形式遍历出来。
Object.keys(obj);
- Object.values()方法可以将一个对象作为参数,然后把这个对象[key,value]对中的 value 值以数组的形式遍历出来。
Object.values(obj);
- Object.entries()方法可以将对象作为参数,返回一个给定对象自身可枚举属性的键值对数组,其排列与使用
for...in
循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)
Object.entries(obj);
点击查看源代码,案例演示
<script>
Object.prototype.score = 100;
let s = Symbol("不可枚举");
const obj = {
name: "张三",
age: "23",
[s]: "不可枚举",
};
var arrkeys = Object.keys(obj);
var arrvalues = Object.values(obj);
var arrentries = Object.entries(obj);
console.log(arrkeys); // ['name', 'age']
console.log(arrvalues); // ['张三', '23']
console.log(arrentries); // [['name','张三'],['age',23]]
</script>
# ⑪、getOwnPropertyNames( )
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
<script>
Object.prototype.name = "数组";
let arr = [1, 2, 3];
let arr2 = Object.getOwnPropertyNames(arr);
console.log(arr2); //['0','1','2','length']
console.log(Object.keys(arr)); //['0','1','2']
for (let key in arr) {
console.log(key); // 0 ,1,2,name
}
</script>
# 总结:可枚举对象中属性的范围
可枚举方法 | 自身可枚举属性 | 自身不可枚举 | Sysmbol 类型 | 原型上属性 |
---|---|---|---|---|
for...in | ✔ | ✘ | ✘ | ✔(自定义原型属性可枚举,默认原型上的不可以) |
Object.keys | ✔ | ✘ | ✘ | ✘ |
Object.values | ✔ | ✘ | ✘ | ✘ |
Object.entries | ✔ | ✘ | ✘ | ✘ |
Object.getOwnPropertyNames | ✔ | ✔ | ✘ | ✘ |
# 2、原型链的终点是什么?(货拉拉)
答案解析
原型链的终点是 null ,因为 Object.prototype.__proto__
指向 null
# 3、说说 [] 的原型链 ?(腾讯)
答案解析
[].__proto__
指向 Array.prototypeArray.prototype.__proto__
指向Object.prototype
Object.protytype.__proto__
的最终指向为 null
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true 所有数组都是由Array构造出来
console.log(Array.prototype.__proto__ === Object.prototype); // true Array构造函数的是由 Object构造出来的。
console.log(Object.prototype.__proto__); // null Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型
[] 的原型链结构图
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X