JavaScript原型与继承(一)

我曾尝试理解关于prototype的相关概念,最初理解起来晦涩难懂,加上当时用的地方又少。后面渐渐明白,当你需要了解一个东西的时候,刻意的去理解是没有本质的作用的,但是能在你的脑海里留下一丝印象,当你真正遇到的时候,会想起曾经看到过,时机成熟的时候再去理解,会有不少收获,轮番看个几遍,拿上实例解析,会发现豁然开朗。

本文阐述的相关内容:

  • 创建对象的几种模式以及创建的过程
  • 原型链prototype的理解,以及prototype__proto__[[Prototype]])的关系
  • 继承的几种实现

1.常见模式与原型链的理解

a.构造函数创建

function Test() {
    // 
}

流程

  • 创建函数的时候会默认为Test创建一个prototype属性,Test.prototype包含一个指针指向的是Object.prototype
  • prototype默认会有一个constructor,且Test.prototype.constructor = Test
  • prototype里的其它方法都是从Object继承而来
示例
// 调用构造函数创建实例
var instance = new Test()

此处的instance包含了一个指针指向构造函数的原型,(此处的指针在chrome里叫__proto__,也等于[[Prototype]]

示例

b.原型模式
由上我们可以知道,默认创建的prototype属性只拥有constructor和继承至Object的属性,原型模式就是为prototype添加属性和方法

Test.prototype.getName = ()=> {
    alert('name')
}

此时的instance实例就拥有了getName方法,因为实例的指针是指向Test.prototype的

instance.__proto__ === Test.prototype

如下图所示
![897RVF]E5@IX$)`IVJ3BOSY.png](http://upload-images.jianshu.io/upload_images/3637499-2c25e10269d8bbbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这里我们可得知:实例instance与构造函数之间是通过原型prototype来相关联的。

c.组合模式
这种模式我们用的最多,其实也是原型模式的另一种写法,只不过有一点小区别而已

function Test() {}

Test.prototype = {
    getName() {
        alert('name')
    }
}

我们经常会这么直接重写prototype方法,由上我们可知,prototype会默认自带constructor属性指向构造函数本身,那么重写以后呢?

Test.prototype.constructor === Object 
// 而并不等于Test了
// 因为重写以后相当于利用字面量方式创建一个实例对象,这个实例的构造函数是指向Object本身的

当然我们也可以手动赋值constructor

Test.prototype = {
    constructor: Test,
    getName() {
        alert('name')
    }
}

那么又会有疑问了constructor要不要有何意义?我觉得constructor意义仅仅是为了来鉴别原型所属的构造函数吧。

当需要获取某个属性的时候,会先从实例中查找,没有就根据指针所指向的原型去查找,依次向上,直到实例的指针__proto__指向为null时停止查找,例如:

// 1 读取name
instance.name 

// 2 instance.__proto__ === Test.prototype
Test.prototype.name

// 3 Test.prototype.__proto__ === Object.prototype
Object.prototype.name

// 4
Object.prototype.__proto__ === null

当找到了这个属性就会直接返回,而不会继续查找,即使这个属性值为null,想要继续查找,我们可以通过delete操作符来实现。

由这里我们自然可以想到Array, Date, Function, String,都是一个构造函数,他们的原型的指针都是指向Object.prototype,它们就像我这里定义的Test一样,只不过是原生自带而已

d.几个有用的方法

  • Object.getPrototypeOf() 获取某个实例的指针所指向的原型
Object.getPrototypeOf(instance) === Test.prototype
  • hasOwnProperty 判断一个属性是存在于实例中还是存在于原型中,如图所示:
    NY~N}CNR`}8W%4QA$M8LFE4.png
  • in操作符,无论该属性是否可枚举
'name' in instance  // true
'getName' in instance // true

无论属性是在实例中,还是在原型中都返回true,所以当我们需要判断一个属性存在与实例中,还是原型中有2种办法

// 一种就是使用hasOwnProperty判断在实例中
// 另一种判断在原型中
instance.hasOwnProperty('getName') === false && 'getName' in instance === true
  • for ... in操作符也是一样的,但只会列出可枚举的属性,ie8版本的bug是无论该属性是否可枚举,都会列出

    D(%S__GN8404{H9X6PW$DVK.png

    name是在实例中定义的,getName是在原型中定义的

  • Object.keys()则不一样,它返回一个对象上所有可枚举的属性,仅仅是该实例中的

Object.keys(instance)
// ["name"]

e.总结
以上讨论了构造函数,原型和实例的关系:

  • 每个构造函数都有原型对象
  • 每个原型对象都有一个constructor指针指向构造函数
  • 每个实例都有一个__proto__指针指向原型

2.继承

继承的实质是利用构造函数的原型 = 某个构造函数的实例,以此来形成原型链。例如

// 定义父类
function Parent() {}
Parent.prototype.getName = ()=> {
    console.log('parent')
}
// 实例化父类
let parent = new Parent()

// 定义子类
function Child() {}
Child.prototype = parent 
// 实例化子类
let child = new Child()

child.getName() // parent
// 此时
child.constructor === parent.constructor === Parent

a.最经典的继承模式

function Parent(name) {
    this.name = name
    this.colors = ['red']
}
Parent.prototype.getName = function() {
    console.log(this.name)
}
// 实例化父类
let parent = new Parent()

function Child(age, name) {
    Parent.call(this, name)
    this.age = age
}
Child.prototype = parent 
// 实例化子类
let child = new Child(1, 'aaa')
child.getName() // parent

这里会让我想到ES6中的class继承

class Parent {
    constructor(name) {
        this.name = name
        this.colors = ['red']
    }
    getName() {
        console.log(this.name)
    }
}

class Child extends Parent {
    constructor(age, name) {
        super(name)
    }
}

let child = new Child(1, 'aaa')
child.getName() // parent

其实是一个道理,这里我们不难想到,将Child.prototype指向parent实例,就是利用原型实现的继承,而为了每个实例都拥有各自的colors和name,也就是基础属性,在Child的构造函数中call调用了Parent的构造函数,相当于每次实例化的时候都初始化一遍colors和name,而不是所有实例共享原型链中的colors和name


以上也是自己一边学习一边整理的,逻辑有点混乱,见谅,还望有误之处指出,不胜感激!

参考:
红宝书第六章
MDN 继承与原型链
理解JavaScript的原型链和继承

相关

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

推荐阅读更多精彩内容