来自 前端知识 2019-10-10 07:08 的文章
当前位置: 金沙澳门官网网址 > 前端知识 > 正文

深入解读JavaScript面向对象编程实践,理一理js中

长远解读JavaScript面向对象编制程序推行

2016/03/14 · JavaScript · 4 评论 · 面向对象

原稿出处: 景庄(@ali景庄)   

面向对象编制程序是用抽象情势创制基于实际世界模型的一种编制程序格局,主要回顾模块化、多态、和打包三种手艺。对JavaScript来讲,其主干是援救面向对象的,同一时间它也提供了强大灵活的依靠原型的面向对象编制程序本领。

正文将会深入的探赜索隐关于使用JavaScript实行面向对象编制程序的部分中坚基础知识,包蕴对象的创建,承袭机制,最后还有可能会轻巧的介绍怎么着借助ES6提供的新的类机制重写古板的JavaScript面向对象代码。

实在要总括这一个概念已经十分久了,只是此前向来都感觉本人还不算完全领会,并且知识点还缺乏系统,所以直接拖着,可是近些日子又再度看了几篇小说,本身也测验了一下,以为始于有一点清晰了,所以想在此地给本人做个小结吧,也期望在学的你们能够在此间学到一点东西。不要躁动,慢慢看,一边看一边做测验,那也是本人近年的觉悟。看了不自然会,要真正自个儿出手去测量检验一下。

面向对象的多少个概念

在步向正题前,先理解守旧的面向对象编程(比如Java)中常会提到到的概念,差不离能够包含:

  • 类:定义对象的特征。它是指标的性质和艺术的沙盘定义。
  • 对象(或称实例):类的三个实例。
  • 个性:对象的特征,比如颜色、尺寸等。
  • 方法:对象的作为,比方行走、说话等。
  • 构造函数:对象初阶化的一眨眼间被调用的秘籍。
  • 承继:子类能够接二连三父类的特色。举个例子,猫承袭了动物的相似性格。
  • 包裹:一种把数量和相关的办法绑定在联合利用的点子。
  • 泛泛:结合复杂的延续、方法、属性的指标能够模拟现实的模子。
  • 多态:分裂的类能够定义同样的情势或性质。

在JavaScript的面向对象编制程序中山大学约也饱含那几个。但是在名字为上或然稍有两样,举例,JavaScript中未有原生的“类”的定义,
而只有对象的定义。因而,随着你认识的彻底,大家会混用对象、实例、构造函数等概念。

何以是目的?

自己的通晓便是那是二个存款和储蓄灌,你能够在里面积攒任李军西,这几个东西便是我们事先学的各个js里面包车型客车数据类型,然后给各种名字贴上二个名字,方便大家现在找到。

例子:

//这个myFirstObject里面有两个属性,分别是firstName和 favoriteAuthor
var myFirstObject = {firstName: "Richard", favoriteAuthor: "Conrad"};

对象(类)的创建

在JavaScript中,大家常见能够采纳构造函数来创建特定项指标对象。诸如Object和Array那样的原生构造函数,在运转时会自动出现在推行景况中。
其余,大家也足以创建自定义的构造函数。比方:

function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } var person1 = new Person('Weiwei', 27, 'Student'); var person2 = new Person('Lily', 25, 'Doctor');

1
2
3
4
5
6
7
8
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
 
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');

依据规矩,构造函数始终都应该以一个大写字母开端(和Java中定义的类同样),普通函数则小写字母开始。
要创建Person的新实例,必得使用new操作符。以这种艺术调用构造函数实际上会经历以下4个步骤:

  1. 始建一个新指标(实例)
  2. 将构造函数的效果域赋给新对象(也正是重设了this的指向,this就本着了那几个新对象)
  3. 实行构造函数中的代码(为这几个新对象加多属性)
  4. 回到新对象

有关new操作符的更加多内容请参照他事他说加以考察这篇文档。

在地方的例证中,咱们创立了Person的多个实例person1person2
那八个指标私下认可都有三个constructor个性,该属性指向它们的构造函数Person,也正是说:

console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true

1
2
console.log(person1.constructor == Person);  //true
console.log(person2.constructor == Person);  //true

何以定义一个对象?

  • 目的字面量
  • 构造函数创设
  • 原型方式开创

自定义对象的种类检查测验

咱俩可以使用instanceof操作符举行项目检查评定。大家成立的具有目的既是Object的实例,同不经常候也是Person的实例。
因为全体的靶子都再而三自Object

console.log(person1 instanceof Object); //true console.log(person1 instanceof Person); //true console.log(person2 instanceof Object); //true console.log(person2 instanceof Person); //true

1
2
3
4
console.log(person1 instanceof Object);  //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object);  //true
console.log(person2 instanceof Person);  //true
对象字面量成立对象

那是最原始的方法,可是也不平价前面包车型大巴多个指标的创导。

//这是一个mango对象,这个对象里面有color shape sweetness属性以及一个​howSweetAmI的方法
​var mango = {
color: "yellow",
shape: "round",
sweetness: 8,
​
​howSweetAmI: function () {
console.log("Hmm Hmm Good");
}
}

构造函数的标题

我们不提议在构造函数中央政府机关接定义方法,固然这样做的话,种种方法都要在各样实例上再也创造一次,那将这几个损耗质量。
——不要忘了,ECMAScript中的函数是指标,每定义二个函数,也就实例化了多少个对象。

幸运的是,在ECMAScript中,大家能够借助原型对象来缓慢解决那一个主题材料。

劣势:这种方式即便老妪能解,可是试想一下,假诺我们要定义有滋有味的水果对象,每一个水果皆有color shape sweetnees的天性,大家都要叁个个定义是还是不是会略带艰苦呢?

那看看上面这种构造函数的创导方法

依附原型情势定义对象的艺术

咱俩创设的各个函数都有二个prototype天性,那天性子是一个指针,指向该函数的原型对象
该对象包括了由特定项目标具备实例分享的性格和形式。也正是说,大家能够运用原型对象来让全体指标实例分享它所包蕴的天性和艺术。

function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } // 通过原型格局来丰盛全体实例共享的办法 // sayName() 方法将会被Person的有所实例分享,而防止了重复创设Person.prototype.sayName = function () { console.log(this.name); }; var person1 = new Person('Weiwei', 27, 'Student'); var person2 = new Person('Lily', 25, 'Doctor'); console.log(person1.sayName === person2.sayName); // true person1.sayName(); // Weiwei person2.sayName(); // Lily

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
 
// 通过原型模式来添加所有实例共享的方法
// sayName() 方法将会被Person的所有实例共享,而避免了重复创建
Person.prototype.sayName = function () {
  console.log(this.name);
};
 
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
 
console.log(person1.sayName === person2.sayName); // true
 
person1.sayName(); // Weiwei
person2.sayName(); // Lily

正如下面的代码所示,通过原型方式定义的法子sayName()为富有的实例所分享。也正是,
person1person2做客的是同多个sayName()函数。一样的,公共性质也得以运用原型情势张开定义。譬喻:

function Chinese (name) { this.name = name; } Chinese.prototype.country = 'China'; // 公共属性,全数实例分享

1
2
3
4
5
function Chinese (name) {
    this.name = name;
}
 
Chinese.prototype.country = 'China'; // 公共属性,所有实例共享

思虑用构造函数的成立方法

构造函数创设方法,正是概念三个构造函数,然后在里面安装属性和艺术值,然后再用new去实例化对象,全体实例化的对象都会有构造函数里面包车型大巴习性和办法。

//在这里定义一个构造函数,在构造函数里面定义属性和方法,注意这里需要用this,后面就可以通过new来实例化对象,使用new的时候,就会将this指向这个实例化的对象。

function Fruit (theColor, theSweetness, theFruitName, theNativeToLand) {
​    this.type = "水果"
    this.color = theColor;
    this.sweetness = theSweetness;
    this.fruitName = theFruitName;
    this.nativeToLand = theNativeToLand;
​
    this.showName = function () {
        console.log("This is a " + this.fruitName);
    }
​
    this.nativeTo = function () {
    this.nativeToLand.forEach(function (eachCountry)  {
       console.log("Grown in:" + eachCountry);
        });
    }

}

原型对象

明日大家来深入的知晓一下什么样是原型对象。

如果成立了三个新函数,就能够依附一组特定的平整为该函数创立三个prototype属性,那特个性指向函数的原型对象。
在默许情形下,全体原型对象都会自行获取二个constructor属性,那些天性包罗三个针对性prototype属性所在函数的指针。
相当于说:Person.prototype.constructor指向Person构造函数。

创制了自定义的构造函数之后,其原型对象暗许只会拿走constructor属性;至于另外艺术,则都以从Object雄起雌伏而来的。
当调用构造函数创立三个新实例后,该实例之少将包括贰个指针(内部属性),指向构造函数的原型对象。ES5中称这几个指针为[[Prototype]]
在Firefox、Safari和Chrome在每一个对象上都扶助二个属性__proto__(近年来已被撇下);而在其他达成中,那特特性对剧本则是完全不可知的。
要注意,其一链接存在于实例与构造函数的原型对象时期,并非实例与构造函数之间

那三者关系的暗指图如下:

澳门金莎娱乐手机版 1

上海教室展现了Person构造函数、Person的原型对象以致Person幸存的多少个实例之间的关联。

  • Person.prototype本着了原型对象
  • Person.prototype.constructor又指回了Person构造函数
  • Person的各类实例person1person2都含有八个中间属性(常常为__proto__),person1.__proto__person2.__proto__针对了原型对象

接下去,大家就足以平素用new的主意来创立形形色色的水果对象了。

//创建一个芒果的对象。
var mangoFruit = new Fruit ("Yellow", 8, "Mango", ["South America", "Central America", "West Africa"]);
mangoFruit.showName(); // This is a Mango.​
mangoFruit.nativeTo();
​//Grown in:South America​
​// Grown in:Central America​
​// Grown in:West Africa​
​
//创建一个pineappleFruit的对象。
​var pineappleFruit = new Fruit ("Brown", 5, "Pineapple", ["United States"]);
pineappleFruit.showName(); // This is a Pineapple.

是否很有利,能够把构造函数想象成二个大工厂,然后您只要利用new的方法去调用那一个工厂,就也就是告诉那么些工厂给自己生育三个东西出来,那么这几个工厂就能够用全数本身有些设备,把它具有的东西能生育的都生产出来。所以假诺在这么些工厂上的配备能生产出来的都会被生产。

再来思索一个难题,这个实例化对象时期是还是不是实际都以有相似性的,正是你能够提炼出个中同样的习性和格局。像上面十分例子,全数水果的type属性和showName方法是或不是都是一样的呢?那大家是否足以用原型来写?

搜索对象属性

从上海教室大家开采,即使Person的多个实例都不含有属性和艺术,但大家却足以调用person1.sayName()
那是经过搜索对象属性的经过来贯彻的。

  1. 搜寻首先从目的实例自个儿伊始(实例person1sayName属性吗?——没有)
  2. 一旦没找到,则一连搜寻指针指向的原型对象person1.__proto__sayName属性吗?——有)

那也是多少个目的实例分享原型所保存的性质和艺术的基本原理。

只顾,假设大家在指标的实例中重写了有个别原型中已存在的习性,则该实例属性会屏蔽原型中的这几个属性。
那时,能够运用delete操作符删除实例上的习性。

怎么着是原型?prototype

js中每一个函数都会有友好的一个原型对象,这几个原型对象叫做prototype.而持有通过那一个构造函数实例化的对象都会指向那么些原型。其实您能够设想一下,构造函数是工厂的话,原型其实是还是不是足以是货仓,全数实例化的指标就能够从货仓里面拿东西。所以大家得以把具备目的公用的性格和方法给放在prototype下边,那样就足防止止属性和章程的双重定义。下边用叁个事例和图来讲美素佳儿(Nutrilon)下。

//这里我们使用原型来创建对象,所有对象共用的属性和方法就放在prototype上。
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}

// 通过原型模式来添加所有实例共享的方法
// sayName() 方法将会被Person的所有实例共享,而避免了重复创建
Person.prototype.sayName = function () {
  console.log(this.name);
};

var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
person1.sayName(); // Weiwei
person2.sayName(); // Lily

实例化的靶子中的name age job属性是从构造函数那获得的,而实例化的对象的原型指向了构造函数的原型对象,所以也许有sayName方法。

image.png

//注意,这里是出口true,所以实际上person1和person2的sayName方法都以同八个,来自同三个地方。

console.log(person1.sayName === person2.sayName); // true

Object.getPrototypeOf()

根据ECMAScript标准,someObject.[[Prototype]] 符号是用于指使 someObject 的原型。
其一等同于 JavaScript 的 __proto__ 属性(现已弃用)。
从ECMAScript 5开始, [[Prototype]] 可以用Object.getPrototypeOf()Object.setPrototypeOf()做客器来访谈。

其中Object.getPrototypeOf()在具备帮助的完毕中,那一个法子重临[[Prototype]]的值。例如:

person1.__proto__ === Object.getPrototypeOf(person1); // true Object.getPrototypeOf(person1) === Person.prototype; // true

1
2
person1.__proto__ === Object.getPrototypeOf(person1); // true
Object.getPrototypeOf(person1) === Person.prototype; // true

也正是说,Object.getPrototypeOf(p1)回来的指标实际正是以此目的的原型。
那些法子的宽容性请参谋该链接)。

小小的计算一下:

指标有二种不一样的成立格局,对象字面量,构造函数,结合原型来创立,最得力的也等于第两种创立方式了,防止同一属性和措施的重新创设,所以能够将指标公用 的属性和艺术定义在prototype上。

Object.keys()

要拿走对象上全体可枚举的实例属性,能够应用ES5中的Object.keys()方法。例如:

Object.keys(p1); // ["name", "age", "job"]

1
Object.keys(p1); // ["name", "age", "job"]

除此以外,假诺你想要获得全部实例属性,无论它是或不是可枚举,都得以行使Object.getOwnPropertyName()方法。

!!!!注意!!!!

设若接纳原型承继的话,假设有四个对象和质量要同时贰头定义的话,需求注意将原型prototype的constructor属性重新赋值,是否听不懂了,别急,先看率先个例证,再看我们后边立异的。

例子1

//这是我们定义水果的属性和方法
function Fruit () {
​
}
​//一个一个使用Fruit.prototype来一一定义各个属性和方法。
Fruit.prototype.color = "Yellow";
Fruit.prototype.sweetness = 7;
Fruit.prototype.fruitName = "Generic Fruit";
Fruit.prototype.nativeToLand = "USA";
​
Fruit.prototype.showName = function () {
console.log("This is a " + this.fruitName);
}
​
Fruit.prototype.nativeTo = function () {
            console.log("Grown in:" + this.nativeToLand);
}

地点的措施固然也会有效的,不过如若属性和措施太多以来,是否太低效了。

更简单的原型成立方法:

function Fruit () {
​
}
​//一个一个使用Fruit.prototype来一一定义各个属性和方法。
Fruit.prototype= {
//这里一定要将prototype的constructor属性重新指向Fruit。因为我们这样相当于是重写了prototype的值。
constructor: Fruit,
color = "Yellow";
sweetness = 7;
fruitName = "Generic Fruit";
showName = function () {
console.log("This is a " + this.fruitName);
}
nativeTo = function () {
            console.log("Grown in:" + this.nativeToLand);
}
}

地方的例证看懂了啊?就是每贰个构造函数的prototype属性都会自带有多少个constructor属性,这几个constructor属性又针对了构造函数,所以我们像上面那样定义的时候,也要将以此constructor属性给重新指向构造函数。(能够再一次看一下地点笔者付出的极度图)

更简短的原型语法

在地点的代码中,就算大家要增添原型属性和方法,将在重新的敲贰遍Person.prototype。为了收缩这一个重复的进度,
更普及的做法是用三个带有全体属性和办法的靶子字面量来重写整个原型对象。
参谋资料。

function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } Person.prototype = { // 这里不可不要再度将构造函数指回Person构造函数,不然会指向这一个新创造的靶子 constructor: Person, // Attention! sayName: function () { console.log(this.name); } }; var person1 = new Person('Weiwei', 27, 'Student'); var person2 = new Person('Lily', 25, 'Doctor'); console.log(person1.sayName === person2.sayName); // true person1.sayName(); // Weiwei person2.sayName(); // Lily

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
 
Person.prototype = {
 
  // 这里务必要重新将构造函数指回Person构造函数,否则会指向这个新创建的对象
  constructor: Person, // Attention!
 
  sayName: function () {
    console.log(this.name);
  }
};
 
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
 
console.log(person1.sayName === person2.sayName); // true
 
person1.sayName();  // Weiwei
person2.sayName();  // Lily

在地点的代码中特意包涵了一个constructor质量,并将它的值设置为Person,从而确定保证了通过该属性能够访谈到符合的值。
瞩目,以这种办法重设constructor属性会产生它的[[Enumerable]]特点设置为true。暗中认可情状下,原生的constructor属性是不可胜计的。
您能够采用Object.defineProperty()

// 重设构造函数,只适用于ES5十二分的浏览器 Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person });

1
2
3
4
5
// 重设构造函数,只适用于ES5兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
  enumerable: false,
  value: Person
});

哪些读取对象的质量:

// We have been using dot notation so far in the examples above, here is another example again:​
​var book = {title: "Ways to Go", pages: 280, bookMark1:"Page 20"};
​
​// To access the properties of the book object with dot notation, you do this:​
console.log ( book.title); // Ways to Go​
console.log ( book.pages); // 280


//当然,也可以用方括号来写:
console.log ( book["title"]); //Ways to Go​
console.log ( book["pages"]); // 280​

结合使用构造函数方式和原型格局

创办自定义类型的最广泛方法,正是构成使用构造函数方式与原型形式。构造函数方式用于定义实例属性,
而原型方式用于定义方法和分享的性质。结果,每一种实例都会有本身的一份实例属性的别本,但同一时间又分享着对方的引用,
最大限度的节约了内存。

怎么贯彻目的的后续:

  • 原型承接
  • 构造函数承接
  • 原型和构造函数承继
  • 创办空对象方法

原型承接:

  • 构造函数都有二个对准原型对象的指针
  • 原型对象都有叁个对准构造函数的constructor
  • 实例化对象都有贰个对准原型的[[prototype]]属性
function Father () {
  this.fatherValue = true;
}

Father.prototype.getFatherValue = function () {
  console.log(this.fatherValue);
};

function Child () {
  this.childValue = false;
}

// 实现继承:继承自Father
Child.prototype = new Father();

Child.prototype.getChildValue = function () {
  console.log(this.childValue);
};

var instance = new Child();
instance.getFatherValue(); // true
instance.getChildValue();  // false

下面的关键点便是用```Child.prototype = new Father();

![image.png](http://upload-images.jianshu.io/upload_images/5763769-c4014978c0314834.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看一下这一个原型链的一个搜索的过程:

var instance = new Child();
instance.getFatherValue(); // true
instance.getChildValue(); // false

当我们查找```instance.getFatherValue(); ```的时候,是如何一个查找的过程呢?

- 先看一下instance 实例上有没有,没有则继续
- Chile prototype上查找有没有,也没有该方法,则继续向上查找
- 向上查找的是Father prototype的属性和方法,查找到了,则输出。

>这种原型继承的方法,其实就相当于延长了Child的原型链,因为其原型现在又可以再向上查找到Father的原型,相当于延长原型链之后可以继续再向上去查找到Father原型上的属性和方法。

#####思考一下:这其实也给了我们一个提示,如果实例,原型上有相同的方法的话,我们一般读取该属性的时候,也是直接读取到了实例上的属性和方法,除非实例本身没有,才会继续往上查找。

####缺点:
这个方法其实也是有缺点的,因为Child的实例化对象的一些属性和方法都是在该原型链上查找的,所以一些引用值得修改也会影响到所有实例化对象的属性,先看个例子。

function father(name,age) {
this.name = name
this.age = age
this.friends = ["lili","koko"]
}
father.prototype.sayname = function () {
console.log(this.name)
}
function children(school) {
this.school = school
澳门金莎娱乐手机版 ,}
children.prototype = new father()
children.prototype.sayname = function () {
console.log("笔者就是不说本人的名字")
}
var instance = new children("幼儿园")
var instance2 = new children("幼儿园")
//这里我们修改了instance的friends的值
instance.friends.push("yoyo")
//大家输出children的三个实例对象试一下,看看七个的属性值的分别
console.log(instance)
console.log(instance2)

![instance的输出.png](http://upload-images.jianshu.io/upload_images/5763769-2bbc0a638ee61a39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![instance2的输出.png](http://upload-images.jianshu.io/upload_images/5763769-b2e3d6d0c8f39176.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

其实从上面两个图也可以发现,一旦修改了一个实例对象上的一个引用值,其他实例化对象的属性值也跟着变化了。因为这里的friends是引用类型的数据,所有的实例都会共享这个属性值,一旦修改其他也跟着修改了。

####构造函数继承

function Animal(){
    this.species = "动物";
  }
Animal.prototype.say = function(){console.log("hahaha")}
 function Cat(name,color){
//这里运用的是构造函数的三番五次,调用Animal构造函数,再用apply将this指向Cat自己
    Animal.apply(this, arguments);
    this.name = name;
    this.color = color;
  }
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
//那样的话Cat的实例化对象就都有Animal的质量了。

>//Cat这个实例化对象就有Animal的属性,但是不会继承来自于Animal原型上的方法。

![image.png](http://upload-images.jianshu.io/upload_images/5763769-49c23d31a71c5e79.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

>构造函数的好处是可以在调用的时候输入参数,```Animal.apply(this, arguments);
```这里可以重新将Cat的参数赋值给Animal中的构造函数。但是这样其实还是有不好之处就是每次新生成一个实例化对象的时候,就会调用一次构造函数。除此之外,Cat并不能继承来自于Animal原型上的方法,这不能实现方法上的复用。

所以,我们可以考虑结合原型方法和构造函数方法。

刚刚是不是说到,只使用原型方法的话,继承父类的所有属性和方法,但是所有实例没有自己的属性,可能会因为一个实例的属性的更改而影响到其他实例;而构造函数的方法只能实现构造函数内的属性方法继承,不能实现父类原型上的继承;;

那就结合这两种方法来实现以下;

// 父类构造函数
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}

// 父类方法
Person.prototype.sayName = function () {
console.log(this.name);
};

// --------------

// 子类构造函数
function Student (name, age, job, school) {
// 承继父类的全部实例属性(获得父类构造函数中的属性)
Person.call(this, name, age, job);
this.school = school; // 加多新的子类属性
}

// 承接父类的原型方法(得到父类原型链上的属性和章程)
Student.prototype = new Person();

// 新添的子类方法
Student.prototype.saySchool = function () {
console.log(this.school);
};

var person1 = new Person('Weiwei', 27, 'Student');
var student1 = new Student('Lily', 25, 'Doctor', "Southeast University");

console.log(person1.sayName === student1.sayName); // true

person1.sayName(); // Weiwei
student1.sayName(); // Lily
student1.saySchool(); // Southeast University

![image.png](http://upload-images.jianshu.io/upload_images/5763769-508d69653dfb5c9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这个就是比较好的继承方法,将父类的属性继承过来,所有的实例都有自己的属性,同时将原型上的方法也继承过来,实现所有实例都有公共的属性和方法。当然,细心的你也许已经发现了,就是这个Student子类的原型上除了有saySchool方法之外,还有父类构造函数内的那些name job age属性,那是因为我们是使用```Student.prototype = new Person();```来实现继承的,所以该原型实际上就是Person的实例;

所以其实这个方法虽然是好,但是也会出现这样一个情况,属性的覆盖,原型上还有对应父类的属性。这也不是我们最初想要的结果。

所以,我们又引入了另外一个方法

####利用中间空对象的方法继承。
>什么意思呢?我们上面的结合原型和构造函数的方法之所以会出现原型上还有相同的属性的问题是因为,我们用```Student.prototype = new Person();```来实现继承,相当于把Student.prototype重新赋值成Person的实例了,我们就肯定会有Person 构造函数上的属性和原型上的方法。那么我们要的最理想的状态就是用```Student.prototype = new Person();```的时候,Person的构造函数上没有属性,但是这显然不够理智,那么我们就可以引入一个中间的空对象,来实现继承。
啊啊啊,还是看例子吧。

//假若那规范的话,是还是不是很周全,Child的原型是F的二个实例,而F的构造函数大家是安装成空的。
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();

>所以我们可以用这样的方式来封装起来以后可以使用‘

//那几个就是Child承继Parent的点子。
function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }

我们再来写个例子吧;

// 父类构造函数
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}

// 父类方法
Person.prototype.sayName = function () {
console.log(this.name);
};

// --------------

// 子类构造函数
function Student (name, age, job, school) {
// 承继父类的具备实例属性(获得父类构造函数中的属性)
Person.call(this, name, age, job);
this.school = school; // 加多新的子类属性
}

function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }
extend( Student,Person); //调用该办法,达成三番五次父类原型链上的质量和艺术;

// 新扩展的子类方法
Student.prototype.saySchool = function () {
console.log(this.school);
};

var person1 = new Person('Weiwei', 27, 'Student');
var student1 = new Student('Lily', 25, 'Doctor', "Southeast University");

console.log(person1.sayName === student1.sayName); // true

person1.sayName(); // Weiwei
student1.sayName(); // Lily
student1.saySchool(); // Southeast University
console.log(student1)

![image.png](http://upload-images.jianshu.io/upload_images/5763769-e762216f5426ad1e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

>这样继承是不是好多了,至少跟前面的例子相比,我们的原型链上不会再继承来自父类上的属性;



>后面还有方法会继续总结的,今天先写到这里好了,感觉自己写的过程真的会发现很不一样,也算是了解多了一些。


参考链接:
http://javascriptissexy.com/javascript-objects-in-detail/#
http://javascriptissexy.com/javascript-prototype-in-plain-detailed-language/#
http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/#
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

继承

好些个的面向对象语言都支持三种持续方式:接口承袭和兑现持续。ECMAScript只协理落实三番五次,並且其完毕持续重要信赖原型链来实现。

原型链继承

运用原型链作为贯彻持续的着力理念是:利用原型让三个援用类型承袭另八个援引类型的习性和措施。首先我们先想起一些基本概念:

  • 各样构造函数皆有三个原型对象(prototype
  • 原型对象包蕴三个针对构造函数的指针(constructor
  • 实例都带有二个针对原型对象的中间指针([[Prototype]]

假使大家让原型对象等于另一个门类的兑现,结果会如何?分明,那时候的原型对象将蕴含多个针对性另一个原型的指针
相应的,另贰个原型中也蕴含着八个对准另三个构造函数的指针。假诺另三个原型又是另贰个品种的实例,那么上述提到仍旧成立,
那样罕见推动,就重组了实例与原型的链条。
更详细的剧情能够参照本条链接。
先看多个回顾的例证,它躬行实践了使用原型链实现持续的主题框架:

function Father () { this.fatherValue = true; } Father.prototype.getFatherValue = function () { console.log(this.fatherValue); }; function Child () { this.childValue = false; } // 实现一而再:继承自Father Child.prototype = new Father(); Child.prototype.getChildValue = function () { console.log(this.childValue); }; var instance = new Child(); instance.getFatherValue(); // true instance.getChildValue(); // false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Father () {
  this.fatherValue = true;
}
 
Father.prototype.getFatherValue = function () {
  console.log(this.fatherValue);
};
 
function Child () {
  this.childValue = false;
}
 
// 实现继承:继承自Father
Child.prototype = new Father();
 
Child.prototype.getChildValue = function () {
  console.log(this.childValue);
};
 
var instance = new Child();
instance.getFatherValue(); // true
instance.getChildValue();  // false

在上头的代码中,原型链承袭的骨干语句是Child.prototype = new Father(),它完成了ChildFather的继承,
而后续是经过创立Father的实例,并将该实例赋给Child.prototype实现的。

福寿双全的实质是重写原型对象,代之以三个新类型的实例。也正是说,原来存在于Father的实例中的全部属性和方法,
当今也存在于Child.prototype中了。

以那一件事例中的实例以致构造函数和原型之间的涉嫌如下图所示:

澳门金莎娱乐手机版 2

在地点的代码中,大家未有接纳Child暗中认可提供的原型,而是给它换了贰个新原型;那么些新原型就是Father的实例。
于是乎,新原型不仅仅具有了作为多个Father的实例所具有的任何性质和办法。况且其里面还应该有三个指针[[Prototype]],指向了Father的原型。

  • instance指向Child的原型对象
  • Child的原型对象指向Father的原型对象
  • getFatherValue()方法如故还在Father.prototype
  • 但是,fatherValue则位于Child.prototype
  • instance.constructor昨东瀛着的是Father

因为fatherValue是一个实例属性,而getFatherValue()则是贰个原型方法。既然Child.prototype现在是Father的实例,
那么fatherValue本来就献身该实例中。

由此兑现原型链,本质上扩张了本章前边介绍的原型搜索机制。譬如,instance.getFatherValue()会经历多个寻觅步骤:

  1. 搜寻实例
  2. 搜索Child.prototype
  3. 搜索Father.prototype

本文由金沙澳门官网网址发布于前端知识,转载请注明出处:深入解读JavaScript面向对象编程实践,理一理js中

关键词: