尽人皆知,Javascript是一门面向对象的言语,假如说针对面向对象来提问的话,我会想到两个题目,在js中,类与实例对象是怎样竖立的,类与实例对象又是怎样完成继续的。
面向对象
怎样声明一个类
ES5中,还没有类的观点,而是经由过程函数来声明;到了ES6,有了class关键字,则经由过程class来声明
// 类的声明
var Animal = function () {
this.name = 'Animal';
};
// es6中class的声明
class Animal2 {
constructor () {
this.name = 'Animal2';
}
怎样竖立对象
1.字面量对象
2.显现的组织函数
3.Object.create
// 第一种体式格局:字面量
var o1 = {name: 'o1'};
var o2 = new Object({name: 'o2'});
// 第二种体式格局:组织函数
var M = function (name) { this.name = name; };
var o3 = new M('o3');
// 第三种体式格局:Object.create
var p = {name: 'p'};
var o4 = Object.create(p);
类与继续
怎样完成继续?
继续的实质就是原型链
借助组织函数完成继续
/**
* 借助组织函数完成继续
*/
function Parent1 () {
this.name = 'parent1';
}
Parent1.prototype.say = function () {
};
function Child1 () {
Parent1.call(this); // 或Parent1.apply(this,arguments)
this.type = 'child1';
}
console.log(new Child1(), new Child1().say());
重点是这句:Parent1.call(this); 在子类的组织函数里实行父类的组织函数,经由过程call/apply转变this指向,从而致使父类组织函数实行时的这些属性都邑挂载到子类实例上去。
题目: 只能继续父类组织函数中声明的实例属性,并没有继续父类原型的属性和要领
借助原型链完成继续
/**
* 借助原型链完成继续
*/
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'child2';
}
Child2.prototype = new Parent2();
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);
重点就是这句: Child2.prototype = new Parent2(); 就是说 new 一个父类的实例,然后赋给子类的原型 也就是说 new Child2().__proto__ === Child2.prototype === new Parent2()当我们在new Child2()中找不到属性/要领,顺着原型链就能够找到new Parent2(),如许就完成了继续。
题目: 原型链中的原型对象是共用的,子类没法经由过程父类竖立私有属性
比方当你new两个子类s1、s2的时刻,改s1的属性,s2的属性也随着转变
组合式继续
/**
* 组合体式格局
*/
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3 () {
Parent3.call(this); // 父类组织函数实行了
this.type = 'child3';
}
Child3.prototype = new Parent3(); // 父类组织函数实行了
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
组合式就是原型链+组织函数继续,处理了前两种要领的题目,但也有不足:子类实例化时,父类组织函数实行了两次,所以有了下面的组合继续的优化1
组合继续的优化1
/**
* 组合继续的优化1
* @type {String}
*/
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4 () {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5, s6);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);
实在就是把原型链继续的那句 Child4.prototype = new Parent4(); 改成 Child4.prototype = Parent4.prototype; 如许虽然父类组织函数只实行了一次了,但又有了新的题目: 没法推断s5是Child4的实例照样Parent4的实例 由于Child4.prototype.constructor指向了Parent4的实例;假如直接加一句 Child4.prototype.constructor = Child4 也不可,如许Parent4.prototype.constructor也指向Child4,就没法辨别父类实例了。
若要推断a是A的实例 用constructor
a.__proto__.constructor === A
用instanceof则不正确, instanceof 推断 实例对象的__proto__ 是否是和 组织函数的prototype 是同一个援用。若A 继续 B, B 继续 C 在该原型链上的对象 用instanceof推断都返回ture
组合继续的优化2(引荐)
/**
* 组合继续的优化2
*/
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5 () {
Parent5.call(this);
this.type = 'child5';
}
//注重此处,用到了Object.creat(obj)要领,该要领会对传入的obj对象举行浅拷贝
//这个要领作为一个桥梁,到达父类和子类的一个断绝
Child5.prototype = Object.create(Parent5.prototype);
//修正组织函数指向
Child5.prototype.constructor = Child5
组织函数属性继续和竖立子类和父类原型的链接
ES6完成继续
引入了class、extends、super关键字,在子类组织函数里挪用super()要领来挪用父类的组织函数。
在子类的组织函数中,只要挪用super以后,才够运用this关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只要super要领才返回父类实例。
class Child6 extends Parent6 {
constructor(x, y, color) {
super(x, y); // 挪用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // super代表父类原型,挪用父类的toString()
}
}
class完成道理
Class充当了ES5中组织函数在继续完成过程当中的作用
有prototype属性,有__proto__属性,这个属性在ES6中的指向有一些主动的修正。
同时存在两条继续链:一条完成属性继续,一条完成要领继续。
class A extends B {}
A.__proto__ === B; //继续属性
A.prototype.__proto__ === B.prototype; //继续要领
ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型。
但是在ES5中 A.__proto__是指向Function.prototype的,由于每个组织函数实在都是Function这个对象组织的,ES6中子类的__proto__指向父类能够完成属性的继续。
只要函数有prototype属性,只要对象有__proto__属性 ;但函数也有__proto__属性,由于函数也是一个对象,函数的__proto__即是 Function.prototype。
extends完成道理
//原型衔接
Man.prototype = Object.create(Person.prototype);
// B继续A的静态属性
Object.setPrototypeOf(Man, Person);
//绑定this
Person.call(this);
前两句完成了原型链上的继续,末了一句完成组织函数上的继续。