javascript基础修炼(1)——一道十面埋伏的原型链面试题

简介: 在基础面前,一切技巧都是浮云。题目是这样的要求写出控制台的输出.function Parent() { this.a = 1; this.b = [1, 2, this.

在基础面前,一切技巧都是浮云。

题目是这样的

要求写出控制台的输出.

function Parent() {
            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 Child() {
    this.a = 2;
    this.change = function () {
        this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}
        Child.prototype = new Parent(); 
        var parent = new Parent();
        var child1 = new Child();
        var child2 = new Child();
        child1.a = 11;
        child2.a = 12;
        parent.show();
        child1.show();
        child2.show();
        child1.change();
        child2.change();
        parent.show();
        child1.show();
        child2.show();

题目涉及的知识点

  • this的指向
  • 原型机原型链
  • 类的继承
  • 原始类型和引用类型的区别
    每一个知识点都可以拿出来做单独的专题研究。

解题需要的知识点细节

  • 1.构造函数,都有一个prototype属性,指向构造函数的原型对象,实例会共享同一个原型对象;
  • 2.实例生成时,会在内存中产生一块新的堆内存,对实例的一般操作将不影响其他实例,因为在堆内存里占据不同空间,互不影响;
  • 3.每一个实例都有一个隐式原型__proto__指向构造函数的原型对象;
  • 4.this的指向问题,常见的情况包含如下几种:
    • 4.1 作为对象方法时,谁调用就指向谁(本题中主要涉及这一条)
    • 4.2 作为函数调用时,指向全局顶层变量window
    • 4.3 作为构造函数调用时,即new操作符生成实例时,构造函数中的this指向实例
    • 4.4 callapply方法中,显示指定this的绑定为指定上下文
  • 5.字面量的方式(也有资料将literal翻译为直接量,个人认为后一种翻译其实更直观更形象)进行对象和数组赋值(数组本质也是对象)时,都是引用,即在堆内存生成资源,在栈内存生成变量,然后变量指向资源的地址。
  • 6.原型链的查找规则遵循最短路径原则,即先查找实例属性,然后顺着原型链去查找指定的属性,直至原型链末端的Object.prototypenull,如果实例自身及整个原型链都不存在所查找的属性则返回undefined
  • 7.赋值语句对于原始值赋值和引用类型赋值时的细节区别.

开始剖题

1.parent.show()

基本没什么可解释的。
直接取值就能得出答案1 [1,2,1] 5;

2.child1.show()

Child的构造函数原本是指向Child
img_59952d3c86d1d5a2b34ab6d76d6289bd.png
题目中显式将Child类的原型对象指向了Parent类的一个实例,这是javascript面向对象编程中常见的继承方式之一。此处需要注意Child.prototype指向的是Parent的实例parent,而不是指向Parent这个类
img_0d56360e99ed560a024776468fe3f747.png
直接在控制台操作输出答案可得11 [1,2,1] 5
img_b6faf09a00a14a60cbe9804645ba54c0.png

此处令人迷惑的是this.b指向的数组最后一列为什么是1而不是11?

先来看一下child1的样子:
img_aa654354df51534ee3f3748c4cf20d78.png

当执行child1.show()这个方法时,由于child1作为Child的实例,是拥有a这个属性的,所以show()方法中的this.a会直接指向这个属性的值,也就是11,而不会继续沿原型链取到__proto__所指的对象上的a属性;

接着寻找this.b,由于child1是没有b这个属性的,所以会沿原型链取到parent上的b属性,其值是一个数组,前2项是常量没什么好说的,数组的最后一项是一个引用,而此处的指针并不是一个动态指向,因为在new Parent()这一步的时候它已经被执行过一次,确定指向了parent.a所指向的资源,也就是child1.__proto__中的a属性所指向的资源,即数值1。

延伸思考

需要注意的是:

1.从代码上看,child1.__proto__.b数组的第三项是指向child1.__proto__.a的,那我们此时修改child1.__proto__.a的值,是否会影响child1.show()的结果呢:
img_53aa8299da139abe34a15bff77e8b189.png
答案是木有影响,为什么看起来指向同一个地址的属性却出现值不一样的情形?因为parent实例生成的时候,this.a指向了一个原始值2,所以this.b中的第三项实际上是被赋值了一个原始值,故此处乍看起来像是引用类型的赋值,实则不是。原始值赋值会开辟新的存储空间,使得this.athis.b[2]的值相等,但是却指向了堆内存里的不同地址。更多详细解释可以参见【扩展阅读】中推荐的博文

2.那怎样让child1.__proto__.b数组的第三项也输出11呢?

  • 实例化后修改
    由于在Parent类定义中,b属性数组的第三项是指向a属性的值的,意味着在Parent实例化之前这个引用是动态指向的,所以只要在Parent实例化之前改变类定义中this.a的值,就可以达到想要的效果,如果在Parent已经实例化,则只能显式修改*.b[2]这个属性的值。
  • get/set方法同步
    另一种方式是通过为a属性设置get/set方法,是的每当a属性的值发生变化时,同步修改b[2]的值,代码和运行结果如下所示:
    img_3cf90d4d53b0e4ade5a28d98158c7d10.png
    img_ebf7c29692914296a2ad9ec98b16140b.png
3.child2.show()

如果理解了上面的解释,那么此处同理即可得出答案:12 [1,2,1] 5

接着代码执行了: child1.change(); child2.change();

4.parent.show()

parent是一个Parent类的实例,Child.prorotype指向的是Parent类的另一个实例,两者在堆内存中是两份资源,互不影响,所以上述操作不影响parent实例,
输出结果保持不变:1 [1,2,1] 5;

5.child1.show(),child2.show()

child1执行了change()方法后,发生了怎样的变化呢?

this.b.push(this.a)
由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child1a属性,所以Child.prototype.b变成了[1,2,1,11];

this.a = this.b.length
这条语句中this.athis.b的指向与上一句一致,故结果为child1.a变为4;

this.c.demo = this.a++
由于child1自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.cthis.a值为4,为原始类型,故赋值操作时会直接赋值,Child.prototype.c.demo的结果为4,而this.a随后自增为5(4 + 1 = 5).

接着,child2执行了change()方法, 而child2child1均是Child类的实例,所以他们的原型链指向同一个原型对象Child.prototype,也就是同一个parent实例,所以child2.change()中所有影响到原型对象的语句都会影响child1的最终输出结果

this.b.push(this.a)
由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child2a属性,所以Child.prototype.b变成了[1,2,1,11,12];

this.a = this.b.length
这条语句中this.athis.b的指向与上一句一致,故结果为child2.a变为5;

this.c.demo = this.a++
由于child2自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.c,故执行结果为Child.prototype.c.demo的值变为child2.a的值5,而child2.a最终自增为6(5 + 1 = 6).

接下来执行输出命令,最终结果将输出:
child1.show():5 [1,2,1,11,12] 5
child2.show():6 [1,2,1,11,12] 5

  • 延伸思考
    自己在解题时,在this.c.demo = this.a++出错,本以为这里会传引用,但实际是传了值,分析后明白因为this.a指向的是一个原始值,故此处相当于将原始值赋值给对象属性,所以赋值后child.c.demo的值不会再受到child.a的变化的影响。如果child.a是一个引用类型,那么结果会变成什么样子呢?
    我们对源码做一些修改,将child.a指向一个对象(即引用类型):
    img_c1790dd83d9c564582e08989675361af.png
    然后运行后就会发现,Child.prototype.c的值会随着child1.a的变化而变化,因为此时child1.a的值是一个引用类型,赋值过程会使得Child.prototype.cchild1.a指向同一份资源的内存空间地址。对于原始类型引用类型更详细的解说,可以参考篇尾扩展阅读中的博客。

收获和反思

1.基础知识本来就是零散的细节,必须本着死磕到底的心态进行学习。

2.基础知识是最枯燥的,也是真正拉开人和人之间差距的东西,也是你想进入大厂必须要跨过的门槛,重要却不紧急。同样是菜鸟,有的人3-5年后成为了前端架构师,有的人3-5年后还在用层出不穷的新框架给按钮绑事件,想成为怎样的人,就要付出怎样的努力,大多数时候都是没毛病的。基础很重要!很重要!很重要!

3.基础这个东西是要不断看的,像红宝书(javascript高级程序设计)犀牛书(javascript权威指南)这种书,最好多过几遍,一些难以理解的现象,往往是由于对底层原理理解不到位造成的,买来新书直接用来垫高显示器你不心疼的吗?喜马拉雅上有一个免费的陪你读书系列节目,30多期的音频通篇讲解了红宝书的内容,对不喜欢看书的童鞋绝对是一大福音。

扩展阅读

相关文章
|
1月前
|
JavaScript 前端开发
常见的JS面试题
【8月更文挑战第5天】 常见的JS面试题
50 3
|
30天前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
21 0
|
19天前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
43 0
|
1月前
|
JavaScript 前端开发 程序员
JS小白请看!一招让你的面试成功率大大提高——规范代码
JS小白请看!一招让你的面试成功率大大提高——规范代码
|
27天前
|
JavaScript 前端开发 开发者
揭开JavaScript的神秘面纱:原型链背后隐藏的继承秘密
【8月更文挑战第23天】原型链是JavaScript面向对象编程的核心特性,它使对象能继承另一个对象的属性和方法。每个对象内部都有一个[[Prototype]]属性指向其原型对象,形成链式结构。访问对象属性时,若当前对象不存在该属性,则沿原型链向上查找。
23 0
|
3月前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
76 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
|
1月前
|
存储 JavaScript 前端开发
JS浅拷贝及面试时手写源码
JS浅拷贝及面试时手写源码
|
1月前
|
JavaScript 前端开发
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?
|
2月前
|
缓存 JavaScript 前端开发
js高频面试题,整理好咯
中级前端面试题,不低于12k,整理的是js较高频知识点,可能不够完善,大家有兴趣可以留言补充,我会逐步完善,若发现哪里有错,还请多多斧正。
|
3月前
|
JavaScript
js奥义:原型与原型链(1)
js奥义:原型与原型链(1)