4.1类理论
面向对象强调的是数据和操作数据的行为本质上是相互关联的。
面向对象是一种编程思想,强调的是对象。而所谓对象,有一个不太准确的解释。即你看到的,你想到的,有形的,无形的事物,都是对象。概括起来就是万物皆为对象。
**类与对象的概念 **
类的定义:具有相同属性和行为的对象的集合;对象的定义:根据类的属性和行为创建的实例化。对象的特点是唯一性、真实性。举个例子,学生就是一个类。而对象就是小明、小白、小红。他们的属性就是年龄、身份证号、民族等,而行为指他们睡觉、吃饭等。
图片来自网络侵删
面向对象和面向过程的区别
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
4.2构造函数
类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。这个方法的任务就是初始化实例需要的所有信息(状态)。
网上还有另外的说法,我觉得更好理解。即通过new 函数名来实例化对象的函数叫做构造函数。
从功能上来说,构造函数的功能是初始化对象。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。构造函数定义时首字母大写(规范)
那么new到底做了哪些呢?
(1)创建了一个新对象;
(2)将它引用给this,继承函数对象的原型;
(3)给新对象添加属性和方法;
(4)返回this指向的新对象,也就是实例。
var obj={};
obj._proto_=foo.prototype;
foo.call(obj);
网上还有另一种解释:主要区别在第一步,是基于函数原型对象创建的新对象;
let parent=function(name,age){
this.name=name;
this.age=age;
}
parent.prototype.say=function(){
console.log("say")
}
//定义new方法
let new=function(parent,...rest){
// 1.以构造器的prototype属性为原型,创建新对象;
let child=Object.create(parent.prototype)
// 2.将this和调用参数传给构造器执行
parent.apply(child,rest)
// 3.返回第一步的对象
return child;
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';
备注:Object.create()方法会创建一个新对象并把它关联到我们指定的对象。
ES5类的写法
function Foo(x,y){ this.x=x; this.y=y; } foo.prototype.toString=function(){ return '(' + this.x + ', ' + this.y + ')'; }var f=new Foo(1,2);
ES6类的写法
class Foo {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var f=new Foo(1,2);
ES6引入了calss这个概念,作为对象的模板,通过calss关键字,来定义类。类的数据类型就是函数,类本身就指向构造函数。
类的所有方法都定义在类的prototype属性上面。类的内部所有定义的方法,都是不可枚举的。
(1)prototype属性
先来看看prototype的定义。每一个创建的函数中都带有一个prototype属性。这个属性是个指针,指向一个对象(为方便理解,可以简单认为就是原型对象),该对象的用途是包含所有实例共享的属性和方法。
所有通过同一个构造函数创建的实例对象,都会共享同一个prototype。
注意区分prototype和_proto_(该属性在ES标准定义中的名字是[[Prototype]]);
**(2)_proto_属性 **
proto_是定义对象的时候,这个对象会自带一个_proto,而这个_proto_指向的是这个对象的构造函数的prototype。
function A(a){
this.a = a;
}
console.log(A.prototype)
let a=new A("xiaoming")
console.log(a)
//A {a: "xiaoming"}
//a: "xiaoming"
//__proto__: Object
利用new 和A函数初始化对象,根据上面new的执行过程,new的时候创建了对象,而a是构造函数的实例对象,所以它会有_proto_属性,并且它的_proto_指向构建函数A的原型。即
a.proto=A.prototype //true
既然说到了_proto_,那就有必要提一提原型链。
var obj={
a:2
}
obj.a //2
上面代码中当obj调用a的属性时,会触发[[get]]操作。对于[[get]]操作来说,第一步是检查对象本身是否有这个属性,有的话就引用,没有的话就会使用_proto_属性。向它的上一级继续寻找,只至找到或结束。_proto_链最终会指向顶端的Object.prototype
(3)constructor
constructor是类的默认方法,通过new命令生成对象实例时,自动调用该方法。从类的角度来说,一个类必须有constructor方法。如果没有显示定义,一个空的constructor方法会被默认添加。这个属性引用的是该对象关联的函数。
每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而
函数创建的对象.__proto__ === 该函数.prototype,该函数.prototype.constructor===该函数本身,
constructor并不是由对象生成的,它是由函数prototype原型对象带有的属性,而对象是通过_proto_原型链查找而来的

图片来自网络侵删
4.3继承
在面向类的语言中,可以先定义一个类,然后定义一个继承前者的类。
后者通常被称为"子类",前者通常被称为"父类"。
定义好子类以后,相对于父类来说它就是一个独立并且完全不同的类。子类会包含父类行为的原始副本,但是也可以重写所有继承的行为甚至定义新行为。注意,子类和父类并不是实例。
4.4多态
相对多态:任何方法中都可以引用继承层次中高层的方法(无论高层的方法名和当前的方法名是否相同)。之所以说"相对"是因为我们并不会定义想要访问的绝对继承层次,而是使用相对引用"查找上一层"。
多态的另一个方面是,在继承链的不同层次中一个方法名可以被多次定义,当调用方法时会自动选择合适的定义。
使用super表示“超类”,代表当前的父类/祖先类。super另一个功能就是从子类的构造函数中通过super可以直接调用父类的构造函数。在js中,实际上类是属于构造函数的。由于js中父类和子类的关系只存在于两者构造函数对应的.prototype对象中,因此它们的构造函数之间并不存在直接联系。
多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。
4.5 混入
在继承或者实例化时,js的对象机制并不会自动执行复制行为。js中只有对象,并不存在可以被实例化的“类”。可以利用混入的方法来模拟类的复制行为。
4.4.1显示混入
// 非常简单的 mixin() 例子
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
// 只会在不存在的情况下复制
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key]
}
}
return targetObj;
}
var Vehicle = {
engines: 1,
ignition: function() {
console.log('Turning on my engine.');
},
drive: function() {
this.ignition();
console.log('Steering and moving forward!');
}
}
var Car = mixin(Vehicle, {
wheel: 4,
drive: function(){
Vehicle.drive.call(this);
console.log('Rolling on all ' + this.wheel + ' wheels!');
}
})
Vehicle.drive.call(this) 这就是显示多态。js中为了指明调用对象,必须通过名称显示指定Vehicle对象并调用它的drvie()函数。如果直接执行Vehicle.drvie()。函数调用中的this会绑定到Vehicle对象而不是Car对象。
混合复制
// 另一种混入函数,可能又重写风险
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
targetObj[key] = sourceObj[key];
}
return targetObj;
}
var Vehicle = {
// ...
}
// 首先创建一个空对象并把 Vehicle 对内容复制进去
var Car = mixin( Vehicle, {} );
// 然后把新内容复制到 Car 中
mixin({
wheel: 4,
drive: function() {
// ...
}
}, Car)
这两种方法都可以把不重叠对内容从 Vehicle 中显示复制到 Car 中。‘混入’ 这个名字来源与这个过程对另一种解释: Car 中混合了 Vehicle 的内容,所以这叫混合复制。复制操作完成后, Car 和 Vehicle 分离了,向 Car 中添加属性不会影响到 Vehicle,反之亦然。
4.4.2隐式混入
var Something = {
cool: function () {
this.greeting = 'Hello World';
this.count = this.count ? this.count + 1 : 1;
}
}
Something.cool();
Something.greeting; // 'Hello World'
Something.count; // 1
var Another = {
cool: function() {
// 隐式把 Something 混入 Another
Something.cool.call(this);
}
};
Another.cool();
Another.greeting; // 'Hello World'
Another.count; // 1
通过在构造函数调用或者方法调用中使用something.cool.call(this),我们实际上"借用"了函数somethig.cool()并在Another的上下文中调用了(通过this绑定)。
参考资料1:彻底搞懂prototype、_proto_和constructor
参考资料2:阮一峰的es6 class的基本语法