本文将循序渐进的介绍js面向对象的基础知识。
面向对象编程的好处
什么是面向对象呢?
一对象
面向对象编程 (OOP : Object Oriented Programming)。
它从90年代开始成为软件开发的主流思想。
它的概念就是将世间万物视为对象,并将其分类。
同一类的对象有相同的属性和行为。
比如,一个人,它包括性别、年龄,颜值等属性。还可以有些行为,比如:微笑、说话、吃饭、写代码等。
面向对象具有4个主要特性:
- 唯一 :每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变。
- 抽象:将具有一致的属性和行为的对象抽象成类。反映了与应用有关的重要性质,而忽略其他一些无关内容。
- 继承:继承子类自动共享父类数据结构和方法的机制,这是类之间的一种关系,继承性是面向对象思想最重要的一点。
- 多态:多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。
面向对象与面向过程到底有什么区别?
- 可维护性强
写代码简单,维护起来却不容易。
经过深思熟虑的抽象,松散的代码可以被重构为“有胳膊有腿”的个体,
属于它的变量被定义成属性,相关的函数片段被定义成它的行为(方法),看到这样清晰的结构,对这些函数片段和散落的变量就会一目了然其作用。 - 可复用性强
说实话,在实际工作前,我读过面向对象的知识,但是从未应用过。
直到工作中遇到一个特定的需求不得已为之,之后就爱上了这种写法。
如果让你做一个轮播图,可能100行之内就可以搞定。但是,如果很多页面都需要你这个轮播图,但是有些许不同,比如轮播时间间隔,轮播方向,切换图片后的行为(轮播动画结束时需要执行的链接地址更新,统计等等等回调函数)。
或者,一个页面有两处轮播,而且两处的class,行为不同,你会将那段代码改吧改吧复制成两个吗?
如果是面向对象,就可以将方法,属性抽象,初始化不同的实例,实例间求同存异,各自都有自己的状态,互不影响。
如何用JS写面向对象?
创建对象的最简单方式就是创建一个Object实例,然后为它添加属性和方法。
- 普通模式
var person = new Object();
person.name = ‘Taylor Swift’;
person.job = ‘singer’;
person.sayName =function(){
alert(this.name);
}
这样,一个有名字,有工作,会说话的人就诞生了。但是如果批量生产多个对象实例呢?
- 工厂模式:
var create = function(name, job){
var o= new Object();
o.name = name;
o.job = job;
o.sayName =function(){
alert(this.name);
}
return o;
}
var person = create('Taylor Swift','singer');
这样,我们就可以将实例间的区别以参数的方式传递给工厂函数,返回给我们对象实例。但是,实例却不知道“爹”是谁?
- 构造函数模式:
var Person = function(name, job){
this.name = name;
this.job = job;
this.sayName =function(){
alert(this.name);
}
}
var person1 = new Person('Taylor Swift','singer');
console.log(person1 instanceof Person);
利用构造模式的instanceof方法我们就可以做“亲自鉴定”了。但是注意看,实例化几个对象,就会有多少sayName()方法,但是sayName()方法都相同,没必要浪费内存啊。这样写可不可以?
var Person = function(name, job){
this.name = name;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
把sayName单独提取出来就可以了,但是看着功能构造器Person没什么关系,很松散。
这时候就要引入一个js中原型对象的概念。
- 原型对象:
无论什么时候,只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
就拿前面的例子来说,Person.prototype.consructor 指向 Person.
用原型对象的方式写:
var Person = function(){};
Person.prototype.name = 'Taylor Swift';
Person.prototype.job = 'singer';
Person.prototype.sayName = function(){
alert(this.name);
};
我们看看原型方式的内存使用图:
内存使用图.png
但是原型模式还不够完美,它省略了为构造函数传递初始化参数的环节,结果所有实例在默认情况下都获得相同的属性值。
另外,因为的共享机制这个优点,存在一个潜在的问题。
如果共享的对象是引用类型(比如Array),实例之间可能会相互影响。比如下面的例子:
var Person = function(){};
Person.prototype.name = 'Taylor Swift';
Person.prototype.job = 'singer';
Person.prototype.friends = ['Ivy', 'Cathy'];
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person2.friends.push('jordan');
console.log(person1.friends,person2.friends);
那么,接下来,我们最优的模式就要亮相了:
var Person = function(){
this.friends = ['Ivy', 'Cathy'];
this.name = 'Taylor Swift';
this.job = 'singer';
};
Person.prototype.sayName = function(){
alert(this.name);
};
ES6的写法会更简单:
class Person {
constructor(name) {
this.name = name;
this.job = job;
}
sayName() {
console.log(this.name);
}
}
虽然目前es6兼容性还不够好,但是如果你想用这种写法的话,可以用编译工具,开发环境的文件是es6,生产环境运行的是编译后的es5写法。