JS中的构造函数,原型,原型链,继承

如果你不是张无忌,有九阳神功护体,那么乾坤大挪移的确不是一炷香的时间就能练到九重的。

记得几个月以前还是看不懂高程上的原型一章的,不是没仔细看,相反,我反反复复看了好几遍,每看一次都感觉自信心受到了打击。。。。今天莫名打通任督二脉= =

写在前头

首先思考一个问题,我们知道通过构造函数和new操作符可以创建一个新的对象,并且同一个构造函数可以创建出相同属性和方法的对象,这正是prototype所做的(使用原型对象的好处是可以让所有实例共享它所包含的属性和方法)。并且,在其他构造函数中通过call和apply,调用其他构造函数不就可以实现继承了吗,那我们还要prototype做什么?


原因是,其实new操作符生成的对象并不能共享属性和方法,每次new一个新的对象时,都要为这个对象开辟一个新的空间来存放它的属性和方法,而且,在构造函数中,每次想要修改某个属性和方法时,就要重新生成所有的实例,对资源造成了极大的浪费!!!
ok,因为不想浪费,所以作者引入了原型

注:部分例子摘自JS高程

理解原型对象

遵循以下几个规则
1, 我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个{...}(对象,也就是原型对象)包含了所有实例共享的属性和方法。
嗯,函数拥有prototype指针,指向原型对象
2, 原型对象除了1所说包含所有实例的共享的属性和方法,还有一个constructor指针指向构造函数,对的,构造函数和原型对象互指,通过prototype和constructor
3, 实例:当构造函数为空,仅在构造函数的原型对象上有属性和函数时,此构造函数所创建的实例本质上只有一个名为proto的指针,所有实例的这个指针统统指向原型对象,因此他们才有了共享的属性和方法,而且没有独自开辟自己的空间,节省了资源
看个例子,理解这三条

function animal(){
}
animal.prototype.name = "Tom"
animal.prototype.say = function(){
      alert('喵喵喵?')
}
var cat = new animal()
console.log(cat.name) //"Tom"
console.log(cat.say) //"喵喵喵?"

ok,解释一下。上面代码中
1, 构造函数为aniaml,当我们创建了这个构造函数时,他已经包含了一个原型属性
prototype
2, 该构造函数的原型对象
console.log(animal.prototype) //返回一个对象,即原型对象,该对象包含一个name属性,一个say函数
3, 该实例
cat.__prpto__ == animal.prototype //true
都指向原型对象


在这个例子中,构造函数自身为空,在构造函数的原型对象上创建了一个属性和方法,他们被所有实例所共享,因为实例中有proto指针指向了该原型对象。但是如果使用这种方法,我们在构造函数内写的属性和方法不是都可以转到prototype上吗,那还要构造函数干什么?
因为,此处是指针式的获取到了原型上的数据,当这个数据为引用型数据(也是指针式)(如数组时),我们在实例上改变这个数组,会影响原型对象上的这个数组属性,从而影响所有实例。因此,构造函数也有它的必要用途,函数原型上一般只定义函数和非引用型数据。

组合使用构造函数和原型模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby","Court"];
}

Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}

var person1 = new Person("Nicholas",29,"Software");
var person2 = new Person("Greg",27,"Doctor");

perons1.frineds.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //“Shelby,Count”

在这个例子中,构造函数中定义实例属性,原型对象上定义方法和共享属性。
每次调用构造函数,给新的对象开辟一个新的空间,此时的实例上的friends属性是全新的,非proto指向的,拥有独立空间的,所以每个空间的friends数组是单独互不影响的,因此在person1对象中添加一个friends后,person2的friends属性并未改变,而person1和person2都可以共享sayName函数,还有name,age,job等属性。

这种构造函数与原型模式混成的模式,是目前在ECMAScript中使用最广泛的、认同度最高的一种创建自定义类型的方法。这是用来定义引用类型的一种默认模式。

原型链继承

在java等oop语言中继承由class和extend关键字来实现,而在ES5中没有这些关键字( ES6有 ),因此只能通过使子函数的原型对象等于父函数的实例来实现链式继承。看例子

function SuperType(){
    this.color = ['red', 'blue' , 'yellow'];
}

function SubType(){}

SubType.prototype = new SuperType(); //子函数的原型对象等于父函数的实例

var instance1 = new SubType();
instance1.color.push ('white');
alert(instance1.color); // red,blue,yellow,white

var instance2 = new SubType();
alert(instance2.color); // red,blue,yellow,white

子函数的原型对象等于父函数的实例,此时子函数SubType的原型被改写,成为了SuperType的实例,而上面讲了实例拥有一个proto属性(指针)指向原型对象,因此这个SubType的原型对象上的proto属性指向了SuperType的原型对象。然后当我们创建了SubType的实例instance时,instance中也有一个proto指针,而它指向SubType.prototype,SubType.prototype上的proto指针,指向SuperType.prototype,其实再往上,SuperType.prototype也是对象的一种,因此它也是Object的一个实例,SuperType.prototype的proto指针指向Object.prototype。(顶层)(原型链的核心就是proto指针)

这种实例与原型的链条,就叫做原型链

组合继承

上述的SubType.prototype = new SuperType(); //子函数的原型对象等于父函数的实例的确实现了继承,但是当SuperType构造函数中有引用型数据时,即此时SubType.prototype上也有了引用类型,现在又出现了上面提过的问题,在sub的实例中改变引用类型的值会反映到所有实例中。
第二点,在创建子类型的实例时无法给超类型的构造函数传递参数。

单独使用借用构造函数

解决上述问题有一种借用构造函数的方法,在子构造函数中使用call或apply,如SuperType.call(this,arguments)改变sub构造函数中this的指向,而且可以通过这种方法向超类型传递参数,但是单单使用借用构造函数,并不能达到函数复用的效果,依然要为每个实例上的函数开辟空间(因为此时函数没有定义在原型上),为了融合借用构造函数和原型链继承的优点,出现了组合继承

组合

function SuperType(name){
    this.name = name;
    this.color = ['red', 'blue' , 'yellow'];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
}

function SubType(name, age){
    //继承属性 超类
    //获得name和color属性
    SuperType.call(this, name);
    this.age= age;
} 

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    alert(this.age)
}

var instance1 = new SubType("Nicholas", 29);
instance1.color.push("black");
console.log(instance1.color); //["red", "blue", "yellow", "black"]
instance1.sayName();
instance1.sayAge();

var instance2 = new SubType("Greg", 27);
console.log(instance2.color); //["red", "blue", "yellow"]
instance2.sayName();
instance2.sayAge();

此处截取了一张图片进行分析


image.png

在这个例子中,超类型构造函数定义了name和colors两个属性,超类型原型对象上定义了一个sayName函数可复用,子类型中使用借用构造函数的方法,使所有的子类实例都继承了父类的colors和name属性,还设立了一个自己的age属性,之后使子类型的原型对象等于超类型的一个实例,继承链生效,又在子类型的原型对象上增加了一个sayAge函数(此处顺序不能反,否则子类原型对象被覆盖,新函数无效),再之后新建了两个子类型的实例,在instance1改变colors数组并未影响instance2,且子类型,超类型函数皆可调用。

此时的逻辑关系从所截的图上来看,实例之所以引用类型互不影响,因为此时实例中的colors属性是同过借用构造函数,新建实例时,向超类型传递参数,并获得了name和colors属性,每个新的实例都是新的空间,且在原型链的最下面(原型链向上查找是否拥有某属性),所以对数组的操作并不互相影响。
这里子类型的实例其实若本身没有colors属性,指向原型的color属性还是会互相影响的,所以是借用构造函数解决了这个问题,让子类型实例先获得了这个属性,当在实例上访问到时,便不会再往下寻找
子类型的实例上的proto依然指向子类型的原型对象,即SubType.prototype,其上还有一个定义的sayAge函数。
此原型对象因为等于超类型的实例,所以也有一个proto指向超类型的原型对象,即SuperType.prototype。
其上还有一个sayName函数,SuperType.prototype上还有一个proto,再往下就是原生的Object.prototype了。


继承方式多种多样,但组合继承无疑是js中最常用的继承模式,读懂原理,其他模式也就不难理解了,千里之行,始于足下。

陈小源2017/9/4

现在ES6的继承通过class...extends...来实现,子类型没有自己的this对象,子类型的构造函数中要显示地调用super()方法,使用父类的构造函数,并获取this对象,再对this对象进行改造,从而实现继承。

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

推荐阅读更多精彩内容