javascript基础修炼(1)——一道十面埋伏的原型链面试题
在基础面前,一切技巧都是浮云。
题目是这样的
要求写出控制台的输出.
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
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数据操作--原始值和引用值的操作本质
- [javascript高级程序设计]第4章
javascript基础修炼(1)——一道十面埋伏的原型链面试题的更多相关文章
- javascript基础修炼(2)——What's this(上)
目录 一.this是什么 二.近距离看this 三. this的一般指向规则 四. 基本规则示例 五. 后记 开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一.thi ...
- javascript基础修炼(7)——Promise,异步,可靠性
开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 别人是开发者,你也是 Promise技术是[javascript异步编程]这个话题中非常重要的,它一度让我感到熟悉 ...
- javascript基础修炼(8)——指向FP世界的箭头函数
一. 箭头函数 箭头函数是ES6语法中加入的新特性,而它也是许多开发者对ES6仅有的了解,每当面试里被问到关于"ES6里添加了哪些新特性?"这种问题的时候,几乎总是会拿箭头函数来应 ...
- javascript基础修炼(4)——UMD规范的代码推演
javascript基础修炼(4)--UMD规范的代码推演 1. UMD规范 地址:https://github.com/umdjs/umd UMD规范,就是所有规范里长得最丑的那个,没有之一!!!它 ...
- javascript基础修炼(10)——VirtualDOM和基本DFS
1. Virtual-DOM是什么 Virtual-DOM,即虚拟DOM树.浏览器在解析文件时,会将html文档转换为document对象,在浏览器环境中运行的脚本文件都可以获取到它,通过操作docu ...
- javascript基础修炼(11)——DOM-DIFF的实现
目录 一. 再谈从Virtual-Dom生成真实DOM 二. DOM-Diff的目的 三. DOM-Diff的基本算法描述 四. DOM-Diff的简单实现 4.1 期望效果 4.2 DOM-Diff ...
- javascript基础修炼(3)—What's this(下)
开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 这一期主要分析各种实际开发中各种复杂的this指向问题. 一. 严格模式 严格模式是ES5中添加的javascript的 ...
- javascript基础修炼(5)—Event Loop(Node.js)
开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 一道考察异步知识的面试题 题目是这样的,要求写出下面代码的输出: setTimeout(() => { co ...
- 玩转JavaScript OOP[3]——彻底理解继承和原型链
概述 上一篇我们介绍了通过构造函数和原型可以实现JavaScript中的“类”,由于构造函数和函数的原型都是对象,所以JavaScript的“类”本质上也是对象.这一篇我们将介绍JavaScript中 ...
随机推荐
- VS中自定义类模版
以下为vs2017 专业版,安装目录在D盘 安装路径: D:\Program Files (x86)\Microsoft Visual Studio\\Professional\Common7\IDE ...
- php基础-cookie&session
设置cookie //设置cookie setcookie('key', 'value', time() + 60, '/'); 设置session //必须开启session session_sta ...
- 微信网页悬浮窗交互效果的web实现
一.微信的悬浮窗交互效果 微信更新后,发现多了个悬浮窗功能.公众号阅读,网页浏览回退后默认会出现.再点击,可以回到刚才阅读的地方.于是,再也不会遇到回复老婆的信息,再切换回来重新找刚才阅读东西的麻烦了 ...
- js 原生: 身份证脱敏、唯一随机字符串uuid、对于高 index 元素的隐藏与显示
1. 对于高 index 元素的隐藏 与 显示 export const hideIndexEle = (cssStr)=>{ const player = getElementsByCss(c ...
- [Codeforces Round #516][Codeforces 1063C/1064E. Dwarves, Hats and Extrasensory Abilities]
题目链接:1063C - Dwarves, Hats and Extrasensory Abilities/1064E - Dwarves, Hats and Extrasensory Abiliti ...
- javaWeb+servlet+mysql实现简单的企业员工管理系统
企业员工信息管理系统 一.源码描述 本程序为企业员工信息管理系统.是javaEE一个系统,主要实现登录功能和两个模块信息的增删改查.可以作为JAVAweb学习,也可在原有基础上进行深一步的 ...
- Hadoop集群搭建-HA高可用(手动切换模式)(四)
步骤和集群规划 1)保存完全分布式模式配置 2)在full配置的基础上修改为高可用HA 3)第一次启动HA 4)常规启动HA 5)运行wordcount 集群规划: centos虚拟机:node-00 ...
- React 之 JSX
开发完了一个项目了才回来研究React 一系列的技术,算是对自己的一个提高吧,也是小公司程序员的无奈. JSX是什么? JSX是javascript的语法的扩展. 为什么使用JSX? 1.React ...
- Java中不定项参数(可变参数)的使用
Java1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理. 注意事项: 1)不定项参数必须放在参数列表最后一个. 2)不定项参数只能有一个(多 ...
- [Swift]LeetCode940. 不同的子序列 II | Distinct Subsequences II
Given a string S, count the number of distinct, non-empty subsequences of S . Since the result may b ...
