面向对象不是一门新技术,而是一门解决问题的新思路。
面向对象是相对于面向过程的一种解决问题新思路,在知道面向对象之前亲我们需要先明白什么是面向过程。
面向过程是解决某一件事情时,按照完成某件事所需要的步骤来一步一步解决问题过程,当每一步都完成时,整件事就已经完成了。
面向对象则是站在对象的角度思考问题,我有哪些特征和行为。也可以说是让对象以我们人类认知世界和思考的方式来构造对象。例如:我们刚出生时,在认识一个新事物时,我们将以自身的五官和感知,将这个事物抽象成一个概念型模型并定义一个概念名称。当某些事物符合这个模型的固有特征时,我们就会将它称为这个概念名称。
而这个概念名称我们称为类,而对象是类的实例化,类是对象的抽象化。万物皆对象,我们却将具有相同特征的对象称为一个类。
面向对象的语言:1.封装 2.继承 3.多态 (4.抽象 但是并没有得到大众的认可);面向对象的语言也是一个类。只要一门语言满足这三个特征,这门语言就是面向对象的语言这一个类的实例化对象。
j s得到对象的方法有三种:1. new Object; 2. var 变量 ={} 3. 通过构造函数得到。
通过第一和第二种创建的对象,一旦对象较多,会造成代码的冗余。
通过工厂模式得到的创建对象,虽然解决了代码冗余的问题。但是出现一个新的问题。我们的对象应该是有类的,而工厂模式得到的对象只是对象这一类。
当工厂模式无法满足我们的需求时,j s给出了一个新的解决问题的方法就是构造函数。
p s:判断类别的方法: instanceof 和typeof来判断。
构造的函数首字母应该大写,这是j s 中的潜规则。
构造函数的方法来创建对象:
js在ES6之前是是没有办法来定义一个类的。所以我们使用构造函数来模拟类的概念。
在说构造函数时,我们需要来知道我们在操作这些代码时计算机内存中发生了什么。
我们在定义一个变量时,栈内存将我们定义的变量以二进制的方式存储了起来,而我们变量的内容将会放进堆内存中或数据区内存中。在变量内放着的只是指向我们定义内容的一个以16进制存储的地址。
基于构造函数的创建对象的方式和基于工厂的方式类似
最大的区别就是函数的名称就是类的名称,按照面向对象语句的
function Person(name,age) {
this.name = name;
this.age = age;
this.say = function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
}
var p1 = new Person("马天鹏",15);
p1.say();
使用构造函数的好处就是可以使用instanceof来判断这个对象的类型了
alert(p1 instanceof Person)
基于构造函数的定义的方式最大的好处除了对象重复使用外,就是让我们还可以判断它
的类型。
此时我们发现基于构造函数的定义对象的方式看似已经很完美了,我们需要的问题它都
可以解决了,但是如果我们仔细的分析这段代码的话,就会发现这样的代码是存在问题的,
为什么呢?
我们通过代码分析得知:say 方法在每个对象创建后都存在了一个方法拷贝(但是我们
发现代码在只有调用时,say 方法才会在堆中创建,基于闭包的原理),这样就增加了内存的
消耗了,如果在对象中有大量的方法时,内存的消耗就会高,这样不行了。
解决这个问题的就是我们可以把这个方法放到全局函数,这样就所有的对象指向了一个
方法。
解决方案就是将方法全部放在外面,成为全局函数
function Person(name,age) {
this.name = name;
this.age = age;
可以将方法成为全局函数。
this.say = say;
}
function say() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
var p1 = new Person("马天鹏",15);
p1.say();
var p2 = new Person("老万",30);
p2.say();
但是这样写的话,会带来另一个问题,就是方法一点定义为全局函数,那么 window 对
象就可以调用,这样就破坏了对象的封装性。而且如果有大量的方法,这样写导致整体代码
充斥着大量的全局函数,这样将不利于开发。所以我们急需一种可以完美的解决上述问题的
方案,javascript 给我们提供了一种解决这些问题的方案,就是基于原型的对象创建方案。
封装--Javascript 的原型(prototype)
Prototype,原型的初览
以上方式在创建对象都不太理想,所以我们可以使用 prototype 的方式来完成对象的创
建。
如何使用原型创建对象呢?首先写段代码让大家看看:
//定义了一个对象
function Person() {
}
//使用原型来给对象赋值
//这样就讲一个对象的属性和方法放在了该对象的原型中
//外界是无法访问到这样数据的
Person.prototype.name = "小妖的八戒";
Person.prototype.age = 18;
Person.prototype.say = function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
var p1 = new Person();
p1.say();//正常访问了
say();//报错了
这样我们发现 window 就无法访问到 say 方法了,此时 say 方法只属于 Person 对象独有
的方法。很好的解决了封装破坏的情况。
什么是原型
上面我们看了基于 prototype 创建对象的方式很好的解决了我们前面遇到的一系列问题,
那么到底什么是原型,原型又是如何解决如上的问题的呢?我们下面来研究研究。
原型是 js 中非常特殊一个对象,当一个函数创建之后,会随之就产生一个原型对象,当
通过这个函数的构造函数创建了一个具体的对象之后,在这个具体的对象中就会有一个属性
指向原型。这就是原型的概念。
鉴于原型的概念比较难以理解,我们就以上面的代码为例,画图为大家讲解:
//第一种状态
//定义了一个对象
function Person() {
}
//第二种状态,这样赋值就会赋在原型对象中
//使用原型来给对象赋值
//这样就讲一个对象的属性和方法放在了该对象的原型中
//外界是无法访问到这样数据的
Person.prototype.name = "小妖的八戒";
Person.prototype.age = 18;
Person.prototype.say = function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
//第三种状态
var p1 = new Person();
//此时调用的是原型中的,因为自己中没有这些属性和方法
p1.say();//正常访问了
// say();//报错了
//可以通过如下的方式检测p1是不是指向Person的原型对象
// alert(Person.prototype.isPrototypeOf(p1))
var p2 = new Person();
p2.name = "张三";
p2.age = 20;
p2.say();
常见的原型检测方式
可以通过如下的方式检测p1是不是指向Person的原型对象
alert(Person.prototype.isPrototypeOf(p1))
//检测p1的构造器是否指向Person对象
alert(p1.constructor == Person)
//检测某个属性是不是自己内存中的
alert(p1.hasOwnProperty("name"));
alert(p2.hasOwnProperty("name"))
同样我们可以使用 delete 语句来删除我们赋予对象的自己属性(注意:原型中的是无法
删除的),如
//可以使用delete语句删除对象中自己的属性,那么就会找到原型中的值
delete p2.name;
p2.say();
alert(p2.hasOwnProperty("name"));
检测在某个对象自己或者对应的原型中是否存在某个属性。
alert("name" in p1);//true
delete p2.name;//虽然删除了自己的name属性,但是原型中有
alert("name" in p2);//true
//原型和自己中都没有sex属性
alert("sex" in p1);//false
那么问题来了?如果检测只在原型中,不在自己中的属性呢?(提问)
//我们可以自己写代码来测试属性不在自己,在原型中
function hasPrototypeProperty(obj,prop) {
if (!obj.hasOwnProperty(prop)) {
if (prop in obj) {
return true;
}
}
return false;
}
alert(hasPrototypeProperty(p1,"name"));
原型重写
在上面的写法中,我们已经解决了大量的问题,使用原型。但是如果我们的对象中存在
大量的属性或者方法的时候,使用上面的方式,感觉要写大量的【对象.prototype.属性名 】
这样的代码,感觉不是很好,那么我们可以使用 json 的方式来写:
function Person() {
}
Person.prototype = {
name : "马帅哥",
age : 18,
say : function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
}
var p1 = new Person();
p1.say()
var p2 = new Person();
p2.name = "张三";
p2.age = 20;
p2.say();
但是这种写法,我们是将该对象的原型覆盖(注意:这两种写法不一样的,第一种是扩
充,第二种是覆盖),就会出现如下的问题:
function Person() {
}
Person.prototype = {
constructor:Person,//手动指向Person
name : "马帅哥",
age : 18,
say : function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
}
var p1 = new Person();
p1.say()
var p2 = new Person();
p2.name = "张三";
p2.age = 20;
p2.say();
//此时p1的构造器不在指向Person,而是指向了Object
//因为我们覆盖了Person的原型,所以如果constructor比较重要的话,
//我们可以手动指向
alert(p1.constructor == Person)
此时就没有问题了。但是原型重写会给我们带来一些非常有趣的现象。下面我们来研究
研究。
function Person() {
}
var p1 = new Person();
Person.prototype.sayHello = function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
// p1.sayHello();
Person.prototype = {
constructor:Person,//手动指向Person
name : "马帅哥",
age : 18,
say : function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
}
var p2 = new Person();
p2.name = "张三";
p2.age = 20;
p1.sayHello();//此时找不到name和age,但是代码正确
p2.say();//正确
p1.say();//错误,因为原型重写了
为了解决原型所带来的问题,需要通过组合构造函数和原型来实现对象的创建将:属性
在构造函数中定义,将方法在原型中定义。这种有效集合了两者的优点,是目前最为常用的
一种方式。
//属性在构造方法定义
function Person(name,age,friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
/**
* 此时所有的属性都是保存在自己的内存中
* 方法都是定义在prototype(原型)中
*/
//方法在原型中定义
Person.prototype = {
constructor:Person,
say : function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");
}
};
var p1 = new Person("马帅哥",18,["老宗","老万"]);
p1.friends.push("老马");
alert(p1.friends);
var p2 = new Person("曾小贤",20,["小丽","小美"]);
alert(p2.friends);
写了那么多的代码,目的就是为了这种定义 javascript 对象的方式,所以我们最终的定
义 javascript 对象的方案就是基于组合的方式定义,将属性的定义放在构造函数中,将方法
的定义放在原型中。
终极方案—基于动态原型的对象定义(选学)
上面的方案在我们看来已经相当的完美了,但是因为一些面向对象的程序员(如:java、
c#)等开发人员他们认为将方法放在外面不像面向对象的写法,所以提供了另一种写法,在
这里说说,经供参考:
//属性在构造方法定义
function Person(name,age,friends) {
this.name = name;
this.age = age;
this.friends = friends;
//判断不存在的时候,如果存在就不在写,内存减少消耗
if (!Person.prototype.say) {
Person.prototype.say = function() {
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");}}}
var p1 = new Person("马帅哥",18,["老宗","老万"]);
p1.friends.push("老马");
alert(p1.friends);
var p2 = new Person("曾小贤",20,["小丽","小美"]);
alert(p2.friends);
构造出的函数都有一个一个属性:Prototype:原型 对象.prototype具有constructor属性
通过json覆盖创建的对象没有constructor属性。