上次笔记大致说了一下javaScript中函数的使用,我们这次来看一下构造函数与原型。
使用工厂方法创建对象
我们现在要创建几个对象:
var obj ={
name:"孙悟空",
age:18,
gender:male,
sayName:function(){
alert(this.name);
}
}
var obj2 ={
name:"猪八戒",
age:28,
gender:male,
sayName:function(){
alert(this.name);
}
}
var obj3 ={
name:"沙和尚",
age:38,
gender:male,
sayName:function(){
alert(this.name);
}
}
我们看到了大量的重复代码,程序员有一个特点就是懒,麻烦的事就不去做,所以我们看到这么多大量重复的代码,我们如果要创建多几个对象就会不难反了。
所以,我们以后遇到大量重复的代码,就要把它们提出来。我们现在说使用工厂方法创建对象。
function createPerson(name,age,gender){
//创建一个新的对象
var obj = new Object();
//向对象中添加属性
obj.name = name;
obj.age = age;
obj.gender = gender;
obj.sayName = function(){
alert(this.name);
}
//将创建新的对象返回
return obj;
}
var obj2 = createPerson("猪八戒",18,"男");
var obj3 = createPerson("白骨精",22,"女");
var obj4 = createPerson("唐僧",66,"男");
console.log(obj2);
console.log(obj3);
console.log(obj4);
使用工厂方法创造的对象,使用的构造函数都是Object,所以常见的对象都是Object这个类型,就导致我们无法区分对象类型。
构造函数
-
构造函数就是一个普通函数,不同的是:构造函数习惯上首字母大写。
-
构造函数和普通函数的区别,就是调用方式的不同。
- 普通函数是直接调用,而构造函数要使用new关键字来调用。
创建一个构造函数,专门用来创建Person对象:
fuction Person(){
}
普通方式调用:
var per = Person();
构造函数调用:
var per = new Person();
使用同一个构造函数构造创建的对象,称为一类对象,也将构造函数称为一个类。我们将通过该构造函数创建的对象,称为该类的实例。
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
alert(this.name);
}
}
//这三个对象都是Person类的实例
var per = new Person("阿牛",18,"男");
var per2 = new Person("女娃",18,"女");
var per3 = new Person("奔波霸",22,"男");
使用instenceof
可以检查一个对象是否是一个类的实例。如果是,则返回true,否则返回false。
语法:对象 instenceof 构造函数
所有的对象都是Object的后代。
构造函数的执行流程
- 立刻创建一个新的对象。
- 将新建的对象,设置为函数中的this,在构造函数中可以使用this来引用新建的对象。
- 逐行执行函数中的代码 。
- 将新建的对象作为返回值返回。
现在我们再来重申一下之前说过的this:
- 当以函数的形式调用的时候,this就是window。
- 当以方法的形式调用的时候,谁调用方法this就是谁。
- 当以构造函数的形式调用的时候,this就是创建的那个对象。
使用toString()
当我们写这样一段代码时:
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var per = new Person("孙悟空",18,"男");
document.write(per);
发现在页面中总是返回这个奇怪的东西。
因为,当我们直接在页面中打印一个对象时,实际上是输出的对象的toString()方法的返回值。
那toString()方法哪来的那??
原来是在Object的原型里。
如果我们希望在输出对象时,不输出[object object],可以为对象添加一个toString()方法。
我们也可以这样修改:
Person.prototype.toString = function(){
return "Person[ name = " +this.name + " , age = " + this.age + " , gender = " + this.gender+" ]";
}
document.write(per);
构造函数的修改
还是这个代码,我们来看看有没有什么问题:
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
alert("hello 大家好,我是"+this.name);
};
}
var per = new Person("孙悟空",18,"男");
var per2 = new Person("猪八戒",28,"男");
per.sayName();
per2.sayName();
-
在Person构造对象中,为每一个对象都添加了一个sayName方法,
-
目前我们的方法是在构造函数内部创建的,也就是构造函数没执行一次就会常见一个新的sayName方法,也就是说,所有实例的sayName都是唯一的。
-
这样就导致构造函数执行一次,就会创建一个新的方法,执行10000次就会创造10000个新的方法,而这10000个方法都是一模一样的,这是完全没有必要的。完全可以使所有的对象共享同一个方法。
那怎么办呢??我们可以将sayName提取出来,这样,我们sayName方法只有一个,对构造函数的性能提升了不少。
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = fun;
}
function fun(){
alert("hello 大家好,我是"+this.name);
}
var per = new Person("孙悟空",18,"男");
var per2 = new Person("猪八戒",28,"男");
per.sayName();
per2.sayName();
但是这是,我们将函数定义在全局作用域中了,将函数定义在全局作用域中,污染了全局作用域中的命名空间。而且,定义在全局作用域中,也很不安全!!所以尽量不要在全局作用域中定义,那现在又怎么办呢???我们要说原型(prototype)。
原型
-
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype。这个属性对应着一个对象,这个对象就是我们所谓的原型对象。
-
如果函数作为普通函数调用prototype没有任何作用。
-
当函数以构造函数的形式调用时,它所创建的对象中,都会有一个隐含的属性,指向该构造函数的原型对象。我们可以通过
__proto__
来访问该属性。 -
原型对象就相当于一个公共的区域,所有同一类的实例,都可以访问到这个原型对象。我们可以将对象中共有的内容,统一设置到原型对象中。
- 当我们访问一个属性或者方法时,先会在自身对象中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
我们也可以向MyClass的原型中添加方法:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
function MyClass(){
}
MyClass.prototype.sayHello = function(){
alert("hello");
}
var mc = new MyClass();
var mc2 = new MyClass();
mc.sayHello();
</script>
</head>
<body>
</body>
</html>
这样既可以调用到我们需要的方法,又不会污染全局作用域中的命名空间。所以可以完善一下:
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayName = function(){
alert("Hello 大家好, 我是"+ this.name);
}
var per = new Person("孙悟空",18,"男");
var per2 = new Person("猪八戒",28,"男");
per.sayName();
per2.sayName();
以后我们创建构造函数时,可以将这些对象共有的属性或者方法,同意添加到构造函数的原型对象中,这样不用想每一个对象添加,也不会影响全局作用域,就可以使每个对象都具有这些属性和方法了。
检测原型
我们之前说过,可以使用in来检查对象中是否包含某属性。
function MyClass(){
}
MyClass.prototype.name = "我是原型中的名字";
var mc = new MyClass();
document.write("name" in mc);//true
但是,此时我们并没有向mc对象中添加name属性,而是向其原型添加了name属性,而使用in检查对象中是否含有某个属性时,如果对象中没有,但是原型中有,也会返回true。
- 可以使用对象的
hasOwnProperty()
来检测对象自身中是否含有该属性。 - 使用该方法只有自身中含有该属性时,才会返回true。
document.write(mc.hasOwnProperty("name"));//false
哪么如果你用hasOwnProperty()去检测mc和mc的原型时,有趣的事情就发生了。
document.write(mc.hasOwnProperty("hasOwnProperty"));
document.write("<br />");
document.write(mc.__proto__.hasOwnProperty("hasOwnProperty"));
document.write("<br />");
我们发现,hasOwnProperty()方法,既不在mc中也不再mc的原型对象中,那它在那里呢??
我们说原型对象还有原型对象!!
document.write(mc.hasOwnProperty("hasOwnProperty"));
document.write("<br />");
document.write(mc.__proto__.hasOwnProperty("hasOwnProperty"));
document.write("<br />");
document.write(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));
document.write("<br />");
原型对象也是对象,所以它也有原型。
-
所以:当我们使用一个对象的属性或者方法的时候,会先在自身中寻找,如果有,则会直接使用。如果没有,则会去原型对象中寻找。如果原型对象中有,则使用,如果没有则去原型对象的原型对象中寻找,直到找到Object对象的原型。如果依然没有则返回undefined。
但是,也不是没完没了的。Object对象的原型没有原型。