备战26春招,彻底搞懂前端八股之原型链(下):代码输出题详解
昨天我们一起学习了原型和原型链的相关知识点,并给大家布置了代码输出题作业,不知道你有没有全部答对呢?今天,我们接着昨天的内容,详细讲解这些题目究竟输出什么,以及原因是什么
第一题
function Person(name) {
this.name = name
}
var xinyishui = new Person('前端新一水');
console.log(xinyishui.__proto__)
console.log(xinyishui.__proto__.__proto__)
console.log(xinyishui.__proto__.__proto__.__proto__)
console.log(xinyishui.__proto__.__proto__.__proto__.__proto__)
console.log(xinyishui.__proto__.__proto__.__proto__.__proto__.__proto__)
console.log(xinyishui.constructor)
console.log(xinyishui.prototype)
console.log(Person.constructor)
console.log(Person.prototype)
console.log(Person.prototype.constructor)
console.log(Person.prototype.__proto__)
console.log(Person.__proto__)
console.log(Function.prototype.__proto__)
console.log(Function.__proto__)
console.log(Object.__proto__)
console.log(Object.prototype.__proto__)
- xinyishui.proto: 是 Person 的实例,所以 xinyishui.__proto__就指向 Person.prototype。在这段代码中,由于我们没有给 Person.prototype 添加任何属性或方法,所以打印出来的是一个空对象, 它只有两个属性,一个是 constructor 指向 Person,一个是 [[Prototype]] 也就是 proto
- xinyishui.proto.proto:由 1 可知,xinyishui.__proto__就是 Person.prototype,所以 xinyishui.proto.__proto__就是 Person.prototype.proto。由昨天的文章我们知道,Person.prototype 是个对象,所以它的__proto__就是 Object.prototype
- xinyishui.proto.proto.proto:由 2可知,xinyishui.proto.__proto__就是 Object.prototype,所以 xinyishui.proto.proto.__proto__就是Object.prototype.proto,由昨天的文章我们知道,它是 null
- xinyishui.proto.proto.proto.proto:由3可知,xinyishui.proto.proto.__proto__是null,所以 xinyishui.proto.proto.proto.__proto__就是 null.proto,null本身是个空值,没有属性或方法,所以 null 后面加点会报错
- 跟 4 同理,会报错
- xinyishui.constructor:根据我们昨天的文章可以知道,找一个属性会先在自身找,然后沿着原型链往上找。 先在 xinyishui 自身找,发现是个空对象,没找到,进入下一步在 xinyishui.__proto__身上找,也就是在 Person.prototype 身上找,发现找到了 constructor 属性,constructor 是 Person,返回 Person
- xinyishui.prototype:不要看到 prototype 就害怕,我们还是把它当成一个普通的属性,沿着原型链去找就好了: 先在 xinyishui 自身找prototype属性,发现 xinyishui 是个空对象,没找到,进入下一步,也就是 它的 proto在 xinyishui.__proto__身上找,也就是在 Person.prototype 身上找**(注意!不是 在 Person 上找,而是在 Person.prototype 上找!),**发现它里面只有 constructor 和 [[Prototype]]也就是 proto,所以还是没找到,继续下一步,也就是 它的 proto在 xinyishui.proto.__proto__身上找,也就是 Object.prototype 上找,发现它里面还是没有prototype属性,那就继续下一步,也就是 它的 proto在 xinyishui.proto.__proto__身上找,也就是 null,到达原型链尽头,返回 undefined所以 xinyishui.prototype 打印 undefined
- Person.constructor:依旧按着原型链找: 先在 Person 上找,发现没有,那就进入下一步,也就是 它的 proto在 Person.__proto__上找,Person的构造函数是 Function,所以 Person.__proto__就是 Fuction.prototype,发现找到了 constructor 属性,constructor 是 Fuction,返回 Fuction
- Person.prototype,沿着原型链找,第一步在自身找,由上一篇文章我们知道,所有的函数都有 prototype 属性,所以就找到了,是个对象,它里面只有两个属性,一个是 constructor 指向 Person,一个是 [[Prototype]] 也就是 proto。返回这个对象
- Person.prototype.constructor,由上一题我们知道,会返回 Person
- Person.prototype.proto,Person.prototype的构造函数是 Object,所以 Person.prototype.__proto__就是 Object.prototype
- Person.proto,由于 Person 是个函数,所以它的构造函数是 Function,所以 Person.__proto__是 Function.prototype
- Function.prototype.proto,Function.prototype的构造函数是 Object,所以 Function.prototype.__proto__就是 Object.prototype
- Function.proto,由于 Function 是个函数,所以它的构造函数是 Function,所以 Function.__proto__是 Function.prototype(没错,Function的构造函数是它自己,如果你感到惊讶,那你一定没好好看上一篇文章,罚你再看一次)
- Object.proto,由于 Object 是个函数,所以它的构造函数是 Function,所以 Object.__proto__是 Function.prototype
- Object.prototype.proto,这个简单,就是 null
function Person () {
getName = function () {
console.log(1);
}
return this;
}
Person.getName = function () {
console.log(2);
}
Person.prototype.getName = function () {
console.log(3);
}
var getName = function () {
console.log(4);
}
function getName () {
console.log(5);
}
Person.getName();
getName();
Person().getName();
getName()
new Person.getName();
new Person().getName();
执行流程如下:
先扫描有没有函数声明和变量声明,发现有一个变量声明(17行)和一个函数声明(21行)
先执行函数提升,也就是把 21 行的 getName 方法提到最上面进行声明
再执行变量提升,也就是把 17 行的 var getName(注意此时没有赋值,只有声明)提升到最上面进行声明。**注意,此时 getName 这个变量名把上面的函数getName给覆盖了。**同时,由于是 var 关键字声明的变量,所以这个变量会挂载到全局对象 Window 下,也就是 Window.getName 就是这里声明的这个变量,后续getName更新时,Window.getName 也会同步更新
关于变量提升和函数提升,我们在下一篇文章《作用域》中详细描述
提升完毕,开始从上到下执行代码
定义一个函数 Person(注意没有执行)
给 Person 添加一个 getName 方法
给 Person.prototype 添加一个 getName 方法
给一开始提升的变量 getName 赋值一个函数,Window.getName 同步更新
然后开始 25 行,执行 Person.getName(),发现在第 9 行声明了Person.getName,所以执行,打印 2
执行 26 行,这时的 getName 是 17 行赋值的函数,所以打印 4
执行 27 行,先是执行 Person(),此时函数内部又声明了一个函数 getName,由于它前面没有 var、let、const,所以这时候会把这个变量赋值给全局作用域下的 getName 变量(这是为什么呢?具体原因跟作用域有关,同样,在下一篇文章中详细解释),Window.getName 也会同步更新。此时,全局下的 getName 变成了打印 1 的函数。Person() 返回 this,由于 Person 直接在全局中执行,所以this就是全局对象 Window,所以当我们 执行 Person().getName(); 就相当于执行 Window.getName()。 所以此时打印 1
再执行 getName()。 由于在上一步中,全局下的 getName()更新了,所以打印 1
执行 new Person.getName(); 注意,此时 Person后面没有跟 (),所以相当于 把 Person.getName 当构造函数去执行。打印 2,然后创建了一个 Person.getName 的实例(由于没有用变量名去接收,所以没什么卵用)
执行 new Person().getName(); 注意,此时 Person后面有 (),所以相当于 把 Person 当构造函数去执行。当我们把函数当 构造函数 使用时,this指向新创建的实例对象。所以 new Person()就是Person的一个新的实例对象。后面是 .getName,这个 getName 就是 新的实例对象的 getName 方法。这时候就沿着原型链去找,自身没有,去找到 Person.prototype 上面,发现有 getName,所以打印 3
最终结果: 2 4 1 1 2 3
var Person = function() {};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
var xinyishui = new Person();
xinyishui.a();
xinyishui.b();
Person.a();
Person.b()
执行流程如下:
先扫描全局,看看有没有变量声明或者函数声明,发现没有函数声明,只有两个变量声明,分别是 Person 和 xinyishui。所以先执行 var Parson 和 var xinyishui
然后执行第一行,把一个函数赋值给 Person
第二行,给 Object.prototype 增加一个 a 方法
第五行,给 Functtion.prototype 增加一个 b 方法
第八行,创建一个 Person 实例,并赋值给 xinyishui 变量
第九行,执行 xinyishui.a(),首先在 xinyishui 自身找 a,没找到,去 xinyishui.__proto__找,也就是 Person.prototype,也没找到,继续往下找,也就是 xinyishui.proto.proto,也就是 Object.prototype,找到了,执行,打印 a
第 10 行,执行 执行 xinyishui.b(),手下在 xinyishui 自身找 b,没找到,去 xinyishui.__proto__找,也就是 Person.prototype,也没找到,继续往下找,也就是 xinyishui.proto.proto,也就是 Object.prototype,还是没找到,继续找,找到了 Object.prototype.__proto__也就是 null 上,返回 undefined。注意!此时 ,xinyishui.b 是 undefined,但是后面跟了括号,所以会报错。到此为止,其实代码就不会继续往 11、12 行执行了。但是我们为了解释代码执行过程,在这里假设 第 10 行没有报错阻塞执行,继续往下讲解
第 11 行,执行 Person.a(),首先在 Person 自身找 a,没找到,去 Person.__proto__找,也就是 Function.prototype,也没找到,继续往下找,也就是 Person.proto.proto,也就是 Object.prototype,找到了,执行,打印 a
第 12 行,执行 Person.b(),首先在 Person 自身找 a,没找到,去 Person.__proto__找,也就是 Function.prototype,找到了,执行,打印 b
最终结果: a 报错 a b
function Person(){
Person.a = function(){
console.log(1);
}
this.a = function(){
console.log(2)
}
}
Person.prototype.a = function(){
console.log(3);
}
Person.a = function(){
console.log(4);
}
Person.a();
let xinyishui = new Person();
xinyishui.a();
Person.a();
执行流程如下:
先看全局有没有变量提升和函数提升,有一个函数声明 Person,没有 var 声明的变量(注意,只有 var 声明的变量会提升, let 和 const 不会),所以声明函数 Person
然后执行 第 10 行,给 Person.prototype 增加一个 a 方法
执行第 14 行,给 Person 增加一个 a 方法
然后执行第 18 行,执行 Person.a() ,开始沿着原型链找。首先找 Person 自身,发现有(在14行增加的),执行,打印 4
执行第 19 行,创建了一个新的变量 xinyishui,然后 以 Person 为构造函数创建新的实例对象并赋值给 xinyishui。「以 Person 为构造函数创建新的实例对象」就是 执行 Person 函数的过程,只不过会把函数中的 this 指向新创建的实例对象。所以我们来执行 Person 函数,首先是第二行,把另一个函数赋值给 Person.a,然后第5行,前面我们说过,在 new 的时候, this 指向的是新的实例,所以 this.a 实际上就是 xinyishui.a,赋值为一个新的函数
执行 xinyishui.a(),由上一步我们知道,在创建实例的时候通过第 5 行给实例对象绑定了 a 方法,所以会打印 2
Person.a,现在已经被 第二行的函数覆盖,所以打印 1
最终结果: 4 2 1
function Person() {
this.name = '前端新一水'
}
Person.prototype.speak = () => {
console.log('前端面试,看我就够了!')
}
const xinyishui = new Person()
console.log(Person.prototype.constructor === Person && xinyishui.constructor === Person && xinyishui instanceof Person)
、执行过程如下:
先声明一个函数 Person
给 Person.prototype 增加一个 speak 方法
创建一个新的Person实例并赋值给 xinyishui,此时 xinyishui 对象自身有一个 name 方法,值为 '前端新一水'
打印:Person.prototype.constructor 就是 Person,所以 && 的左边为 true;xinyishui.constructor 会沿着原型链找,首先在 xinyishui自身找,找不到,就去 xinyishui.__proto__也就是 Person.prototype 上找,找到了,是 Person,所以也为 true;xinyishui 的原型链上有 Person.prototype,所以 xinyishui instanceof Person 也为 true。均为true,所以最终结果为 true
最终打印结果: true
var A = {n: 1};
var B = function(){this.n = 2};
var C = function(){var n = 3};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);
执行流程如下:
扫描全局,找变量声明和函数声明,准备提升。找到了五个变量声明,没有函数声明。所以把这五个变量都提升到顶部
执行第一行,把一个对象赋值给 A 变量
执行第二行,把一个函数赋值给 B
执行第三行,把一个函数赋值给 C
执行第四行,把 A 赋值给 B 函数的 prototype 属性
执行第五行,把 A 赋值给 C 函数的 prototype 属性
执行第六行,创建一个 B 的实例,并赋值给 b 变量。此时 b 这个对象本身自带了一个 n 属性, value 为 2
执行第七行,创建一个 C 的实例,并赋值给 c 变量
执行第八行, A.n 由 1 变成 2
执行第九行,打印 b.n。沿着原型链找 b 的 n 属性,首先是在 b 自身找,找到了,value 为 2,所以打印 2
执行第十行,打印 c.n。沿着原型链找 c 的 n 属性,首先是在 c 自身找,没找到,然后找 c.proto,也就是 C.prototype,也就是 A,找到了,打印 A.n ,也就是 2
最终结果:2 2
function A(){
}
function B(a){
this.a = a;
}
function C(a){
if(a){
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);
执行流程如下:
扫描全局,找函数声明和变量声明,找到了三个函数声明,没有找到变量声明。把三个函数声明提升到顶部
执行第 11 ~13行,逻辑类似,不赘述
执行第 15 行,创建一个 A 的实例,然后打印实例对象的 a 属性。沿着原型链网上找:先在实例对象自身找,没找到,因为实例对象是个空对象。继续在 实例对象 的 __proto__上找,也就是 A.prototype上找,找到了,是 1,所以打印 1
执行第 16 行,创建一个 B 的实例,然后打印实例对象的 a 属性。在创建实例的过程中,我们把传入的 a 赋值给 实例对象的 a 属性。没有传入实参,所以相当于传入了 undefined,所以实例对象自己身上的 a 就是 undefined。我们打印实例对象的 a 属性,第一步在自身找,找到了,是个 undefined,所以我们就打印 undefined
执行第 17 行,创建一个 C 的实例,然后打印实例对象的 a 属性。在创建实例的过程中,我们把传入的 a 赋值给 实例对象的 a 属性。传入的实参为2,所以实例对象自己身上的 a 就是 2。我们打印实例对象的 a 属性,第一步在自身找,找到了,是个 2,所以我们就打印 2
最终结果: 1 undefined 2
function Person() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Student() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Student.prototype = new Person();
var person = new Person();
var student1 = new Student();
var student2 = new Student();
student1.a = 11;
student2.a = 12;
person.show();
student1.show();
student2.show();
student1.change();
student2.change();
person.show();
student1.show();
student2.show();
执行流程如下:
扫描全局的函数声明和变量声明,并进行提升。由于变量提升对本题没有影响,我们跳过提升的过程
执行第一行,声明一个函数 Person
执行第十行,声明一个函数 Student
执行第十九行,创建一个 Person 的实例,并赋值给 Student.prototype。我们来详细看下 Person 的实例内部包含了什么:首先是一个 a 属性,value 为 1,然后是个 b 属性,是个数组,里面有三个元素,分别是 1,2,this.a。在用 new 调用函数的时候, this 会指向新创建的实例对象,所以 this.a 就是刚刚赋值的 1。然后是 c 属性,是个对象,里面有个 demo 属性,value 为 5。然后有一个 show 方法
执行第二十行,创建一个 Person 函数的实例,并赋值给 person。person 内部的属性,跟十九行相同,我们不赘述
执行第 21 行,创建一个 新的 Student 实例,并赋值给 student1。我们来详细看下 Student 实例对象内部有哪些东西:首先是个 a 属性,value 为 2,然后是 change 方法
执行第 22 行,创建一个 新的 Student 实例,并赋值给 student2。过程跟 21 行类似,不赘述
执行第 23 行,把 11 赋值给 student1.a。首先我们要找到 student1.a 在哪里,沿着原型链找,第一步在 student1 自身找,找到了,原来的value为2,现在用 11 覆盖
执行第 24 行,把 12 赋值给 student2.a。首先我们要找到 student2.a 在哪里,沿着原型链找,第一步在 student2 自身找,找到了,原来的value为2,现在用 12 覆盖
执行 person.show(),person 自身有 show 方法,所以直接执行,打印 a、b、c.demo,这三个属性都在 person 对象自身,a 为 1,b为 [1,2,1],c.demo 为 5
执行 student1.show(),student1 自身没有 show 方法,找到 student1.__proto__也就是 Student.prototype 也就是 一个 Person 实例。找到了 show 方法。然后执行。打印 a、b、c.demo,这三个属性都在 student1 对象自身,a 为 11,b为 [1,2,1],c.demo 为 5
执行 student2.show(),执行过程同上一步,只有 a 不一样,是 12
执行 student1.change,首先把 this.a push 进 this.b。this 指向 student1,所以 this.a 是 11,this.b 实际上是 Student.prototype.b,同理,自身也没有 c ,实际上也是 Student.prototype.c
所以 Student.prototype.b 变成了 [1,2,1,11]; 然后 this.a 变成了 this.b.length,现在 this.b 有 4 个元素,所以 this.a 变成了 4;this.c.demo 变成了 this.a++:这里要当心,this.a++的逻辑是,先把 this.a 赋值给 this.c.demo,然后 this.a变成了原来值+1,也就是说, this.c.demo 变成了 4, this.a++变成了5
执行 student2.chang,首先把 this.a push 进 this.b。this 指向 student2,所以 this.a 是 12,this.b 实际上是 Student.prototype.b,同理,自身也没有 c ,实际上也是 Student.prototype.c
所以 Student.prototype.b 变成了 [1,2,1,11,12]; 然后 this.a 变成了 this.b.length,现在 this.b 有 5 个元素,所以 this.a 变成了 5;this.c.demo 变成了 this.a++:同上,this.a++的逻辑是,先把 this.a 赋值给 this.c.demo,然后 this.a变成了原来值+1,也就是说, this.c.demo 变成了 5, this.a++变成了 6
再次执行 person.show,person自身带有 a、b、c、show,这些都还跟初始一样没变过,所以还是 1 [1,2,1] 5
再次执行 student1.show,刚才已经把student1的a、b、c.demo 都更新,所以会输出 5 [1,2,3,11,12] 5;
再次实行 student2.show,刚才已经把student2的a、b、c.demo 都更新,所以会输出 6 [1,2,3,11,12] 5;
最终输出:
1 [1, 2, 1] 5
11 [1, 2, 1] 5
12 [1, 2, 1] 5
1 [1, 2, 1] 5
5 [1, 2, 1, 11, 12] 5
6 [1, 2, 1, 11, 12] 5
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subProperty;
};
var instance = new SubType();
console.log(instance.getSuperValue());
执行流程如下:
依旧先声明提升,但是不影响,所以我们跳过这个过程
声明一个 SuperType 函数
给 SuperType.prototype 增加一个 getSuperValue 方法
声明一个 SubType 函数
创建一个 SuperType 实例,并赋值给 SubType.prototype
给 SubType.prototype 增加一个 getSubValue 方法
创建一个 SubType 实例,并赋值给 instance
打印 instance.getSuperValue(),沿着原型链找 instance 的 getSuperValue 方法,首先在自身找,自身没有,然后在 instance.__proto__也就是 SubType.prototype 上面找,也就是 SuperType 的实例,发现还是没有,继续在 SuperType 实例 的 __proto__上找,也就是 SuperType.prototype,找到了。然后执行,发现是返回 this.property,由于是 instance 调用的 方法,所以 this 就指向 instance,所以 this.property就是 instance.property。接下来我们就找 instance.property,沿着原型链找:首先在 instance 自身找,没有,然后在 instance.__proto__找,也就是 SubType.prototype 上面找,也就是 SuperType 的实例,发现找到了,是 true。所以打印 true
最终结果:true
#前端实习准备##前端实习##前端八股#前端新一水八股系列,每日讲解一道面试高频八股题,涵盖CSS、JS、Vue、React、工程化、性能优化、场景题等
查看10道真题和解析
百度成长空间 579人发布