2.03.04 面向对象编程 构造函数
1.面向对象 与 面向过程 的 理解
-
我们可以将面向过程与面向对象看做手工烤面包和使用烤箱烤面包的差别:手工烤面包时,需要制作加热线圈,将线圈连接到电源并通电,手持面包放在离线圈很近的地方烤,然后耐心等待,等烤熟后再将线圈断电;使用烤箱时,只需将面包放入烤箱再按下按钮即可。
-
面向过程:第一种方式是过程型的。我们烤面包需要制作线圈,电源等零件,并把这些零件零零散散地放在环境中
-
面向对象:而第二种方式是面向对象的。你有一个烤箱对象,让你能够轻松地放入并烤好面包。那么你只需要去做一个烤箱,你需要烤面包时,你就拿出烤箱用一下。这个烤箱以打包好加热过程中需要用到的好线圈,电源等零件的方式暴露在环境中。当然这些在烤箱里面线圈,电源也是需要你去制造的,你制造好这些零件,并把它封装在烤箱中。
-
-
抽象 与 具体 的 理解
- 根据上面这个情景,我们知道我们把线圈、电源等零件制造好放在一个铁盒子中,但这个铁盒子不一定是烤箱啊,可以是加热,烘干的一些类似的机器
- 抽象:一个装着线圈、电源等的铁盒子 就是 抽象的
- 具体:我们把这个铁盒子拿来做什么,做成烤箱,做成烘干机等。 这些 烤箱 烘干机等 就是 具体
2.创建对象的思考过程
- 思考第一步
//到目前为止,我们学习的都是使用对象字面量来创建对象的。使用对象字面量。创建对象时,你逐个地指定其属性,假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性。
var Cat = {
name : '',
color : ''
}
//现在,我们需要根据这个原型对象的规格(schema),生成两个实例对象。
var cat1 = {}; // 创建一个空对象
cat1.name = "大毛"; // 按照原型对象的属性赋值
cat1.color = "黄色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
- 思考第二步
//按约定创建对象
/*我们可以写一个函数,如果有一种让他像模具一样的对象创建方式,可帮助创建基本结构相同的对象。这样,创建的所有对象都将看起来一样,它们包含在一个地方定义的所有属性和方法。解决代码重复的问题。*/
function Cat(name,color) {
return {
name:name,
color:color
}
}
// 然后生成实例对象,就等于是在调用函数:
var cat1 = Cat("大毛","黄色");
var cat2 = Cat("二毛","黑色");
//思考:这种方法的问题依然是,cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例。
- 思考第三步
- 构造函数模式
- 介绍:为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。对象构造函数(简称为构造函数)让你能够更好地创建对象。构造函数犹如一个小型工厂,能够创建无数类似的对象。定义后,每当需要创建新对象时都可调用它。
- 构造函数模式的用法:
/*要明白构造函数的工作原理,最佳方式是创建一个。我们以前面的小猫对象例,编写一个构造函数,让我们能够根据需要创建任意数量的小猫对象。若小猫对象,它包含属性name、color和weight。*/
/*先定义一个构造函数,再使用它来创建对象。所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。再对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
*/
//比如,猫的原型对象现在可以这样写:
// 注意到我们给构造函数命名时,采用了首字母大写的方式。并非必须这样做,但人人都将此视为一种约定加以遵守。
function Cat(name,color,weight){ // 构造函数与常规函数看起来没什么两样。这个函数的形参对应于要给每个小猫对象提供的属性。
this.name=name; // 我们将每个形参都赋给了关键字this的属性
this.color=color;
this.weight = weight
}
// 我们现在就可以生成实例对象了。
var cat1 = new Cat("大毛","黄色",9);
var cat2 = new Cat("二毛","黑色",12);
alert(cat1.name); // 大毛
alert(cat1.color); // 黄色
3.构造函数的工作原理
- 构造函数与普通函数无异,但因为构造函数需要结合new来创建对象,所以称他为构造函数
- 弄清楚 new 与 this 的作用
//1.正常情况
/*
function Cat(name,color,weight){
this.name=name;
this.color=color;
this.weight = weight;
}
var cat1 = new Cat("大毛","黄色",9);
console.log(cat1);
*/
//2.没有this 的情况
/*
function Cat(name,color,weight){
name1=name;
color1=color;
weight1 = weight;
}
var cat1 = new Cat("大毛","黄色",9);
console.log(cat1); // {} 一个空对象 //明白new 可以创建新对象,并且可以调用Cat这个函数
console.log(cat1.name); //undefined
console.log(name1); // 大毛
console.log(color1); //黄色
*/
// 没有this的情况下,name1 color1 weight1 都是全局变量(是window下的属性)
//明白this用来指向 新对象
//new 在这有个给返回值的作用 使得函数本没有return 也行
//3.没有new的情况
/*
function Cat(name,color,weight){
this.name=name;
this.color=color;
this.weight = weight;
}
var cat1 = Cat("大毛","黄色",9);
console.log(cat1); //undefined 函数没有返回值默认返回undefined
console.log(name); //大毛 */
//明白new 用来创建新对象
//new+this 使得this的指向发生改变 使得this指向新的对象
- 构造函数的工作原理的
/*介绍:你知道了如何声明构造函数以及如何使用它来创建对象,但还需看看幕后的情况,以了解构造函数的工作原理。要明白构造函数的工作原理,关键在于了解运算符new都做了些什么。先来看看前面用来创建对象cat1的语句:*/
var cat1 = new Cat("大毛","黄色",9);
//请看赋值运算符的右边,所有的操作都是在这里进行的。我们来跟踪一下其执行过程:
//1.new 首先创建一个新的空对象。
//2.接下来,new 设置 this,使其指向这个新对象。this存储了一个引用,指向代码当前处理的空对象。
//3.设置this后,调用函数Cat,并将 “大毛”,“黄色” 和 9作为实参传递给它。
//"大毛" "黄色" 9
function Cat(name,color,weight) {
this.name = name;
this.color = color;
this.weight = weight;
}
//4.接下来,执行这个函数的代码。与大多数构造函数一样,Cat给新创建的this对象的属性赋值。
this.name ="大毛"
this.color = "黄色"
this.weight = 9
//5.最后,Cat函数执行完毕后,运算符new返回this指向新创建的对象的引用。注意:它会自动为你返回this,你无需在代码中显式地返回。指向新对象的引用被返回后,我们将其赋给变量cat1。
- new 操作符的作用:
- 有创建空对象的作用
- 并且可以修改this的指向
- 并且返回创建的对象
- 总结:new可以创建空对象,并以空对象的身份去调用new后面紧接着的函数,这时候new后面紧接着的函数中的this就指向new出来的空对象了
- 在构造函数中定义方法
- 介绍:构造函数Cat创建的小猫对象也可以设置方法。因为在构造函数中,除了能给属性指定值外,还可以定义方法。
//例子:给Cat添加bark方法,扩展前面的代码。
function Cat(name,color,weight) {
this.name = name;
this.color = color;
this.weight = weight;
// 要添加bark方法,只需将一个函数(这里是一个匿名函数)赋给属性this.bark。
this.bark = function() {
if (this.weight > 12) {
alert(this.name + " says meow!");
} else {
alert(this.name + " SAYS MEOW!");
}
};
}
var cat = new Cat("小花","黑色", 14);
cat.bark()
//与以前创建的其他所有对象一样,我们使用this来表示当前对象。
4.理解对象实例
-
介绍:你无法通过观察确定JavaScript对象是哪种类型的对象,如小狗或汽车。在JavaScript中,对象是一个动态的结构,无论对象包含哪些属性和方法,其类型都是object。
-
如果知道对象是由哪个构造函数创建的,就能获悉一些有关它的信息。上面的代码中,每当你使用运算符new调用构造函数时,都将创建一个新的对象实例。因此,如果你使用构造函数Cat创建一个对象,这个对象就是猫;更准确地说,这个对象就是一个Cat实例。
-
说对象是某个构造函数的实例可以通过JavaScript提供的方法判断。
- 使用运算符instanceof来确定对象是由哪个构造函数创建的。
//instanceof`运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
/*
实际上,创建对象时,运算符new在幕后存储了一些信息,让你随时都能确定对象是由哪个构造函数创建的。运算符instanceof就是根据这些信息来确定对象是否是指定构造函数的实例。
JavaScript没有严格意义上的对象类型。如果要比较两个对象,看它们是否都是小猫或小狗,可检查它们是否是以相同的方式(即使用相同的构造函数)创建的。前面说过,小猫之所以是小猫,是因为它是由构造函数Cat创建的;而小狗之所以是小狗,是因为它是由构造函数Dog创建的。
*/
- 使用构造函数创建对象后,实例对象会自动含有一个constructor属性,指向它们的构造函数。
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
- 实例对象也可以有独特的属性
//介绍:使用构造函数来创建一致的对象,即包含相同属性和方法的对象。使用构造函数创建对象后,可对其进行修改,因为使用构造函数创建的对象是可以修改的。如:
// 使用构造函数Cat创建的小猫对象cat。
var cat = new Cat("大毛","黄色",9);
cat.owner = "老李"; // 要给这个对象添加新属性,只需给这个新属性赋值即可。
delete cat.weight; // 也可使用运算符delete删除既有的属性。
cat.trust = function(person) { // 要添加新方法,只需将这个方法赋给一个新属性。
return (person === "老李");
};
//上面的代码只修改了对象cat。如果给对象cat添加一个方法,那么只有cat对象有这个方法,其他小猫对象都不会包含这个方法。并且修改小猫示例对象,它依然是小猫实例对象。
- 注意:通常,你不会使用构造函数来创建一个对象,再将它修改得面目全非,看起来根本不像是使用这个构造函数创建的。一般而言,你使用构造函数来创建相当一致的对象;但如果你希望对象更灵活,JavaScript也提供了这样的支持。作为代码设计人员,由你决定如何以你和同事认为合理的方式使用构造函数和对象。
5. 对象字面量在构造函数中的应用
function Car(make, model, year, color, passengers, convertible, mileage) {
this.make = make;
this.model = model;
this.year = year;
this.color = color;
this.passengers = passengers;
this.convertible = convertible;
this.mileage = mileage;
this.started = false;
this.start = function() {
this.started = true;
};
}
//一个构造函数是这样的,就会显得参数十分不简洁
- 这里的问题是,构造函数Car包含大量的形参,难以阅读和维护。而且,编写调用这个构造函数的代码时,必须按正确的顺序指定所有实参。
//所以我们可以这么做:
var cadiParams = {
make: "GM",
model: "Cadillac",
year: 1955,
color: "tan",
passengers: 5,
convertible: false,
mileage: 12892
};
var cadi = new Car(cadiParams);
// 现在,需要删除构造函数Car的所有形参,用所传入对象的属性替代。我们将把这个形参命名为params。为使用这个对象,还需对这个构造函数的代码稍作修改
function Car(params) {
this.make = params.make;
this.model = params.model;
this.year = params.year;
this.color = params.color;
this.passengers = params.passengers;
this.convertible = params.convertible;
this.mileage = params.mileage;
this.started = false;
this.start = function() {
this.started = true;
};
this.stop = function() {
this.started = false;
};
this.drive = function() {
if (this.started) {
alert("Zoom zoom!");
}else {
alert("You need to start the engine first.");
}
};
}