JS原型链和继承的实现方式

1.面向对象的补充

1.1 枚举属性的补充

  • 不可枚举属性在node环境下,不会被打印出来
  • 但是如果是浏览器中会将其显示出来,但是颜色会呈灰色,这是为了让开发者更好的去调试
var obj={
  name:"wjy",
  age:20
}
Object.defineProperty(obj,"address",{
  value:"怀化",
  enumerable:false,
  writable:true,
  configurable:true
})
console.log(obj);

node环境下:

35.png

浏览器下:

36.png

2.JavaScript中的类和对象

当我们编写如下代码的时候,我们会如何来称呼这个Person呢?

  • 在JS中Person应该被称之为是一个构造函数
  • 从很多面向对象语言过来的开发者,也习惯称之为类,因为类可以帮助我们创建出来对象p1,p2
  • 如果从面向对象的编程范式角度来看,Person确实是可以称之为类的
function Person(){

}
var p1=new Person()
var p2=new Person()
  • 从严格模式下,这并不能称之为是一个类,

3.面向对象的特性-继承

面向2对象有三大特性:封装、继承、多态

  • 封装:我们前面蒋属性和方法封装到一个类中,可以称之为封装的过程
  • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中)
  • 多态:不同的对象在执行时表现出不同的形态

那么继承是做什么呢?

  • 继承可以帮我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。

那么javascript当中是如何实现继承呢?

  • 不着急,我们先来看一下JavaScript原型链的机制
  • 再利用原型链的机制实现一下继承:

4.JavaScript原型链

在真正实现继承之前,我们需要了解一个非常重要的概念:原型链。

  • 我们知道,从一个对象上获取属性,如果在当前对象中没有找到,会去原型中找,如果原型还没找到,会去原型的原型中查找,如果还没找到,去原型的原型的原型中查找,直到到最顶层的对象,如果还没有找到,则会返回一个undefined

  • 在查找的过程中其实会沿着原型链去查找

var obj={
  name:"wjy",
  age:20
}
obj.__proto__={

};
obj.__proto__.__proto__={

};
obj.__proto__.__proto__.__proto__={
  address:"怀化"
}
console.log(obj.address); //怀化
37.png

5.Object的原型

那什么会是原型链的尽头呢?比如第三个对象是否也有原型[[prototype]]属性呢?

console.log(obj.__proto__.__proto__.__proto__.__proto__);
  • 我们会发现它打印的是 [Object: null prototype] {}

    • 事实上它已经是顶层对象了
    • 从Object直接创建出来的对象的原型都是[Object: null prototype] {}
  • 那么我们可能会问:[Object: null prototype] {}到底有什么特殊的

    • 特殊一:该对象有原型属性,它的原型属性已经指向的是null,也就是已经是顶层原型了
    • 特殊二:该对象上有很多默认的属性和方法
var obj={};//创建一个对象
var obj2=new  Object();//创建一个对象

//*  Object其实是一个构造函数,只不过这个构造函数,是js自己给我们创建好的,它也有原型对象,原型对象有个 constructor属性指向这个构造函数本身
// * 使用new关键字调用函数时会执行以下几个步骤
/**
 * * 1 在内存中创建一个新的对象(空对象)
 * * 2 这个新对象的[[prototype]]赋值为这个构造函数的prototype属性
 * * 3 函数内部的this指向这个新对象
 * * 4 执行函数内部的代码
 * * 5 如果函数没有返回其他非空对象,则将新对象返回
 */

// * 所有直接通过Object创建出来的对象的原型都是[Object:null prototype]{}
//* 所以 obj.__proto__是指向Object.prototype
// * obj2.__proto__也是指向Object.prototype

console.log(obj.__proto__==Object.prototype);//true
console.log(obj2.__proto__==Object.prototype);//true
console.log(obj2.__proto__==obj2.__proto__);//true

//* 我们可以尝试将Object.prototype对象打印出来  
// *之前我们讲过,如果一个原型对象的值为 [Object:null prototype]{}则代表这个对象是顶层对象,它的[[prototype]]属性的值为null
console.log(Object.prototype); //[Object: null prototype] {}
console.log(Object.prototype.__proto__); //null

// * 我们可以试着将这个Object.prototype对象的所有属性描述符打印出来,其实Object.prototype对象有很多属性,只不过是不可枚举的
console.log(Object.getOwnPropertyDescriptors(Object.prototype));

// * Object 是所有对象的最顶层的父类

6.Object是所有类的父类

从我们上面的Object原型我们可以得出一个结论:原型链的最顶层对象就是Object的原型对象

function Person(){

}
//* 查看Person的原型对象
console.log(Person.prototype);
console.log(Object.getOwnPropertyDescriptors(Person.prototype));
// * 查看Person的原型对象的__proto__
console.log(Person.prototype.__proto__);//* 它指向的是Object的原型对象  [Object: null prototype] {}
// * Object的原型对象的__proto__是指向null
console.log(Person.prototype.__proto__.__proto__);//* null

var p1=new Person()
38.png

7.为什么需要继承

  • 学生构造函数

    • 有以下属性
      • name
      • age
      • sno
    • 有以下方法
      • eating
      • running
      • studying
  • 老师构造函数

    • 有以下属性
      • name
      • age
      • title
    • 有以下方法
      • eating
      • running
      • studying
      • teaching
// Student
function Student(name,age,sno){
  this.name=name;
  this.age=age;
  this.sno=sno;
}
Student.prototype.eating=function(){
  console.log(this.name+" eatings");
}
Student.prototype.running=function(){
  console.log(this.name+" running");
}
Student.prototype.studying=function(){
  console.log(this.name+" studying");
}

// Teacher
function Teacher(name,age,title){
  this.name=name;
  this.age=age;
  this.title=title;
}
Teacher.prototype.eating=function(){
  console.log(this.name+" eatings");
}
Teacher.prototype.running=function(){
  console.log(this.name+" running");
}
Teacher.prototype.studying=function(){
  console.log(this.name+" studying");
}
Teacher.prototype.teaching=function(){
  console.log(this.name+" teaching");
}

我们仔细一看会发现 存在大量的重复代码,

比如说 重复的属性:name、age

比如说:重复的方法:eating、running、studying

我们是否可以将这些共有的属性和方法抽象到一个父类中?

// * 父类 :放公共的属性和方法
function Person(){
  this.name="why";
}
Person.prototype.eating=function(){
  console.log(this.name+"在吃东西");
}

function Student(){
  this.sno=111;
}
Student.prototype.studying=function(){
  console.log(this.name+"在学习");
}

var stu=new Student();
console.log(stu.name);
console.log(stu.eating);

  • 以上这两个类没有关联.所以stu.eating会是undefined

7.1 继承-原型链的继承方案

// * 父类 :放公共的属性和方法
function Person(){
  this.name="why";
}
Person.prototype.eating=function(){
  console.log(this.name+"在吃东西");
}

function Student(){
  this.sno=111;
}
// * 通过原型链的方式实现了继承,将Student的原型对象赋值为Person函数的实例对象
var p=new Person()
Student.prototype=p;
Student.prototype.studying=function(){
  console.log(this.name+"在学习");
};

var stu=new Student();
console.log(stu.name);
console.log(stu.eating()); 
39.png
7.1.1 缺点
  • 打印stu,有些属性是看不到的,因为继承的属性是看不到的,(原型上的属性或方法是不能直接看到的

  • 如果创建两个对象,两个对象的部分属性不是相互独立的

    • 如果是直接修改对象属性的值,这个属性的值是独立的
    • 如果是获取对象属性的引用,修改引用中的值,这个属性的值不是独立的,会相互影响的
  • 在前面实现类的继承中,是没有传递参数的

7.1.1 缺点(官方术语)

某些属性是保存在p对象上的

  1. 我们通过直接打印对象是看不到这个属性的
  2. 这个属性会被多个对象共享,如果这个属性是引用类型,那么就会造成问题
  3. 不能给Person传递参数,因为这个对象是一次性的(不能实现定制化)

7.2 继承——借用构造函数方案

为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(有很多名称:借用构造函数或者称之为经典继承或者称之为伪造对象)

  • steal是偷窃、剽窃的意思,但是这里可以翻译成借用;

借用继承的做法非常简单:在子类型构造函数内部调用父类型构造函数

  • 因为函数可以在任意时刻被调用
  • 因此通过apply()和call()方法也可以在新创建的对象执行构造函数
7.2.1 实现可定制化(可以传参)
// * 父类 :放公共的属性和方法
function Person(name,age,friend){
  this.name=name;
  this.age=age;
  this.friend=friend;
}
Person.prototype.eating=function(){
  console.log(this.name+"在吃东西");
}

function Student(name,age,friend,sno){
  Person.call(this,name,age,friend)
  this.sno=sno;
}
//* 1.实现传参
var stu=new Student("wjy",20,['hyz','zwl'],111)
console.log(stu);
7.2.2 创建的多个对象之间的数据是独立的,不会相互影响
// * 父类 :放公共的属性和方法
function Person(name,age,friend){
  this.name=name;
  this.age=age;
  this.friend=friend;
}
Person.prototype.eating=function(){
  console.log(this.name+"在吃东西");
}

function Student(name,age,friend,sno){
  Person.call(this,name,age,friend)
  this.sno=sno;
}
//* 1.实现传参
var stu=new Student("wjy",20,['hyz','zwl'],111)
console.log(stu);

// * 2.创建的两个对象不会相互影响
var stu1=new Student("hyz",21,['wjy','zmj'],222)
var stu2=new Student("zmj",21,['lt','txy'],333)
console.log(stu1);
console.log(stu2);
stu1.friend.push("zzz");
console.log(stu1);
console.log(stu2);
7.2.3 使用构造函数也是有弊端
  • Person函数至少会调用两次
  • stu的原型对象会多一些属性,但是这些属性是没有存在的必要。

8.总结

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

推荐阅读更多精彩内容