前端笔记四(js继承的方式:精选篇)

前言

此篇文章的目的是让你搞懂这些继承到底是为什么?他们的优缺点的原理到底是什么?看了很多文章说的太抽象,怎么可能记得住,要想熟记于心,请花费较长的时间,静下心理解,大家最好复制代码,看下实现,才容易理解原理。

1.原型链继承

function Parent () {
 this.name = '欣雨';
}

Parent.prototype.getName = function () {
 console.log(this.name);
}

function Child () {}
Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName()) // 欣雨

但是他有两个问题:

  • 1.引用类型属性被所有实例共享:
    到底啥意思呢,先看下代码,然后来解释。
function Parent () {
  this.names = ['小米', '小明'];
}

function Child () {}
Child.prototype = new Parent();
 
var child1 = new Child();
 
child1.names.push('小哥');
console.log(child1) // {}
console.log(child1.names); // ["小米", "小明", "小哥"]
 
var child2 = new Child();
console.log(child2) // {}
console.log(child2.names); //["小米", "小明", "小哥"]

为什么会这样?(“看文字前,先自己写下代码,看下打印的内容!”)

1)首先我们要明白对象这个属性有个特性,复制了对象,只是栈内存有两个值,而指针指向同一个堆内存,不懂可以看js堆栈理解、实现浅拷贝与深拷贝

\color{red}{一句话}就是对象是能被修改的,不然还要什么浅拷贝,深拷贝。

2)Child.prototype = new Parent()就是Child.prototype有了this.names这个属性,而不是Child本身有这个属性,我们打印了child1是一个空对象{},它的__proto__才有this.names里面的属性。打印了child2,同理。说明了这个this.names不是new Child()\color{red}{独有的属性},而是prototype\color{red}{共有的属性},所以一个实例属性更改,另一个实例属性也更改。

总结\color{red}{一句话}就是只有函数独有的属性不会被更改,共享的属性会被更改

  • 2.在创建 Child 的实例时,不能向Parent动态传参:
function Parent(name) {
    this.name = name;
}

function Child(age) {
    this.age = age; 
}

Child.prototype = new Parent("小鱼");

var p1 = new Child(20);
console.log(p1.name); // 小鱼
console.log(p1.age); // 20

var p2 = new Child(30);
console.log(p2.name); // 小鱼
console.log(p2.age); // 30

这个很好理解,我们看到Child.prototype = new Parent("小鱼"),我们传参name为小鱼,你会发现之后无论我构建多少实例,名字就固定了,而且你没有办法动态传参了。

至此,原型链继承继承我们讲完了。


2. 构造函数继承

function Parent (name) {
  this.names =  ["小米", "小明",];
  //this.getName = function () {
  //  console.log(this.name)
  //}
}
 
function Child (name) {
  Parent.call(this, name); // 重点
  // Parent.apply(this, arguments); // 第二种方法,更通用
}
 
var child1 = new Child();
 console.log(child1) // {names: ["小米", "小明", "小哥"]}

child1.names.push('小哥');
console.log(child1.names); //  ["小米", "小明", "小哥"]
 
var child2 = new Child();
console.log(child2.names); //  ["小米", "小明"]
  • 我们看到它解决了原型链继承的通病:
    1.避免了引用类型的属性被所有实例共享。
    2.可以在 Child 中向 Parent 传参。

为什么可以这样呢?

重点在于Parent.call(this, name)这句话。call()或apply(),实际上是在新创建的Child实例的环境下调用了Parent构造函数,相当于Child拷贝了一份Parent里面的属性和方法,变成自己独有的属性和方法了。此时我们打印下child1,有了name的属性,神奇吧。有心的同学会发现,有三个数组,Parents里面不是两个吗,因为你下面push了一个数组,数组和对象一样都会因更改导致原数组也变化。

总结\color{red}{一句话}就是call()或apply()会把父类方法变成子类方法独有的属性。

  • 但是它也有缺点:就是每次创建实例都会创建一遍方法。
    我们把注释的this.getName方法打开,你会发现,我每次new Child()都会创建一次这个方法,但是这个方法是做同一件事情,就是拿到名字,岂不是很浪费内存。(大家想想,如果方法用prototype实现,不就不需要每次new的时候都创建了嘛,所以引出组合继承。)

3. 组合继承(原型链继承+构造函数继承)

function Parent (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
 
Parent.prototype.getName = function () {
  console.log(this.name)
}
 
function Child (name, age) {
  Parent.call(this, name); 
  this.age = age;
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child('小鱼', '18');
console.log(child1) // 实例本身和__proto__有相同的属性
child1.colors.push('black');
console.log(child1.name); // 小鱼
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
 
var child2 = new Child('小米', '20');
console.log(child2.name); // 小米
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

咋一看,厉害呀,之前的问题都解决了。我们来看看是怎么解决的。

Parent.prototype.getName = function () {
  console.log(this.name)
}

这段代码解决了,每一次new实例重复创建问题。原理就是不在函数内,创建跟我没关呀。

Child.prototype = new Parent();

我们来看这句话,之前我们是直接var child1 = new Child();,原来构造函数方式是不能继承原型属性/方法 (原型中定义的方法和属性对于子类是不可见的)。原来还有特殊规定呀。\color{red}{原型链继承的方式就可以继承原型的属性/方法}

  • 我们打印一下child1发现,创建的实例和原型上(__proto__)存在两份相同的属性。造成了资源浪费和占用。所以我们引出寄生组合继承。原因是调用两次父构造函数:
Child.prototype = new Parent();
var child1 = new Child('小鱼', '18');  调用此实例的时候调用了如下方法
Parent.call(this, name);

ps: 在学习寄生组合继承之前我们储备两个知识:Object.create和寄生式继承

4. Object.create(原型式继承)

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

\color{red}{一句话}:创建的对象的原型=传入的对象,就是Object.create原理。
来个例子:

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

var person = {
  name: '小黑',
  friends: ['A', 'B']
}
 
var person1 = createObj(person);
var person2 = createObj(person);
// var person2 = Object.create(person);
console.log(person1)
person1.name = '小白';
console.log(person1.name); // 小白
console.log(person2.name); // 小黑
 
person1.friends.push('C');
console.log(person1.friends); // ["A", "B", "C"]
console.log(person2.friends); // ["A", "B", "C"]

缺点很明显,跟原型链有着同样问题:引用类型的属性值始终都会共享相应的值
有人说啦,person1.name不是变了,person2.name没变呀,你打印下person1,看下当前实例的name是’小白‘,而它__proto__上面是'小黑',当前的属性会覆盖原型上的属性

总结\color{red}{一句话}就是Object.create会将当前属性和原型属性分开。(person1.name = '小白'是添加到它自身的实例上了,而不是修改了原型的属性)


5. 寄生式继承

function person (o) {
  var clone =   Object.create(o);
  clone.sayName = function () {
    console.log('hello world');
  }
  return clone;
}

var obj = {
  name: '小黑',
  friends: ['A', 'B']
}

var p1 = person(obj)
console.log(p1)

缺点很明显:跟构造函数模式一样,每次创建对象都会创建一遍方法。


6. 寄生组合式继承

function Parent (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
 
Parent.prototype.getName = function () {
  console.log(this.name)
}
 
function Child (name, age) {
  Parent.call(this, name);
  this.age = age;
}
 // 组合继承方法
// Child.prototype = new Parent();
 // 寄生组合继承方法
  var F = function () {};
  F.prototype = Parent.prototype;
  Child.prototype = new F();

  var child1 = new Child('小鱼', '18');
  console.log(child1)

注释掉的就是组合继承方法。看看你打印的child1,是不是__proto__没有重复属性了。看着很高大上,其实就是F.prototype = Parent.prototype仅仅继承了Parent.prototype.getName方法而已。Parent.call(this, name);拿到构造函数属性。
\color{red}{一句话}就是child1的属性方法 = Parent.call(this, name) + F.prototype上面的属性方法。自信领悟一下,其实也不过如此。

它只调用了一次Parent构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceofisPrototypeOf

封装版:

function Parent(name){
    this.name = name;
    this.colors = ["red", "blue", "yellow"];
}

Parent.prototype.getName = function () {
  console.log(this.name)
}

function Child(name){
    Parent.call(this, name);
}

function objectCreate (o) {
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(Child,Parent){ 
  var p=objectCreate(Parent.prototype);
  p.constructor=Child;
  Child.prototype=p;
}

inheritPrototype(Child, Parent);
var child1 = new Child('小鱼');

封装方法与上面唯一的不同就是加了p.constructor=Child,这到底干啥的,当你创建一个对象,并且创建实例的时候,比如如下代码:

function Parent(name){
  this.name = name;
  this.colors = ["red", "blue", "yellow"];
}

Parent.prototype = {
  constructor: Parent,
  getName: function () {
    console.log(this.name)
  }
}

var p = new Parent()
console.log(p.constructor === Parent) // true

当我们重写prototype方法的时候实例p的constructor就不等于Parent,此时我们需要constructor: Parent已防止constructor混乱,不改变其原本结构,继承中也需要重新对constructor赋值。了解更多请看别人写的一篇文章constructor属性解析


Class继承我们专门拿一个主题去讲,下一篇见。

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