1什么是对象?
Everything is objec(万物皆对象),对象到底是什么,我们可以从两个层次来理解。
1.1对象是单个事物的抽象
一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
1.2 对象是一个容器,封装了属性(property)和方法(method)
属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)
在实际开发中,对象是一个抽象的概念,可以将其简单理解为 : 数据集或功能集。
2.什么是面向对象?
ECMAScript 有两种开发模式:1.面向过程(pop
),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript
没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
2.1面向过程编程POP(Process-oriented progamming)
面向过程就是分析解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个一次调用就可以了。
举个栗子:将大象装进冰箱(需要几步),面向过程做法
* 打开冰箱门
* 大象装进去
* 关山冰箱门
面向过程,就是按照我们分析好的步骤,按照步骤解决问题。
2.2面向对象编程OOP(Object oriented progamming)
面向对象是把事务分解成一个一个对象,然后对象之间分工与合作。
举个栗子:将大象装进冰箱(需要几步),面向对象做法。
先找出对象,并写出这些对象的功能:
* 打开冰箱门
*进去
* 冰箱对象
*打开
*关闭
* 使用大象和冰箱的功能
面向对象,是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型项目。
面向对象的特性:
* 封装性
* 继承性
* 多态性
3. 面向过程和面向对象比较
面向过程:
优点:性能比面向对象高,适合跟硬件联系很紧密的东西,列如单片机就采用的面向过程编程。
缺点:没有面向对象易维护、易复用、易扩展。
面对对象:
优点:易维护、易复用、易扩展。由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使用系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低。
用面向过程写出来的方法写出来程序使一份炒面,而用面向对象写出来程序是一份蛋炒面。
4.概述
在典型的OOP语言中(如Java、PHP),都存在类的概念,类就对象模板,对象就是类的实例,但是在es6之前,js中并没有引入类的概念。
ES6,全程ECMAScript6.0,2015年.6月份发布,但是目前浏览器的js是ES5版本的,多数高版本浏览器也支持ES6,不过只实现了ES6的部分特性和功能。
在ES6的之前,对象不是基于类创建,而是用一种称为构建函数的特殊函数来定义对象和它门特征:
创建对象可以通过以下方式:
* 字面量创建对象
* New object()
* 自定义构造函数
5. 创建方法
5.2.1 字面量创建对象
<script type="text/javascript">
// 1.字面量创建对象;
var obj={
name:"栗伟林",
age:18,
sex:"男",
fn:function(a,b){
alert("我真帅")
return a+b
}
}
console.log(obj.name)// 第1种:访问属性
console.log(obj["age"])// 第2种:访问属性
console.log(obj.fn(1,2))// 访问方法 ,正常传递参数
</script>
5.2.2 New object()
<body>
<button id="btn" onclick="btn()">点点</button>
</body>
<script type="text/javascript">
var obj=new Object() //创建一个对象
obj.name="胖淇";
obj.age=89;
obj.sex="不知道";
obj.fn=function(){
return this.name+ this.age+'运行中....'
}
</script>
上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box 对象本身。这种是JavaScript
创建对象最基本的方法,但有个缺点,想创建多个类似的对象,就会产生大量的代码。
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
5.2.3 工厂方法
function num(name,age){
//1. 原料
var obj=new Object(); //创建对象
//2.加工
obj.name=name;
obj.age=age;
obj.sun=function(){
alert("我的名字"+this.name)
}
//3.出厂
return obj;
}
var p1=num("栗伟林",18)
var p2=num("张延涛",18)
console.log(p1)
console.log(p2)
工厂模式:
原料
加工
出厂
[注]:凡是满足上诉三个步骤创建对象的函数,我们把它叫做工厂模式。
工厂模式解决了重复实例化的问题,但是它有许多问题,创建不同对象其中属性和方法都会重复建立,消耗内存;还有函数识别问题等等。
5.2.4 构造函数方法
//构造函数:就是把对象中的一些公共属性和方法抽取出来,封装到函数里面。
function fun(name,age){
// 在这里的this指向我们对象的调用者
this.fn=name;
this.fn1=age;
this.fn2=function(){
alert("我的名字叫"+this.fn+"我今年"+this.fn1+'')
}
}
var zxy=new fun("张学友",78,"男","吻别")
var ldh=new fun("刘德华",78,"男","冰雨")
console.log(gfc.fn2())
构造函数
是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总是与new一起使用,我们可以把对象中的一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在js中,使用构造函数是要注意以下两点:
* 构造函数用于创造某一类对象,其首字母大写。
* 构造函数要和new一起使用才有意义。
New在执行时会做四件事情:
* 内存中创建一个新的对象。
* 让this指向这个新的对象。
* 执行构造函数里面的代码,给这个新对象添加属性和方法。
* 返回这个新对象(所有构造函数里面不需要return)
构造函数中的属性和方法我们称为成员,成员可以添加。
function fun(name,age){
this.fn=name;
this.fn1=age;
this.fn2=function(){
alert("我的名字叫"+this.fn+"我今年"+this.fn1+'')
}
}
var zxy=new fun("张学友",78,"男","吻别")
var ldh=new fun("刘德华",78,"男","冰雨")
console.log(gfc.fn2())
zxy.fn2()
console.log(sun.fn)
* 实例成员就是构造函数内部通过this添加的成员,fn、fn1、fn2就是实例成员。
* 不能通过构造函数来访问实例成员。
* 3.静态成员在构造函数添加的成员,sex 就是静态成员。
4.静态成员只能通过构造函数来访问。
[注]:
1)构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。
2)this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
这种方法解决了函数识别问题,但消耗内存问题没有解决。同时又带来了一个新的问题,全局中的this 在对象调用的时候是sun
本身,而当作普通函数调用的时候,this 又代表window。即this作用域的问题。
5.2.5 构造函数原型(混合方式构造对象)
构造函数通过原型分配的函数是所有对象所共享的。
js规定每个构造函数都有一个prototype属性,指向一个对象,注意prototype就是一个对象,这个对象的所有方法和属性,都会被构造函数拥有。
我们可以把哪些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
问1:原型是什么?
答:一个对象,我们也称为prototype对象
问2:原型的作用?
答:共享方法。
function Box(){} //声明一个构造函数
Box.prototype.name="Lee" //在原型里添加属性
Box.prototype.age=20
Box.prototype.run=function(){ // 在原型里添加方法
return this.name+this.age+'运行中...';
}
构造函数的声明方式和原型模式的声明方式存储情况如下:
所以,它解决了消耗内存问题。当然它也可以解决this作用域等问题。
我们经常把属性(一些在实例化对象时属性值改变的),定义在构造函数内;把公用的方法添加在原型上面,也就是混合方式构造对象(构造方法+原型方式)。
5.2.6 constructor 构造函数
1.对
象原型__proto__和构造函数原型对象prototype都有一个属性,叫做constructor,称之为构造函数,主要记录该对象引用了哪个构造函数,可以让原型对象重新指向原来的构造函数,这就是为什么在实例对象中传递参数时,构造函数本身不需要写return返回结果的原因。
function Star(name,name){
this.name=name;
this.name=name;
}
Star.prototype.uesrsing=function(){
console.log("prototype name")
}
var lxy=new Star();
console.log(lxy.__proto__)
console.log(Star.prototype)
为了更清晰的看到是否为构造函数本身:
console.log(lxy.__proto__.constructor)
console.log(Star.prototype.constructor)
构造函数原型对象中可以写多个方法
function Star(name,name){
this.name=name;
this.name=name;
}
Star.prototype={
constructor:Star,
uesrsing:function(){
console.log("uesrsing")
},
uesrmove:function(){
console.log("uesrmove")
}
}
var lxy=new Star();
注意以上代码,我在Star.prototype原型对象中虽然写了多个方法,但同时也添加了另一段代码constructor:Star,这是为了手动利用constructor指回原来的构造函数,如果不写此代码。则原来的构造函数就会被替代,如下代码和打印图。
console.log(lxy.__proto__.constructor)
console.log(Star.prototype.constructor)
如果添加了该代码,再次打印
console.log(lxy.__proto__.constructor)
console.log(Star.prototype.constructor)
再往上一层打印会更清晰
console.log(lxy.__proto__)
console.log(Star.prototype)
同时,就可以通过实例对象去调用构造函数的方法
lxy.uesrsing()
lxy.uesrmove()
构造函数、实例、原型对象三者之间的关系
5.2.7 JavaScript成员查找机制
对象成员查找规则
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的
prototype 原型对象)。
③ 如果还没有就查找原型对象的原型(Object的原型对象)。
④ 依此类推一直找到 Object 为止(null)。
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
栗1:
function Star(name,age){
this.name=name;
this.age=age;
}
Star.prototype.sing=function(){
console.log("我会唱歌");
};
Star.prototype.sex='女';
var ldh=new Star();
ldh.sex='男';
console.log(ldh.sex)// 男
原型链:
分析:
1. 给ldh实例对象添加一个名叫sex的属性,并给它赋值为‘男’。
2. 给Star原型对象也添加一个名叫sex的属性,并给它赋值为‘女’。
3. 输出ldh实例对象的sex属性值,结果为‘男’。
栗2:
function Star(name,age){
this.name=name;
this.age=age;
}
Star.prototype.sing=function(){
console.log("我会唱歌");
};
Star.prototype.sex='女';
Object.prototype.sex='男'
var ldh=new Star();
ldh.sex='男';
console.log(ldh.sex)// 女
分析:
还是刚才那个例子。
给Star原型对象添加一个名叫sex的属性,并给它赋值为‘女’。给Object原型对象添加一个名叫sex的属性,并给它赋值为‘男’。输出ldh实例对象的sex属性值,结果为‘女’。
5.2.8 继承
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript
只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
在JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。
1.call+遍历
属性使用对象冒充(call)(实质上是改变了this指针的指向)继承基类,方法用遍历基类原型。
function A(){
this.abc=12;
};
A.prototype.show=function(){
alert(this.abc)
};
// 继承A
function B(){
// 继承属性:this->new B()
A.call(this) //有参数可以传参数A.call(this,name,age)
};
//继承方法:B.prototype=A.prototype
for(var i in A.prototype){
B.prototype[i]=A.prototype[i];
};
//添加自己的方法
B.prototype.fn=function(){
alert('abc')
};
var objB=new B();
var objA=new A();
objB.show()
可以实现多继承。
2.寄生组合继承
主要是Desk.prototype = new Box(); Desk 继承了Box,通过原型,形成链条。主要通过临时中转函数和寄生函数实现。
临时中转函数:基于已有的对象创建新对象,同时还不必因此创建自定义类型
寄生函数:目的是为了封装创建对象的过程。
// 临时中转函数
function obj(o){//o表示将要传递进入的一个对象
function F(){};//F构造是一个临时新建的对象,用来存储传递过来的对象
F.prototype=o;//将o对象实例赋值给F构造的原型对象
return new F();//最后返回这个得到传递过来对象的对象实例
};
//寄生函数
function create(box,desk){
var f=obj(box.prototype);
f.constructor=desk; //调整原型构造指针
desk.prototype=f;
};
function Box(name){
this.name=name;
this.arr=['apple','pear','orange'];
};
Box.prototype.run=function(){
return this.name
};
function desk(name,age){
Box.call(this,name);
this.age=age;
}
//通过寄生组合继承实现继承
create(Box,desk)
var desk=new desk('lee',100);
desk.arr.push('peach');
alert(desk,arr);
alert(desk,run());
临时中转函数和寄生函数主要做的工作流程:
临时中转函数:返回的是基类的实例对象函数。
寄生函数:将返回的基类的实例对象函数的constructor指向派生类,派生类的prototype指向基类的实例对象函数(是一个函数原型),从而实现继承。