在基础面前,一切技巧都是浮云。javascript
要求写出控制台的输出.html
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();
prototype
属性,指向构造函数的原型对象,实例会共享同一个原型对象;__proto__
指向构造函数的原型对象;this
的指向问题,常见的状况包含以下几种:
window
new
操做符生成实例时,构造函数中的this指向实例call
和apply
方法中,显示指定this
的绑定为指定上下文Object.prototype
和null
,若是实例自身及整个原型链都不存在所查找的属性则返回undefined
基本没什么可解释的。
直接取值就能得出答案1 [1,2,1] 5
;前端
Child
的构造函数本来是指向Child
的
题目中显式将Child
类的原型对象指向了Parent
类的一个实例,这是javascript面向对象编程中常见的继承方式之一。此处须要注意Child.prototype
指向的是Parent
的实例parent
,而不是指向Parent
这个类
直接在控制台操做输出答案可得11 [1,2,1] 5
java
此处使人迷惑的是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。app
须要注意的是:框架
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属性的值发生变化时,同步修改b[2]
的值,代码和运行结果以下所示:若是理解了上面的解释,那么此处同理便可得出答案:12 [1,2,1] 5
接着代码执行了: child1.change(); child2.change();
parent
是一个Parent
类的实例,Child.prorotype
指向的是Parent
类的另外一个实例,二者在堆内存中是两份资源,互不影响,因此上述操做不影响parent
实例,
输出结果保持不变:1 [1,2,1] 5
;
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多期的音频通篇讲解了红宝书的内容,对不喜欢看书的童鞋绝对是一大福音。