根据最近看过的书和博客,总结一下自己对于javascript构造函数,原型和继承的理解与认知
参考资料:javascript高级程序设计
目录结构如下:
- 认识构造函数
- 原型模式
- 组合使用构造函数与原型模式
- 简单的介绍了构造函数与原型,现在开始正式学习继承
- 4.1 原型链继承
- 4.2 借助构造函数继承
- 4.3 组合继承
- 4.4 原型式继承
- 4.5 寄生式继承
- 4.6 寄生组合式继承
1:认识构造函数
构造函数与普通的函数最大的区别就是调用方式不同,构造函数是通过new来调用的。new后面的函数,就是构造函数(通常函数名以大写字母开头)。那这样调用函数,跟直接调用函数有什么区别呢?我们来看以下例子
// 这个例子通过new,创建了2个对象
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName =
}
var person1 = new Person('jack','26','engineer')
var person2 = new Person('rose','26','lawyer')
person1.sayName() // jack
person2.sayName() // rose
那通过new来调用函数有什么作用呢?作用有以下几点
- 创建一个新对象
- this 指向新对象
- 执行构造函数中的代码,(为这个新对象添加新属性)
- 返回新对象(默认)
优点:创建自定义的构造函数意味着可以将实例标识为一种特定的类型,可以区别类型。
构造函数的问题
我们通过以下代码来分析下构造函数的问题
function Person (name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
console.log(this.name)
}
}
var jack = new Person('jack', '26', 'engineer')
var rose = new Person('rose', '26', 'lawyer')
jack.sayName === rose.sayName // 他们相等吗,结果是false
//每个方法都要在每个实例上重新创建一遍(但是每个方法不是同一个function实例),不同实例上的同名函数是不等价的。
//我们改写以下,这样会看得更明白
function Person (name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = new Function ('console.log(this.name)')
}
person1.sayName === person2.sayName //false
那函数有没有可以共用的形式呢
function Person (name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = sayName
}
function sayName () {
console.log(this.name)
}
var jack = new Person('jack', '26', 'engineer')
var rose = new Person('rose', '26', 'lawyer')
jack.sayName === rose.sayName // true
// 此时多个对象可以共享同一个方法,可以大大节省了内存的开销
那以上共享的方式有没有问题呢,当然还是有,此时实例就共享了全部作用域的同一个函数,全局函数没有封装性可言,若有多个函数,就需要创建多个全局函数。
2:原型模式
理解原型对象的要点就是,无论何时,只要创建一个函数,就会根据一组特定的规则,为该函数创建一个prototype属性,,这个属性指向函数的原型对象,在默认情况下,所有原型对象都自动获得一个construtor属性,这个属性是一个指向prototype属性所在函数的指针(指向构造函数)。当构造函数创建一个新实例后,该实例内部又有一个指针,指向构造函数的原型对象。—— javascrip 高级程序设计
对于原型链的理解就是,当在本对象上找不到某个属性的时候,它在当前对象的原型链上去查找,如果没有找到,会一直追溯到原型链的顶层就是Object.prototype,而这个对象是没有原型对象的
function Person(){}
Person.prototype.name = 'jack'
Person.prototype.age = 25
Person.prototype.job = 'engineer'
Person.prototype.sayName = function () {
console.log(this.name)
}
var person1 = new Person()
person1.sayName() // jack
var person2 = new Person()
person2.sayName() // jack
person1.sayName === person2.sayName //true
Person.prototype.isPrototype(person1) //true
Person.prototype.isPrototype(person2) //true
Object.getPrototypeOf(person1)==Person.prototype //true
不过原型模式有一个让人特别困惑的点,请看一下代码
<!--先创建一个实例,然后重新写了其原型。-->
function Person () {}
var friend = new Person()
Person.prototype = {
constructor: Person,
name: 'jack',
age: '29',
job: 'engieer',
sayName: function () {
console.log(this.naem)
}
}
friend.sayName() // error is not a function
// 为什么会这样呢,不是明明说本身对象上没有,可以到原型对象上查找吗
我对以上疑问的理解,如下
friend. __proto__
指向函数的原型对象(此时请不要理解为是Person.prototype)
Person.prototype在没有重新赋值前,与friend.__proto__
指向同一个对象。
从以上分析则有friend.__proto__ === Person.prototype
但是请记住Person.prototype只是函数原型对象的指针,当对其进行重新赋值后,就指向了新的原型对象,,此时friend.__proto__
仍然指向最初原型对象
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,实例引用的仍然是最初的原型。
3:组合使用构造函数与原型模式
构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性,这样每个实例都会有自己的一份实例属性的副本,但同时还共享方法的引用,同时还支持向构造函数传参数,但是对于引用类型的变量,还是只能共用。
function Person (name,age,job) {
this.name = name
this.age = age
this.job = job
this.friends = ['jack','rose','wille']
}
Person.prototype = {
constructor: Person,
sayName: function () {
console.log(this.name)
}
}
var willie = new Person('willie','18','clerk')
var bob = new Person('bob','20','athlete')
4:简单的介绍了构造函数与原型,现在开始正式学习继承
关于继承的概念,不在此赘述,不清楚的可以自行学习下
4.1 原型链继承
//子类
function SuperType () {
this.property = true
}
//父类原型上的方法
SuperType.prototype.getSuperValue = function () {
return this.property
}
// 父类
function SubType () {
this.subProperty = false
}
// 子类的原型作为父类的实例
SubType.prototype = new SuperType ()
SubType.prototype.getSubValue = function () {
return this.subProperty
}
var instance = new SubType ()
console.log(instance.getSuperValue()) // true
// 查找过程如下:
//instace->instance.__proto__(SubType.prototype)->instance.__proto__.__proto__(Super.prototype)
原型继承的主要问题有以下几点
- 原型实际上会变成另一个类型的实例,另一个类型的实例属性也会变成现有原型的属性了
- 在创建子类型时,不能向父类型的构造函数中传递参数
4.2 借助构造函数继承
核心思想: 子类型构造函数的内部调用父类型构造函数
function SuperType (name) {
this.name = name
this.colors = ['red', 'blue', 'green']
this.sayName = function () {
console.log(this.name)
}
}
function SubType (name) {
SuperType.call(this, name) // 继承了SuperType
}
var instance1 = new SubType ('jack')
instance1.colors.push('black')
var instance2 = new SubType ('rose')
instance2.colors // red,blue,green
instance1.name // jack
instance2.name // rose
// 解决了引用变量共享问题,但是没有解决函数复用问题
4.3 组合继承
组合继承就是利用构造函数继承和原型链继承的特点来实现的
function SuperType (name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
consle.log(this.name)
}
function SubType(name, age) {
SuperType.call(this,name) //调用父类的构造函数
this.age = age
}
SubType.prototype = new SuperType() //再次调用父类的构造函数
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
console.log(this.age)
}
var instance1 = new SubType('jack', 29)
instance1.colors.push('black')
var instance2 = new SubType('rose', 20)
console.log(instance2.colors)
<!--组合继承最大的缺点就是调用了2次父类的构造函数-->
4.4 原型式继承
道爷在2006年写过一篇文章,题为Prototype Inheritance in javaScript,在这边文章中,他介绍了一种实现继承的方法,他的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到目的,他给出了如下的函数:
function object (o) {
function F(){} // 临时的构造函数
F.prototype = o //传入的对象作为构造函数的原型
return new F() // 返回临时对象的新实例
}
来看下面这个例子
var person = {
name: 'jack',
friends: ['rose', 'jeny', 'rubin']
}
var wille = object(person)
wille.name = 'wille'
wille.friends.push('tom')
var bob = object(person)
bob.name = 'bob'
bob.friends.push('mike')
console.log(person.friends) //['rose', 'jeny', 'rubin','tom','mike']
<!--object.create() 方法规范了原型式继承,此方法接受2个参数,一个作为新对象的原型对象,一个为新对象定义额外属性的对象,在传入一个参数的情况下,Object.create()与object()方法行为相同。-->
var anotherPerson = object.create(person, {
name: {
value: 'wangdonghai'
}
})
console.log(anotherPerson.name) //wangdonghai
4.5寄生式继承
寄生式继承式与原型式继承紧密相关的一种思路,即创建一个仅用于封装继承过程的函数,该函数内部以某种方式增强。
function createAnother(original) {
var clone = object(original)
clone.sayHi = function () {
console.log('hello')
}
return clone
}
var person = {
name: 'jack',
friends: ['rose','mike','bob']
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() //hello
// 寄生式继承的缺点仍是不能做到函数的复用。
4.6寄生组合式继承(推荐使用的继承方法)
核心思想: 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
不必为了指定子类的原型而调用父类型的构造函数,我们需要的无非就是父类型的一个副本而已。本质上就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype (subType, superType) {
var prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name) //继承父类的属性
this.age = age //添加自己的属性
}
// 子类继承父类原型上的方法
inheritPrototype(SubType, SuperType)
// 子类自己的方法
SubType.prototype.sayAge = function () {
console.log(this.age)
}
var instance = new SubType('jack', 26)
instance.sayName() // jack