面向对象的艺术,都是艺术,全是艺术,全是爆炸~


面向对象是一种思想,是基于面向过程而言的,就是说面向对象是将功能等通过对象来实现,把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是以功能划分问题,
把属性和方法写在一起,作为一个相互依存的整体——对象,面向对象有三大特征:封装性、继承性、多态性。就像去一家咖啡店,你只需要和服务员说你要什么样的咖啡,然后只需要等服务员
把咖啡拿上来喝掉就可以了,不需要去关注咖啡怎么做,这里服务员就是对象(不是那种对象,是另一种对象),你面向她,拿到你喜欢的咖啡,她也不需要知道你是怎么把咖啡喝完的。顺带
提一下面向过程,面向过程就是根据需要分析出解决问题所需要的步骤,顺序执行,其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构。各模块之间的关系尽可能简单,
在功能上相对独立,每一模块内部均是由顺序、选择和循环三种基本结构组成,其模块化实现的具体方法是使用子程序。程序流程在写程序时就已决定,同样还是刚才的服务员,面向过程就
是你和她说要喝什么咖啡,然后浪漫的一起从调料,磨咖啡豆,煮咖啡等等等,然后你张嘴,喝一口,闭嘴,咽下去。。。

面向对象的神器

之前和一些道友谈论面向对象的过程中得到了一张很厉害的图

img
img

对象

系统对象,诸如Array(),Date()这类系统自带的对象
一般程序员在讨论说没有对象的时候,总有人说new一个就有了,这里我new一个系统自带的数组对象

1
var arr = new Array();

对象new出来之后就可以使用它里头的函数

1
2
arr.push();  
arr.sort();

对象的组成是由肤白+貌美+大长腿。。。错了错了。是由属性和方法构成的

对象的构成

诶。。。由于对象是undefined,所以我构造一个对象出来,并且调用

1
2
3
4
5
var girlFriend=new Girlfriend();    //构造对象
console.log("年龄"+girlFriend.age); //打印对象的年龄
console.log("性别"+girlFriend.gender); //打印对象的性别
girlFriend.can(); //调用对象的can方法
girlFriend.canNot(); //调用对象的canNot方法

console
console

构造的方法很简单,对象的方法我用vue的methods的写法,把Girlfriend.prototype当成vue的methods,因为我觉得相对于它通常的写法略酷一些。

1
2
3
4
5
6
7
8
9
10
11
12
function Girlfriend(){      //对象的构造函数
this.age=18; //对象的属性
this.gender="female";
}
Girlfriend.prototype={ //对象的方法
can(){
console.log("可以拿来面向")
},
canNot(){
console.log("不可以打")
}
}

工厂方式

知道什么是当当当当吗。。。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPerson(name){
//1.原料
var obj = new Object();
//2.加工
obj.name = name;
obj.showName = function(){
console.log( this.name );
};
//3.出场
return obj;
}
var p1 = createPerson('HHardyy');
p1.showName();
var p2 = createPerson('Hardy');
p2.showName();

但是请记住这里的p1.showName()不等于p2.showName(),即使是传入同一个参数,因为p1.showName()==p2.showName(),它是拿双方的地址来对比的,就像java中一样,但是java中可以用xxx1.equals(xxx2)来对比它们的内容是否相等。

原型

去改写对象下面公用的方法或者属性,让公用的方法或者属性在内存中存在一份 ( 可以提高性能 ),原型 就相当于写html页面时,CSS中的class,普通方法相当于CSS中的style,例如我在系统的数组对象添加一个求和方法count

1
2
3
4
5
6
7
8
9
10
11
var arr1 = [1,2,3,4];
var arr2 = [2,2,2,2];
Array.prototype.count = function(){
var result = 0;
for(var i=0;i<this.length;i++){
result += this[i];
}
return result;
};
console.log( arr1.count() ); //10
console.log( arr2.count() ); //8

原形下面不止可以是方法,也可以添加属性

1
Array.prototype.number=5201314

工厂方式之原型

当new去调用一个函数 : 这个时候函数中的this指向创建出来的对象,而且函数的的返回值直接就是this(隐式返回)

1
2
3
4
5
6
7
8
9
function Object(name){
this.name = name;
}
Object.prototype.showName = function(){
console.log( this.name );
};
var p1 = new Object('HHardyy');
var p2 = new Object('小方块');
alert( p1.showName == p2.showName ); //true

在JS源码中,系统对象也是基于原型的程序,所以尽量不要去修改或者添加系统对象下面的方法和属性,不然可能导致部分原本js的部分功能改变

包装对象

基本数据类型都有自己对应的包装对象,如String,Number,Boolean ,基本类型会找到对应的包装对象类型,然后包装对象把所有的属性和方法给了基本类型,然后包装对象消失
如果在String下面添加number类型的数据,会输出undefined

1
2
3
4
5
var str = 'HHardyy';
String.prototype.lastValue = function(){
return this.charAt(this.length-1);
};
alert( str.lastValue() ); //y

原型链

提到面向对象就肯定会有原型链proto,原型链是实例对象与原型之间的连接,它的最外层是Object.prototype,比如我在最外层添加一个love属性,那么这个属性可以在任何构造出来
的对象中调用

1
2
3
4
5
6
function HHardyy(){
this.say="666";
}
Object.prototype.love = "鸡翅";
var h = new HHardyy();
alert(h.love); //鸡翅

原型链手残图
原型链手残图

诶,。。。应该能看懂

hasOwnProperty,constructor,instanceof,toString

1、hasOwnProperty:看是不是对象自身下面的属性

1
console.log( h.hasOwnProperty("say"))

2、in:in运算符和hasOwnProperty不同,只要存在在原型上或者对象上就返回true

1
'HHardyy' in Object;

3、constructor:查看对象的构造函数
4、instanceof:对象与构造函数在原型链上是否有关系

1
console.log(h instanceof Array)

5、toString():系统对象下面自带的 , 自己写的对象都是通过原型链找object下面的

1
console.log( Object.prototype.toString.call(h) == '[object Array]' )

6、Object.keys:ES5的Object.keys方法可以返回对象上的所有可枚举属性(只有对象上的,从原型上继承的没有)

继承

继承的概念:子类不影响父类,子类可以继承父类的一些功能 ( 代码复用 ),继承分成属性继承和方法继承。继承的类型分成拷贝继承,原型继承和类式继承
拷贝继承: 通用型的,有new或无new的时候都可以
类式继承: new构造函数
原型继承: 无new的对象

属性的继承 : 调用父类的构造函数 call

1
2
3
4
5
6
7
8
var obj1 = new Object1('HHardyy');
function Object1(name){ //父类
this.name = name;
}
function Object2(name,sex){ //子类
Object1.call(this,name);
this.sex = sex;
}

方法的继承:for in也就是拷贝继承 (jquery也是采用拷贝继承extend),原理就是遍历Object1之后赋给Object2,也可以封装成一个extend方法,在需要用的时候只需要传入2个对象

1
2
3
4
5
function extend(obj1,obj2){
for(var attr in obj2){
obj1[attr] = obj2[attr];
}
}

写好了这个extend方法,可以用来写自定义事件,比如各种弹框、拖拽什么的,先给对象设置默认参数,然后再做个参数传递,然后如果有用户输入参数,则该参数覆盖本身设置的默认参数,达到自定义效果。
codepen上做的一个小demo

原型继承

原型继承的几种方式
一、原型链继承

1
2
3
4
5
function Object1(){ this.name="Object1"; }
function Object2(){
this.age="2"; //Object2继承了Object1,通过原型,形成_proto_
}
Object2.prototype=new Object1();

二、构造函数继承(对象冒充继承
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题

1
2
3
4
5
6
7
8
9
10
11
12
function Object1(age){
this.name=['zhangsan','lisi','wangwu']
this.age=age;
}
function Object2(age){
Object1.call(this,age); //对象冒充,给超类型传参
}
var o2 = new Object2(20);
console.log(o2.age);//20
console.log(o2.name);//['zhangsan','lisi','wangwu']
o2.name.push('HHardyy'); //添加的新数据,只给 o2
console.log(o2.name)//['zhangsan','lisi','wangwu','HHardyy']

三、组合继承(原型链继承+构造函数继承)
原型链+借用构造函数=组合继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Object1(age) {
this.name = ['zhangsan','lisi','wangwu']
this.age = age;
}
Object1.prototype.run = function () {
return this.name + this.age;
};
function Object2(age) {
Object1.call(this, age); //对象冒充
}
Object2.prototype = new Object1(); //原型链继承
var o2 = new Object2(100);
console.log(o2.run());

四、原型式继承
借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Object1(o) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var box = { //字面量对象
name : 'zhangsan',
arr : ['lisi','wangwu','zhaoliu']
};
var box1 = Object1(box); //传递
console.log(box1.name);
box1.name = 'Jack';
console.log(box1.name);
console.log(box1.arr);
box1.arr.push('parent');
console.log(box1.arr);
var box2 = Object1(box); //传递
console.log(box2.name);
console.log(box2.arr); //引用类型共享了

五、寄生组合式继承
寄生组合式继承解决了两次调用的问题,组合式继承就会有两次调用的情况

1
2
3
4
5
6
7
8
9
10
11
function object(o) {
function F() {}
F.prototype = o;
return new F();
}

function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}

原型式继承其实就是说构造函数和子构造函数,或者说类和子类之间(js中不存在类),唯一的继承传递方式是通过原型,而不是像其他语言直接通过extends(ES6的语法糖出现了extends)
,所以需要手写prototype。

1
2
3
4
5
6
7
8
9
var Object1 = { name : 'HHardyy' };
var Object2 = cloneObj(Object1);
console.log(Object2.name);

function cloneObj(obj){
var F = function(){};
F.prototype = obj;
return new F();
}

类式继承

在js中没有类的概念,所以将js中的构造函数当成类,要做属性和方法继承的时候,要分开继承

1
2
3
4
5
6
7
8
9
10
11
// 父类 
function Object1() { this.name = 'HHardyy'; }
Object1.prototype.showName = function() {
alert( this.name );
};
// 子类
function Object2() {};
// 将父类创建出来的对象赋值给子类的原型,就是类式继承
Object2.prototype = new Object1();
var o2 = new Object2();
o2.showName()//HHardyy

上面这就是一句话继承,这时候把o2.constructor打印出来,发现其实是有问题的,它把Object1给打印了出来

Object1
Object1

原因是直接把newObject1()赋值给Object2.prototype,它就会把他原先自动生产的还有我们自己添加的原型下面的属性都覆盖掉了,所以把construtor指向给修改了,因此还需要修正con
structor指向Object2.prototype.constructor=Object2,其实这么改了以后虽然表面正常了,但还是有问题的,假如Object1的name是个数组[1,2]的话,声明出来的对象.name也不是
同一个对象,这时候可以构建一个空对象H()继承Object1的prototype,然后Object2再继承H()的prototype,就可以解决问题了。

点表示法与中括号表示法

一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法,不过,在javascript也可以使用方括号表示法来访问对象的属性, 在使用方括号语法时,应该将要访问的属性以字符串的形式放在括号中,就像这样

1
2
alert(person["name"]); //hhardyy
alert(person.name); //hhardyy

从功能上看,这两种访问对象属性的方法没有任何区别。但是方括号语法的主要优点是可以通过变量来访问属性,就像这样

1
2
var prototypeName="name";
alert(person[prototypeName]);//hhardyy

如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或者保留字,也可以使用方括号表示法。

1
person["first name"]="hhardyy"

由于first name中包含一个空格,所以不能使用点表示法来访问它。然而属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们。通常,除非必须使用变量来访问属性,否则建议使用点表示法