当前位置: 永利皇宫463手机版 > Web前端 > 正文

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

时间:2019-10-06 20:01来源:Web前端
深切解读JavaScript面向对象编制程序实施 2016/03/14 · JavaScript· 4 评论 ·面向对象 初稿出处:景庄(@ali景庄)    面向对象编制程序是用抽象方式开创基于具体世界模型的一种编制程序情

深切解读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下边,那样就能够幸免属性和章程的双重定义。下边用三个事例和图来讲圣元(Synutra)下。

//这里我们使用原型来创建对象,所有对象共用的属性和方法就放在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

别忘了Object

拥有的函数都暗中认可原型都以Object的实例,由此暗中认可原型都会蕴藏八个里边指针[[Prototype]],指向Object.prototype
那也正是具有自定义类型都会一连toString()valueOf()等暗中同意方法的根本原因。所以,
小编们说上边例子体现的原型链中还应有包罗别的多少个继续档期的顺序。关于Object的越多内容,能够参谋那篇博客。

也正是说,Child继承了Father,而Father继承了Object。当调用了instance.toString()时,
实则调用的是保存在Object.prototype中的那些格局。

原型链承继的标题

先是是各种,一定要先再而三父类,然后为子类增加新点子。

其次,利用原型链达成持续时,不可能运用对象字面量创造原型方法。因为这么做就能重写原型链,如上面的例证所示:

function Father () { this.fatherValue = true; } Father.prototype.getFatherValue = function () { console.log(this.fatherValue); }; function Child () { this.childValue = false; } // 承接了Father // 此时的原型链为 Child -> Father -> Object Child.prototype = new Father(); // 使用字面量增多新措施,会招致上一行代码无效 // 此时大家着想的原型链被隔断,而是改为 Child -> Object Child.prototype = { getChildValue: function () { console.log(this.childValue); } }; var instance = new Child(); instance.getChildValue(); // false instance.getFatherValue(); // error!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Father () {
  this.fatherValue = true;
}
 
Father.prototype.getFatherValue = function () {
  console.log(this.fatherValue);
};
 
function Child () {
  this.childValue = false;
}
 
// 继承了Father
// 此时的原型链为 Child -> Father -> Object
Child.prototype = new Father();
 
// 使用字面量添加新方法,会导致上一行代码无效
// 此时我们设想的原型链被切断,而是变成 Child -> Object
Child.prototype = {
  getChildValue: function () {
    console.log(this.childValue);
  }
};
 
var instance = new Child();
instance.getChildValue();  // false
instance.getFatherValue(); // error!

在地点的代码中,我们总是五回修改了Child.prototype的值。由于现行反革命的原型蕴含的是三个Object的实例,
而非Father的实例,因而大家思索中的原型链已经被切断——ChildFather以内业已远非涉及了。

最后,在创立子类型的实例时,不能够向超类型的构造函数中传递参数。实际上,应该正是未有艺术在不影响全数目标实例的状态下,
给超类型的构造函数字传送递参数。因而,大家非常少单独接纳原型链。

借用构造函数继承

借用构造函数(constructor stealing)的为主思虑如下:即在子类构造函数的中间调用超类型构造函数。

function Father (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } function Child (name) { // 承接了Father,同不经常间传递了参数 Father.call(this, name); } var instance1 = new Child("weiwei"); instance1.colors.push('black'); console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ] console.log(instance1.name); // weiwei var instance2 = new Child("lily"); console.log(instance2.colors); // [ 'red', 'blue', 'green' ] console.log(instance2.name); // lily

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Father (name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
 
function Child (name) {
  // 继承了Father,同时传递了参数
  Father.call(this, name);
}
 
var instance1 = new Child("weiwei");
instance1.colors.push('black');
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
console.log(instance1.name); // weiwei
 
var instance2 = new Child("lily");
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]
console.log(instance2.name); // lily

为了保险Father构造函数不会重写子类型的天性,能够在调用超类型构造函数后,再添加应该在子类型中定义的质量。

借用构造函数的劣势

同构造函数同样,不只怕落实格局的复用。

结合使用原型链和借用构造函数

日常说来,大家会组成使用原型链承袭和借用构造函数来完结三番五次。也便是说,使用原型链达成对原型属性和办法的接轨,
而透过借用构造函数来完毕对实例属性的三番五次。这样,既通过在原型上定义方法实现了函数复用,又能够确定保障各种实例都有它协调的属性。
大家改换最早的例证如下:

// 父类构造函数 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(); // Lilystudent1.saySchool(); // Southeast University

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 父类构造函数
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

结缘集成防止了原型链和借用构造函数的劣点,融合了它们的长处,成为了JavaScript中最常用的后续形式。
而且,instanceofisPropertyOf()也能够用于识别基于组合承接创立的目的。

整合承袭的立异版:使用Object.create()

在地点,大家承袭父类的原型方法运用的是Student.prototype = new Person()
这样做有相当多的难点。
精雕细刻格局是行使ES5中新扩充的Object.create()。能够调用那么些艺术来创设一个新指标。新指标的原型便是调用create()办法传入的第叁个参数:

Student.prototype = Object.create(Person.prototype); console.log(Student.prototype.constructor); // [Function: Person] // 设置 constructor 属性指向 Student Student.prototype.constructor = Student;

1
2
3
4
5
6
Student.prototype = Object.create(Person.prototype);
 
console.log(Student.prototype.constructor); // [Function: Person]
 
// 设置 constructor 属性指向 Student
Student.prototype.constructor = Student;

详细用法能够参谋文书档案。
关于Object.create()的兑现,大家能够参照一个简便的polyfill:

function createObject(proto) { function F() { } F.prototype = proto; return new F(); } // Usage: Student.prototype = createObject(Person.prototype);

1
2
3
4
5
6
7
8
function createObject(proto) {
    function F() { }
    F.prototype = proto;
    return new F();
}
 
// Usage:
Student.prototype = createObject(Person.prototype);

从实质上讲,createObject()对传播在那之中的靶子实践了一回浅复制。

ES6中的面向对象语法

ES6中引进了一套新的第一字用来促成class。
JavaScript依然是依赖原型的,这么些新的入眼字回顾class、
constructor、
static、
extends、
和super。

对前方的代码修改如下:

'use strict'; class Person { constructor (name, age, job) { this.name = name; this.age = age; this.job = job; } sayName () { console.log(this.name); } } class Student extends Person { constructor (name, age, school) { super(name, age, 'Student'); this.school = school; } saySchool () { console.log(this.school); } } var stu1 = new Student('weiwei', 20, 'Southeast University'); var stu2 = new Student('lily', 22, 'Nanjing University'); stu1.sayName(); // weiwei stu1.saySchool(); // Southeast University stu2.sayName(); // lily stu2.saySchool(); // Nanjing University

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'use strict';
 
class Person {
 
  constructor (name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
  }
 
  sayName () {
    console.log(this.name);
  }
 
}
 
class Student extends Person {
 
  constructor (name, age, school) {
    super(name, age, 'Student');
    this.school = school;
  }
 
  saySchool () {
    console.log(this.school);
  }
 
}
 
var stu1 = new Student('weiwei', 20, 'Southeast University');
var stu2 = new Student('lily', 22, 'Nanjing University');
 
stu1.sayName(); // weiwei
stu1.saySchool(); // Southeast University
 
stu2.sayName(); // lily
stu2.saySchool(); // Nanjing University

类:class

是JavaScript中存活基于原型的接轨的语法糖。ES6中的并非一种新的创制对象的办法,只但是是一种“特殊的函数”,
进而也包含类表达式和类声明,
但供给当心的是,与函数评释不一样的是,类阐明不会被提升。
参照他事他说加以考察链接

类构造器:constructor

constructor()主意是有一种奇特的和class手拉手用于成立和初阶化对象的艺术。注意,在ES6类中不得不有叁个称呼为constructor的方法,
要不然会报错。在constructor()形式中得以调用super驷不及舌字调用父类构造器。固然你从未点名三个构造器方法,
类会自动使用三个私下认可的构造器。参谋链接

类的静态方法:static

静态方法正是足以一直动用类名调用的主意,而毋庸对类进行实例化,当然实例化后的类也力所不及调用静态方法。
静态方法常被用于成立应用的工具函数。参照链接

一而再父类:extends

extends重要字能够用于后续父类。使用extends能够扩充学一年级个放权的指标(如Date),也能够是自定义对象,或然是null

关键字:super

super驷马难追字用于调用父对象上的函数。
super.propsuper[expr]表明式在类和对象字面量中的任何办法定义中都有效。

super([arguments]); // 调用父类构造器 super.functionOnParent([arguments]); // 调用父类中的方法

1
2
super([arguments]); // 调用父类构造器
super.functionOnParent([arguments]); // 调用父类中的方法

要是是在类的构造器中,要求在this至关重大字此前运用。仿照效法链接

小结

本文对JavaScript的面向对象机制进行了相比深刻的解读,特别是构造函数和原型链形式达成目的的创立、承接、以及实例化。
其它,本文还简介了如在ES6中编辑面向对象代码。

References

  1. 详解Javascript中的Object对象
  2. new操作符
  3. JavaScript面向对象简单介绍
  4. Object.create()
  5. 雄起雌伏与原型链

    2 赞 7 收藏 4 评论

图片 3

编辑:Web前端 本文来源:深入解读JavaScript面向对象编程奉行,理一理js中

关键词: