JavaScript原型继承
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype
属性上,而非对象实例本身。
在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是proto属性,是从构造函数的prototype
属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。–MDN
实例
说一下自己的理解,我们在JavaScript创建的函数都有共同的属性叫prototype,它长这样
1 | function test (){} |
向原型中添加属性
1 | test.prototype.foo = 'bar'; |
理解原型对象
原型链中的方法和属性没有被复制到其他对象
官方并没有规定方法可以直接访问对象原型,原型链的’连接’被定义在内部属性中,在JavaScript中用
[[prototype]]
表示,在使用chrome和firefox时,提供了__proto__
这个属性可以访问原型对象
需要注意的是__proto__
和prototype
的区别,前者可以访问对象的原型对象,后者包含了一个对象,可以在里面定义被继承的成员,prototype
是构造器的专属属性
而前面说的Object.create()
,就是将参数里的对象作为原型对象返回一个新的对象
constructor
每个实例的对象还会从原型中继承constructor
属性,该属性就是这个对象的构造函数
1 | let o1 = new test(); |
用途1,利用这个构造函数创建对象
1 | let o2 = new o1.constructor(); |
用途2,得到构造函数的各种信息
1 | //名字 |
修改原型
因为原型链的特性,所以增加构造函数的原型属性时,使用构造函数实例化的对象也可以访问到这个属性,如
1 | Person.prototype.newAtrribute = function(){ |
注意上面的this是在函数里面的,在使用时就会转换为对象实例
常见的对象定义模式就是,构造器(函数体)里定义属性,prototype属性里定义方法
继承
原型继承
Java的继承是规定某个类继承自某个类,而JavaScript的继承方式有点特别,被称为 原型式继承 —— prototypal inheritance
先创建一个Person类
1 | function Person(first, last, age, gender) { |
此时的原型链是
1 | new Person() ----> Person.prototype ----> Object.prototype ----> null |
然后需要创建一个Teacher类,它需要继承Person类,并添加一个新的属性subject
,重写一个方法greeting
,于是有
1 | function Teacher(first, last, age, gender, subject) { |
call()
表示为Teacher调用了Person构造方法,然后第一个参数绑定this变量
如果继承的是无参函数,那么只需要传递一个this即可
但是它的原型链并没有指向Person,而是
1 | new Teacher() ----> Teacher.prototype ----> Object.prototype ----> null |
我们需要的是
1 | new Teacher() ----> Teacher.prototype ----> Person.prototype ----> Object.prototype ----> null |
原型与构造器引用
上面Teacher类还不完整,有两个问题
1.Teacher的原型没有继承自Person
解决方法,使用create(),得到一个原型是Person的中间对象,并将Teacher的原型指向这个对象(即Teacher的原型对象指向了Person的原型对象)
1 | Teacher.prototype = Object.create(Person.prototype); |
2.因为使用了create(),所以Teacher的prototype的constructor指向Person,它应该指向Teacher本身,所以
1 | Teacher.prototype.constructor = Teacher; |
注:每一个函数对象(
Function
)都有一个prototype
属性,并且只有函数对象有prototype
属性,因为prototype
本身就是定义在Function
对象下的属性。当我们输入类似var person1=new Person(...)
来构造对象时,JavaScript实际上参考的是Person.prototype
指向的对象来生成person1
。另一方面,Person()
函数是Person.prototype
的构造函数,也就是说Person===Person.prototype.constructor
(不信的话可以试试)。–MDN
最后,重写greeting函数
1 | Teacher.prototype.greeting = function() { |
注: 考虑到JavaScript的工作方式,由于原型链等特性的存在,在不同对象之间功能的共享通常被叫做 委托 - 特殊的对象将功能委托给通用的对象类型完成。这也许比将其称之为继承更为贴切,因为“被继承”了的功能并没有被拷贝到正在“进行继承”的对象中,相反它仍存在于通用的对象中。–MDN
class继承
ES6引入了calss,简化了原型继承的代码量(继承原理不变)
1 | class Person { |
注意兼容性再考虑是否使用class