这道题是我面试的时候碰到的

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();

首先画出原型链图

下来逐句进行分析

1.parent.show()。直接取值1 [1,2,1] 5

2.child1.show()。

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

关于b:由于child1是没有b这个属性的,所以会沿着原型链取到Child Prototype上面的b属性,其值是一个数组,前面两项值固定,数组的最后一项是一个引用(this.a)。此处的this并不是一个动态指向,因为在Child.prototype = new Parent()这个语句中的new Parent()已经确定了this的指向,即指向实例指向的资源也就是1(这也就是下文提到的作为构造函数调用时,即new操作符生成实例时,构造函数中的this指向实例).所以b的值是[1,2,1]

也可以粗暴的这样理解this.b没有,顺着原型链网上找,找到了Child.Prototype.this.b=[1,2,this.a],this.b里面的this.a的this指的是Child Prototype,所以this.b=[1,2,1]

关于c:由于child1是没有c这个属性的,所以会沿着原型链取到Child Prototype上面的c属性。所以this.c.demo为5

最后打印11 [1,2,1] 5

3.child2.show()。和上面解释一样打印12 [1,2,1] 5

4.child1.change()

this.b.push(this.a);

        this.b会指向Child.prototype上的b数组[1,2,1],this.a会指向child1的a属性11,所以Child.prototype.b变成了[1,2,1,11];

this.a = this.b.length;

        this.b指向的仍然是Child.prototype上的b数组[1,2,1,11],所以this.a(也就是child1.a)变成了4

this.c.demo = this.a++;

        this.c指向的是Child.prototype上的c{demo:5},因为this.a(也就是child1.a)是4,是一个原始类型,故赋值操作会直接赋值(这块待会再讨论),Child.prototype.c.demo的结果为4,而this.a随后自增为5(4 + 1 = 5)。

所以这一句执行之后,console.log(this.a , this.b , this.c.demo );会打印5 [1,2,1,11],4

5.child2.change()

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

this.b.push(this.a);

        this.b会指向Child.prototype上的b数组[1,2,1,11],this.a会指向child2的a属性12,所以Child.prototype.b变成了[1,2,1,11,12];

this.a = this.b.length;

        this.b指向的仍然是Child.prototype上的b数组[1,2,1,11,12],所以this.a(也就是child2.a)变成了5

this.c.demo = this.a++;

        this.c指向的是Child.prototype上的c{demo:4},因为this.a(也就是child2.a)是5,是一个原始类型,故赋值操作会直接赋值(这块待会再讨论),Child.prototype.c.demo的结果为5,而this.a随后自增为5(6 + 1 = 5)。

所以这一句执行之后,console.log(this.a , this.b , this.c.demo );会打印6 [1,2,1,11,12],5

延伸思考

自己在解题时,在this.c.demo = this.a++出错,本以为这里会传引用,但实际是传了值,分析后明白因为this.a指向的是一个原始值,故此处相当于将原始值赋值给对象属性,所以赋值后child.c.demo的值不会再受到child.a的变化的影响。如果child.a是一个引用类型,那么结果会变成什么样子呢?

我们对源码做一些修改,将child.a指向一个对象(即引用类型):

然后运行后就会发现,Child.prototype.c的值会随着child1.a的变化而变化,因为此时child1.a的值是一个引用类型,赋值过程会使得Child.prototype.c和child1.a指向同一份资源的内存空间地址。对于原始类型和引用类型更详细的解说,可以参考篇尾扩展阅读中的博客。

6.parent.show();

上面的child1.change()和child2.change()只是操作的图1,  图2和图1是相互独立的,所以这一步打印1 [1.2,1] 5

7.child1.show():5 [1,2,1,11,12] 5

8.child2.show():5 [1,2,1,11,12] 5

这个理解起来真费劲,一口老血喷了出来!

------------------------------------------------------------------------华丽分割线---------------------------------------------------------------------------------

javascript基础修炼——一道十面埋伏的原型链面试题https://mp.weixin.qq.com/s/IyizX_HkmWXMLjwD6gVcLQ

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

题目是这样的

要求写出控制台的输出.

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();

题目涉及的知识点

1、this的指向

2、原型机原型链

3、类的继承

4、原始类型和引用类型的区别

解题需要的知识点细节:

1.构造函数,都有一个prototype属性,指向构造函数的原型对象,实例会共享同一个原型对象;

2.实例生成时,会在内存中产生一块新的堆内存,对实例的一般操作将不影响其他实例,因为在堆内存里占据不同空间,互不影响;

3.每一个实例都有一个隐式原型__proto__指向构造函数的原型对象;

4.this的指向问题,常见的情况包含如下几种:

  • 作为对象方法时,谁调用就指向谁(本题中主要涉及这一条)

  • 作为函数调用时,指向全局顶层变量window

  • 作为构造函数调用时,即new操作符生成实例时,构造函数中的this指向实例

  • call和apply方法中,显示指定this的绑定为指定上下文

5.字面量的方式(也有资料将literal翻译为直接量,个人认为后一种翻译其实更直观更形象)进行对象和数组赋值(数组本质也是对象)时,都是引用,即在堆内存生成资源,在栈内存生成变量,然后变量指向资源的地址。

6.原型链的查找规则遵循最短路径原则,即先查找实例属性,然后顺着原型链去查找指定的属性,直至原型链末端的Object.prototype和null,如果实例自身及整个原型链都不存在所查找的属性则返回undefined

7.赋值语句对于原始值赋值和引用类型赋值时的细节区别。

开始剖题

1.parent.show()

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

2.child1.show()

Child的构造函数原本是指向Child的

题目中显式将Child类的原型对象指向了Parent类的一个实例,这是javascript面向对象编程中常见的继承方式之一。此处需要注意Child.prototype指向的是Parent的实例parent,而不是指向Parent这个类

直接在控制台操作输出答案可得11 [1,2,1] 5

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

先来看一下child1的样子:

当执行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()的结果呢:

答案是木有影响,为什么看起来指向同一个地址的属性却出现值不一样的情形?因为parent实例生成的时候,this.a指向了一个原始值2,所以this.b中的第三项实际上是被赋值了一个原始值,故此处乍看起来像是引用类型的赋值,实则不是。原始值赋值会开辟新的存储空间,使得this.a和this.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]的值,代码和运行结果如下所示:

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会指向child1的a属性,所以Child.prototype.b变成了[1,2,1,11];

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

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

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

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

this.a = this.b.length
这条语句中this.a和this.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指向一个对象(即引用类型):

然后运行后就会发现,Child.prototype.c的值会随着child1.a的变化而变化,因为此时child1.a的值是一个引用类型,赋值过程会使得Child.prototype.c和child1.a指向同一份资源的内存空间地址。对于原始类型引用类型更详细的解说,可以参考篇尾扩展阅读中的博客。

收获和反思

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

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

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

本文为华为云社区用户“大史不说话”原创内容,特此感谢!转载时必须标注文章的来源“华为云社区”,文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。

JavaScript系列—一道十面埋伏的原型链面试题相关推荐

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

    在基础面前,一切技巧都是浮云. 题目是这样的 要求写出控制台的输出 题目涉及的知识点 1.this的指向 2.原型机原型链 3.类的继承 4.原始类型和引用类型的区别 5.每一个知识点都可以拿出来做单 ...

  2. 进击的 JavaScript 之(七) 原型链

    原文链接:周大侠啊 进击的 JavaScript (七) 之 原型链 算是记录一下自己的学习心得吧,哈哈 首先说一下,函数创建的相关知识 在JavaScript中,我们创建一个函数A(就是声明一个函数 ...

  3. JavaScript系列之详解原型和原型链

    文章の目录 1.prototype 2.__proto__ 3.constructor 4.实例与原型 5.原型的原型 6.原型链 参考 写在最后 1.prototype 在 JavaScript 中 ...

  4. JavaScript简餐——继承之原型链继承

    文章目录 前言 一.实现方式 二.继承实例 三.问题所在 1.引用值误修改 2.子类型实例化时无法给父类构造函数传参 四.总结 前言 写本<JavaScript简餐>系列文章的目的是记录在 ...

  5. javascript es6 属性 __proto__ prototype 原型链 简介

    目录 prototype __proto__ 原型链 构造函数创建对象实例 今天同事小英童鞋问了我一个问题: function Foo(firstName, lastName){this.firstN ...

  6. javascript对象、类与原型链

    js是一个基于对象的语言,所以本文研究一下js对象和类实现的过程和原理. 对象的属性及属性特性 下面是一个对象的各个部分: var person = {name: "Lily",a ...

  7. JavaScript 变量、函数与原型链

    定义 || 赋值 1-函数的定义 函数定义的两种方式: "定义式"函数:function fn(){ alert("哟,哟!"); } "赋值式&qu ...

  8. JavaScript的面向对象原理之原型链

    二.JavaScript的对象 为了能够清楚的解释这一切,我先从对象讲起.从其他面向对象语言(如Java)而来的人可能认为在JS里的对象也是由类来实例化出来的,并且是由属性和方法组成的. 实际上在JS ...

  9. Javascript Prototype污染攻击(原型链污染,Bugku-web-sodirty wp)

    prototype 它表示原型,样本,标准. 在javascript中,你使用构造函数时会创建一些属性和方法. 在构造方法时,你书写了函数的内容,那么,当你每创建一次对象时就会执行一次函数内容并将方法 ...

最新文章

  1. StringUtils.join的用法
  2. Compmgmtlauncher.exe问题解决方法
  3. 十大不变计算机网络安全法则
  4. 树莓派命令行配置无线网络和SSH远程登陆
  5. 使用OGR创建dxf格式矢量数据
  6. MySQL的体系结构是C S结构_c/s结构的数据库系统结构是指
  7. 人家可是见过大世面的
  8. C++中的函数汇总(新手必知)!
  9. c++中STL的常用算法---2(遍历算法,查找算法)
  10. 【029】获取选择要素的属性
  11. 疑似荣耀V30 Pro 5G网络测试截图曝光:前置开孔双摄加持
  12. 解决PyScripter中文乱码问题
  13. kali 克隆网页_Web侦察工具HTTrack (网站克隆)
  14. 64位系统装32位mysql有问题吗_32位系统部署到64位下常见问题及解决
  15. 学习Linux/Unix这么久了,你真的知道什么是终端吗?
  16. 【用Unity实现抛物线向目标点发射炮弹功能】
  17. Xtext语言语法介绍
  18. Oracle 查看和修改数据库时区
  19. ArangoDB——操作案例二
  20. 联想微型计算机怎么拆开,联想昭阳e42g拆机步骤

热门文章

  1. 搜索和查询html代码,html查找框功能
  2. CS-NLR 学习笔记(三)——块匹配
  3. android分享分享到朋友圈图片,android app分享微信小程序(包含封面网络图片)+图片到朋友圈...
  4. python刷新获取实时数据_Python实时数据更新解决方法
  5. 使用python制作一个简易的远控终端
  6. School StartsFirstProject~UnityVR(HTCVive设备开发)
  7. c语言 %是什么,c语言里%是什么意思
  8. Axure rp9 3658可用注册码 授权码 激活码 key 亲测可用
  9. 如何在Boxee中管理电影
  10. html实现经典捕鱼达人小游戏