javascript构造函数,原型和继承的理解

根据最近看过的书和博客,总结一下自己对于javascript构造函数,原型和继承的理解与认知
参考资料:javascript高级程序设计
目录结构如下:

  1. 认识构造函数
  2. 原型模式
  3. 组合使用构造函数与原型模式
  4. 简单的介绍了构造函数与原型,现在开始正式学习继承
    • 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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容