1.面向对象的概念
1.1什么是面向对象:
1 .面向对象是一种思维方法
2.面向对象是一种编程方法
3.面向对象并不只针对某一种编程语言
1.2面向对象和面向过程的区别
1.面向过程过程侧重整个问题的解决步骤,着眼局部或者具体
2.面向对象侧重具体的功能,让某个对象具有这样的功能。更加侧重于整体。
1.3面向对象的实现方式
1.基于类的面向对象
2.基于原型的面向对象
1.4访问对象的属性
访问一个对象的属性,我们可以直接通过 对象.属性名 和 对象[属性名] 来访问。
alert(person.name); // 访问person对象的 name属性值
person.age = 30; //修改person对象的 age 属性
person.eat(); //既然是调用方法(函数) 则一定还要添加 ()来表示方法的调用
alert(person["name"]); //
两种使用方式有一些不同的地方:
- 对象.属性名的方式,只适合知道了属性的名字,可以直接写。比如: person.age 。如果属性名是个变量,则这种方法无效, 对象.变量名 会出现语法错误。
- 对象[属性名],这种方式使用无限制。如果是字符串常量,则应该用""或''引起来,如果是变量,可以直接使用。
person.age = 100; // ok
var n = "age";
person.a = 101; // no ok 语法错误
person["age"] = 102; // ok
person[n] = 103; //ok
1.5给对象添加属性
//给person对象的属性 girlFriend 赋值。在赋值的过程中,首先会判断这个属性在JavaScript中是否存在,如果存在就对这个
//属性重写赋值。如果不存在,就给这个对象添加这个属性,并赋值。
person.girlFrient = "小丽";
//给对象添加方法
person.play = funcion(){
alert("打击high起来");
}
1.6删除对象属性
使用 delete 操作符
// 使用delete操作关键字,删除person对象的属性age
delete person.age;
alert(person.age); //弹出undefined。表示这个属性没有定义
1.7使用for...in遍历对象的属性
for...in 可以遍历对象的所有属性。
// 在用for...in遍历的时候, in前面的变量pn指的是属性的名称。
for (pn in person) {
alert(pn + " " + person[pn]);
}
2.创建对象的方式
2.1使用new Object()创建
<script type="text/javascript">
//使用object创建一个对象 完全等同于 var person = {};
var person = new Object();
//给对象添加属性
person.name = "李四";
//给对象添加方法
person.eat = function () {
alert("好好吃")
}
</script>
2.2使用工厂模式创建
<script type="text/javascript">
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person1 = createPerson("张三", 29, "js开发者");
var person2 = createPerson("李四", 27, "java开发者");
</script>
2.3构造函数创建对象
<script type="text/javascript">
function Person (name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.eat = function () {
alert(this.name + "在吃东西");
}
}
var p1 = new Person("张三", 20, "男");
p1.eat(); //张三在在吃东西
var p1 = new Person("李四", 30, "男");
p1.eat(); //李四在在吃东西
alert(p1 instanceof Person); //
</script>
说明:
- 使用构造函数创建对象,必须使用关键字new ,后面跟着构造函数的名,根据需要传入相应的参数。
- 其实使用 new 构造函数() 的方式创建对象,经历了下面几个步骤。
- 创建出来一个新的对象
- 将构造函数的作用域赋给新对象。意味着这个时候 this就代表了这个新对象。
- 执行构造函数中的代码。 在本例中就是给新对象添加属性,并给属性初始化值。
- 构造函数执行完毕之后,默认返回新对象。 所以外面就可以拿到这个刚刚创建的新对象了。
2.3.1构造函数与普通函数的关系
- 他们都是函数。构造函数也是函数,也可以像普通的函数一样进行调用。 做普通函数调用的时候,因为没有创建新的对象,所以this其实指向了window对象。
function Person(){
this.name = "张三"; // 把name属性添加到了window对象上面
alert(this === window); //如果不作为构造方法调用,则 是true
}
Person(); // 把构造函数当做普通方法调用。这个时候内部的this指向了weindow
alert(window.name); //张三
function Human(){
this.name = "王五";
alert(this instanceof window); // false
alert(this instanceof Human); //true
}
var h = new Human(); //当做构造函数来调用,创建一个对象
alert(h.name);
- 构造函数和普通函数仅仅也仅仅是调用方式的不同。也就是说,随便一个函数你如果用new 的方式去使用,那么他就是一个构造函数。
- 为了区别,如果一个函数想作为构造函数,作为国际惯例,最好把这个构造函数的首字母大写。
3.理解原型
3.1 函数的原型对象
在JavaScript中,我们创建一个函数A(就是声明一个函数), 那么浏览器就会在内存中创建一个对象B,而且每个函数都默认会有一个属性 prototype 指向了这个对象( 即:prototype的属性的值是这个对象 )。这个对象B就是函数A的原型对象,简称函数的原型。这个原型对象B 默认会有一个属性 constructor 指向了这个函数A ( 意思就是说:constructor属性的值是函数A )。
看下面的代码:
<body>
<script type="text/javascript">
/*
声明一个函数,则这个函数默认会有一个属性叫 prototype 。而且浏览器会自动按照一定的规则
创建一个对象,这个对象就是这个函数的原型对象,prototype属性指向这个原型对象。这个原型对象
有一个属性叫constructor 指向了这个函数
注意:原型对象默认只有属性:constructor。其他都是从Object继承而来,暂且不用考虑。
*/
function Person () {
}
</script>
</body>
3.2使用构造函数创建对象
当把一个函数作为构造函数 (理论上任何函数都可以作为构造函数) 使用new创建对象的时候,那么这个对象就会存在一个默认的不可见的属性,来指向了构造函数的原型对象。 这个不可见的属性我们一般用 [[proto]] 来表示,只是这个属性没有办法直接访问到。
看下面的代码:
<body>
<script type="text/javascript">
function Person () {
}
/*
利用构造函数创建一个对象,则这个对象会自动添加一个不可见的属性 [[proto]], 而且这个属性
指向了构造函数的原型对象。
*/
var p1 = new Person();
</script>
</body>
观察下面的示意图:
说明:
- 从上面的图示中可以看到,创建p1对象虽然使用的是Person构造函数,但是对象创建出来之后,这个p1对象其实已经与Person构造函数没有任何关系了,p1对象的[[ proto ]]属性指向的是Person构造函数的原型对象。
- 如果使用new Person()创建多个对象,则多个对象都会同时指向Person构造函数的原型对象。
- 我们可以手动给这个原型对象添加属性和方法,那么p1,p2,p3...这些对象就会共享这些在原型中添加的属性和方法。
- 如果我们访问p1中的一个属性name,如果在p1对象中找到,则直接返回。如果p1对象中没有找到,则直接去p1对象的[[proto]]属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型---原型链。 后面再讲)。
- 如果通过p1对象添加了一个属性name,则对p1对象来说就屏蔽了原型中的属性name。 换句话说:在p1中就没有办法访问到原型的属性name了。
- 通过p1对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值。 p1.name = "李四"; 并不是修改了原型中的值,而是在p1对象中给添加了一个属性name。
看下面的代码:
<body>
<script type="text/javascript">
function Person () {
}
// 可以使用Person.prototype 直接访问到原型对象
//给Person函数的原型对象中添加一个属性 name并且值是 "张三"
Person.prototype.name = "张三";
Person.prototype.age = 20;
var p1 = new Person();
/*
访问p1对象的属性name,虽然在p1对象中我们并没有明确的添加属性name,但是
p1的 [[prototype]] 属性指向的原型中有name属性,所以这个地方可以访问到属性name
就值。
注意:这个时候不能通过p1对象删除name属性,因为只能删除在p1中删除的对象。
*/
alert(p1.name); // 张三
var p2 = new Person();
alert(p2.name); // 张三 都是从原型中找到的,所以一样。
alert(p1.name === p2.name); // true
// 由于不能修改原型中的值,则这种方法就直接在p1中添加了一个新的属性name,然后在p1中无法再访问到
//原型中的属性。
p1.name = "李四";
alert("p1:" + p1.name);
// 由于p2中没有name属性,则对p2来说仍然是访问的原型中的属性。
alert("p2:" + p2.name); // 张三
</script>
</body>
3.3与原型有关的几个属性和方法
3.3.1prototype属性
prototype存在于构造函数中,它指向于构造函数的原型对象。
3.3.2 constructor属性
constructor属性存在于原型对象中,他指向了构造函数
看下面的代码:
<script type="text/javascript">
function Person () {
}
alert(Person.prototype.constructor === Person); // true
var p1 = new Person();
//使用instanceof 操作符可以判断一个对象的类型。
//typeof一般用来获取简单类型和函数。而引用类型一般使用instanceof,因为引用类型用typeof 总是返回objece。
alert(p1 instanceof Person); // true
alert(typeof p1); // object
</script>
3.3.3 hasOwnProperty()方法
hasOwnProperty方法用来判断一个属性是否来自对象本身。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//sex属性是直接在p1属性中添加,所以是true
alert("sex属性是对象本身的:" + p1.hasOwnProperty("sex"));
// name属性是在原型中添加的,所以是false
alert("name属性是对象本身的:" + p1.hasOwnProperty("name"));
// age 属性不存在,所以也是false
alert("age属性是存在于对象本身:" + p1.hasOwnProperty("age"));
</script>
3.3.4 in操作符
in 操作符用来判断一个属性是否存在于这个对象中。但是在查找这个属性时候,先在对象本身中找,如果对象找不到再去原型中找。换句话说,只要对象和原型中有一个地方存在这个属性,就返回true
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
alert("sex" in p1); // 对象本身添加的,所以true
alert("name" in p1); //原型中存在,所以true
alert("age" in p1); //对象和原型中都不存在,所以false
</script>
回到前面的问题,如何判断一个属性是否存在于原型中:
如果一个属性存在,但是没有在对象本身中,则一定存在于原型中。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//定义一个函数去判断原型所在的位置
function propertyLocation(obj, prop){
if(!(prop in obj)){
alert(prop + "属性不存在");
}else if(obj.hasOwnProperty(prop)){
alert(prop + "属性存在于对象中");
}else {
alert(prop + "对象存在于原型中");
}
}
propertyLocation(p1, "age");
propertyLocation(p1, "name");
propertyLocation(p1, "sex");
</script
3.4组合使用原型模型和构造函数模型创建对象
原型模式适合封装方法,构造方法模式适合封装属性,综合两种模式的优点就有了组合模式。
<script type="text/javascript">
//在构造方法内部封装属性
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型对象内封装方法
Person.prototype.eat = function (food) {
alert(this.name + "爱吃" + food);
}
Person.prototype.play = function (playName) {
alert(this.name + "爱玩" + playName);
}
var p1 = new Person("李四", 20);
var p2 = new Person("张三", 30);
p1.eat("苹果");
p2.eat("香蕉");
p1.play("志玲");
p2.play("凤姐");
</script>
3.5动态原型模式创建对象
前面讲到的组合模式,也并非完美无缺,有一点也是感觉不是很完美。把构造方法和原型分开写,总让人感觉不舒服,应该想办法把构造方法和原型封装在一起,所以就有了动态原型模式。
动态原型模式把所有的属性和方法都封装在构造方法中,而仅仅在需要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优点。
看下面的代码:
<script type="text/javascript">
//构造方法内部封装属性
function Person(name, age) {
//每个对象都添加自己的属性
this.name = name;
this.age = age;
/*
判断this.eat这个属性是不是function,如果不是function则证明是第一次创建对象,
则把这个funcion添加到原型中。
如果是function,则代表原型中已经有了这个方法,则不需要再添加。
perfect!完美解决了性能和代码的封装问题。
*/
if(typeof this.eat !== "function"){
Person.prototype.eat = function () {
alert(this.name + " 在吃");
}
}
}
var p1 = new Person("志玲", 40);
p1.eat();
</script>
说明:
- 组合模式和动态原型模式是JavaScript中使用比较多的两种创建对象的方式。
- 建议以后使用动态原型模式。他解决了组合模式的封装不彻底的缺点。