开头说点题外话,不知道何时开始,我发如今 JavaScript 中,你们都喜欢用 foo 和 bar 来用做示例变量名,为此专门查了一下这家伙的 来源:javascript
“The etymology of foo is obscure. Its use in connection with bar is generally traced to the World War II military slang FUBAR, later bowdlerised to foobar. ... The use of foo in a programming context is generally credited to the Tech Model Railroad Club (TMRC) of MIT from circa 1960.”html
foo的词源是模糊的。 它与bar的关系能够追溯到第二次世界大战的军事俚语 FUBAR
,后简化为foobar。 而在编程环境中使用 foo
一般认为起源于约 1960 年时麻省理工学院的技术模型铁路俱乐部(TMRC)。前端
okay ,那么今天,咱们也看看这段 Foo
的代码来聊聊原型。java
function Foo(name){
this.name = name
}
let foo = new Foo('demoFoo')
console.log(foo.name) // demoFoo
复制代码
你们都知道 Java
做为面向对象的语言的三个要素:封装
,继承
,多态
,我以前也写过 Java
,因此在一开始学习 JavaScript
的时候,我老是会去经过类比熟悉的 Java
来理解 JavaScript
中关于继承的概念,可是不管怎么去类比都以为不是那么回事,由于这自己就是两种彻底不一样的方式。编程
JavaScript
是如何设计出来的呢,wiki 是这样说的函数
“网景决定发明一种与 Java 搭配使用的辅助脚本语言,而且语法上有些相似”学习
无论怎么说,JavaScript
在设计之初都受到了 Java
的影响,因此在 Javascript
中也有了对象的概念,可是做为一种 辅助脚本语言 ,类的概念有些过于笨重,不够简单。可是对象之间须要一种让彼此都产生联系的机制,怎么办呢?ui
依旧是参考了 Java
的设计,Java
中生成一个对象的语法是这样:this
Foo foo = new Foo() // 请注意这里的Foo 指的是类名,而不是构造函数。
复制代码
因而 Brendan Eich 也模仿了这样的方式使用了 new
来生成对象,可是 JavaScript
中的 new
后面跟的不是 Class
而是 Constructor
。spa
okay 解决了实例化的问题,可是仅仅只靠一个构造函数,当前对象没法与其余的对象产生联系,例若有的时候咱们指望共享一些属性:
function People(age) {
this.age = age
this.nation = 'China'
}
// 父子大小明
let juniorMing = new People(12)
let seniorMing = new People(38)
// 有天他们一块儿移民了,此时我想改变他们的国籍为 America
juniorMing.nation = 'America'
// 可是改变小明一人的国籍并不能影响大明的国籍
console.log(seniorMing.nation) // China
复制代码
(nation)国籍 在这个例子中成为了咱们想在两个对象之间共享的属性,可是因为没有类的概念,必须得有一个新的机制来处理这部分 须要被共享的属性。这就是 prototype 的由来。
因此咱们上面的例子变成了什么呢?
function People(age) {
this.age = age
}
People.prototype.nation = 'China'
// 父子大小明
let juniorMing = new People(12)
let seniorMing = new People(38)
// 有天他们一块儿移民了,此时我想改变他们的国籍为 America
People.prototype.nation = 'America'
console.log(seniorMing.nation) // America
console.log(juniorMing.nation) // America
复制代码
结合前言部分中的代码:
function Foo(name){
this.name = name
}
let foo = new Foo('demoFoo')
console.log(foo.name) // demoFoo
复制代码
先提取一下关键信息:
foo
是被构造出来的实例对象。foo
的构造方法是 Foo()
因此最基础的原型链就是这样:
在这个例子中
Constructor.prototype
等价于Foo.prototype
构造函数 Foo
能够经过 Foo.prototype
来访问原型,同时被构造出来的对象 foo
也能够经过 foo.__proto__
来访问原型:
Foo.prototype === foo.__proto__ // true
foo.__proto__.constructor === Foo // true
复制代码
简单的来讲,Foo
函数,参照了 Foo.prototype
生产出来了一个 foo
对象。
为了更好的理解这一过程,我得从一个故事开始提及:
Foo.prototype
)Foo
)foo
)。刚刚的故事尚未结束,后来一天这个工匠开始思考,以前看到的那个雕塑是哪里来的呢?又是怎么作出来的呢?就算是自然造成的,那又是什么条件造成了这样的雕塑呢?
带着这些问题,他开启了 996 模式,寻师访友,查阅典籍,经历了多年苦心研究,终于有了新的发现:
Foo.prototype
/ foo.__proto__
) 是 n 年前一位雕刻大师参照天然的现象( Object.prototype
)而后设计了铸造方式( Object()
)造出来的。从代码来看:
foo.__proto__.__proto__ === Object.prototype //true
Foo.prototype.__proto === Object.prototype //true
Object === Object.prototype.constructor
复制代码
用图来描述这一过程就是:
Object()
),是根据天然现象的造成规律( Function.prototype
)来设计的。因此,本质上来讲,他所设计出的生产线( Foo()
),也间接的参考了天然现象的造成规律( Function.prototype
)console.log(Object.__proto__=== Function.__proto__)
console.log(Foo.__proto__ === Function.prototype)
复制代码
而后咱们来看看图会更加清晰一些:
Function.prototype
)是先辈们从这天然现象( Object.prototype
)中总结出来的。因此咱们从代码中看到:
Function.prototype.__proto__ === Object.prototype
复制代码
因此 人法地、地法天、天法道、道法天然,如今咱们能够看到完整的原型链:
因此代码被改写为:
let foo = new Object()
console.log(foo.__proto__ === Object.prototype) // true
复制代码
而这一故事也一直流传到今天:
看到这里,相信你对 JavaScript 中的原型和原型链,都有了新的认识,那么咱们再来聊聊原型继承,在聊原型继承以前,咱们想一想什么叫作继承呢?
抛开计算机中的理论,咱们就说一个最简单的例子,小王经过继承了他老爸的遗产走上了人生巅峰。这里面其实有一个关键信息:他老爸的遗产帮助他更快的走上了人生巅峰,换言之,小王不须要本身努力也能经过他老爸留下的财产走上人生巅峰。
听起来很像是废话,可是本质也在这里:
child
parents
而后让 child
直接从 parents
那里把全部东西都继承过来。parents
的基础上,我可能为 child
定制了一些内容,因此之后有可能直接从 child
来继承这一切。听起来继承就像是 让一个普通的对象快速的得到本来不属于它的超能力。
蝙蝠侠的故事告诉咱们:rich 也是一种超能力。
okay 让咱们看看怎么样让 JavaScript 中的对象快速得到超能力呢?结合咱们上面了解的内容,咱们会发现几个很关键的点,若是要让一个对象得到超能力,只有下面的三种途径:
constructor
生产的,因此咱们能够经过改变 constructor
来实现。constructor
有一个关键的属性: constructor.prototype
因此改变原型也能达到目的。不管你怎么去找继承的方法,继承的本质就在这里,领悟本质会让问题变得简单,因此咱们一块儿来看看具体的实现。
咱们从最简单的开始,这种方式的核心就在于直接在当前构造函数中,调用你想继承的构造函数。
function Student(){
this.title = 'Student'
}
function Girl(){
Student.call(this)
this.sex = 'female'
}
let femaleStudent = new Girl()
console.log(femaleStudent)
复制代码
这种方式并无影响到你的原型链,由于本质上来讲,你仍是经过 Girl()
来生成了一个对象,而且 Girl.prototype
也并未受到影响,因此原型链不会产生变化。
改变 constructor.prototype 这一方式有不一样的状况,咱们能够分开来看
这种方式的核心已经写在了标题上,因此咱们来看看代码吧:
function Parent() {
this.title = "parent"
}
function Child() {
this.age = 13
}
const parent = new Parent()
Child.prototype = parent
const child = new Child()
console.log(child);
console.log('child.__proto__.constructor: ', child.__proto__.constructor);
console.log('parent.constructor: ', parent.constructor);//每个实例也有一个constructor属性,默认调用prototype对象的constructor属性
复制代码
打印出来是什么呢?
把他和本来没有继承以前的 child
对比一下:
结论是:
child.prototype
被替换为 parent
对象后与构造器之间的联系成为了单向:console.log(parent.constructor.prototype === parent) //false
复制代码
咱们用图来描述一下:
为了解决上面存在的问题,咱们改写了代码,添加了一行
...
const parent = new Parent()
Child.prototype = parent
Child.prototype.constructor = Child; //添加了这行
const child = new Child()
...
复制代码
因此原型链成为下面这样:
有的同窗可能会不理解为何 Child.prototype === parent
以及 parent.constructor === Child()
Child.prototype === parent
是由于咱们在代码中中强行设置了,parent.constructor === Child()
是由于 parent
对象自己也有一个 constructor
属性,这个属性默认返回 parent.__proto__.constructor
因此以前是 Parent()
可是如今也被代码强制设置为了 Child()
Child.prototype = parent
Child.prototype.constructor = Child; // parent.constructor === Child
复制代码
咱们把前面两种方式组合起来:
function Parent() {
this.title = "parent"
}
function Child() {
Parent.call(this)
this.age = 13
}
const parent = new Parent()
Child.prototype = parent
Child.prototype.constructor = Child;
const child = new Child()
console.log(child);
复制代码
打印的结果是:
这样咱们生成的 child
对象自己包含了他从 Parent
中继承来的 title
属性,可是同时 Child.prototype
同时也包含了全部 Parent
上的全部属性,形成内存的浪费:
因此咱们把方法 ② 改变一下,避免内存的浪费,既然缘由是由于咱们将 Child.prototype
设置为 new Parent()
的过程当中,使用 Parent()
进行实例化因此将属性都继承到了 Child
原型上,那么为何不能够直接使用原型对原型进行赋值呢?
也就是 chidConstructor.prototype = Parents.prototype
:
function Parent() {
this.title = "parent"
}
function Child() {
Parent.call(this)
this.age = 13
}
Child.prototype = Parent.prototype
Child.prototype.constructor = Child;
const child = new Child()
console.log(child);
复制代码
咱们看下打印信息:
okay,关键的信息看起来都很完美,再分析下原型链:
嗯,单纯站在 child 的角度来看好像没有什么问题,可是若是咱们打印下面这几行会发现问题:
console.log('parent: ', new Parent());
console.log(Child.prototype === Parent.prototype) //true
复制代码
Parent.prototype
的构造器变成了 Child
是不合理的,并且此时 Child.prototype === Parent.prototype
两个属性指向同一个对象,当咱们改变 Child.prototype
的时候,咱们并不但愿影响到 Parent.prototype
可是在这里成为了避免可避免的问题。
那有什么办法能够解决这个问题呢?
若是咱们并不直接将 Parent.prototype
赋值给 Child.prototype
而是复制一个如出一辙的新对象出来替代呢?
function Parent() {
this.title = "parent"
}
function Child() {
Parent.call(this)
this.age = 13
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child;//咱们在这里修改了构造器的指向,你一样能够在Object.create 方法中作这件事。
const child = new Child()
console.log('parent: ', new Parent());
console.log(child);
复制代码
最后打印出来的结果如何呢?
没有任何问题,避免了父子原型的直接赋值致使的各类问题~
除了上面的解决方案,还有没有别的办法呢?答案是有的,除了经过复制创造一个新的原型对象,咱们还能够用一个中间函数来实现这件事:
...
function extend(Child, Parent) {
var X = function () { };
X.prototype = Parent.prototype;
Child.prototype = new X();
Child.prototype.constructor = Child;
}
...
复制代码
一样很完美。这也是 YUI 库实现继承的方式。
咱们再也不基于原型去玩什么花样,而是直接把整个父对象的属性都拷贝给子对象。若是仅仅是值类型的话,是没有问题的,但若是这时候,父对象的属性中本来包含的是引用类型的值呢?
咱们就要考虑把整个引用类型的属性拷贝一份到子对象,这里就设计到浅拷贝和深拷贝的内容啦~
若是你仔细的读完本文,相信你对 JavaScript 中的原型,原型链,原型继承,会有新的认识。
若是以为对你有帮助,记得 点赞 哦,
毕竟做者也是要恰饭的。
很是感谢 阮一峰 老师关于继承整理。
本来想写写静态分析,可是写起来发现本身还有不少的不足,因此这里也不说下次会写啥了,由于太渣也不必定写得出来...
这里是 Dendoink ,奇舞周刊原创做者,掘金 [联合编辑 / 小册做者] 。
对于技术人而言:技 是单兵做战能力,术 则是运用能力的方法。驾轻就熟,出神入化就是 艺 。在前端娱乐圈,我想成为一名出色的人民艺术家。
扫码关注公众号 前端恶霸 我在这里等你: