前言
构造函数不仅只出现在JavaScript中,它同样存在于很多主流的程序语言里,比如c++、Java、PHP等等。与这些主流程序语言一样,构造函数在js中的作用也是用来创建对象时初始化对象,并且总与new运算符一起使用
一、使用工厂方法创建对象
如果现在让你去创建三个不同的对象:
obj1:{孙悟空,19,男},
obj2:{猪八戒,28,男},
obj3:{白骨精,18,女}
你会如何去创建呢?
很多人都会想到使用字面量或new运算符来创建,当我们使用字面量创建时:
var obj1 = {
name: "孙悟空",
age: 19,
gender: "男",
sayName: function () {
console.log(this.name);
}
};
var obj2 = {
name: "猪八戒",
age: 28,
gender: "男",
sayName: function () {
console.log(this.name);
}
};
var obj3 = {
name: "白骨精",
age: 18,
gender: "女",
sayName: function () {
console.log(this.name);
}
};
obj1.sayName();//孙悟空
obj2.sayName();//猪八戒
obj3.sayName();//白骨精
很明显,这段代码给人一种笨拙感,且灵活性不高。也许你会说,我看着挺简单的啊。的确,如果仅仅是创建几个对象,并不会有多麻烦。但当我们需要创建大量、甚至海量的对象时,如果仍然是将其一个个的创建出来便显得有些笨拙了。
于是,为了解决这个问题,著名的工厂方法便应运而生了。
所谓工厂方法,就是指像现实生活中的工厂生产产品一样机械化的创建对象
function createPerson(name, age, gender) {
var obj=new Object();
obj.name=name;
obj.age=age;
obj.gender=gender;
obj.sayName=function(){
console.log(this.name);
}
return obj;
}
var obj1=createPerson("孙悟空",19,"男");
var obj2=createPerson("猪八戒",28,"男");
var obj3=createPerson("白骨精",18,"女");
obj1.sayName();//孙悟空
obj2.sayName();//猪八戒
obj3.sayName();//白骨精
在这段代码中,函数createPerson()就是工厂,obj1,obj2,obj3便是其所生产的产品。
二、构造函数
先来看下面这段代码:
var person = {
name: "张三",
age: 19,
gender: "男",
sayName: function () {
console.log(this.name);
}
};
var dog = {
name: "旺财",
age: 3,
gender: "雄",
sayName: function () {
console.log(this.name);
}
};
console.log(person);//[object Object]{age: 19, gender: "男", name: "张三"}
console.log(dog);//[object Object]{age: 3, gender: "雄", name: "旺财"}
console.log(person instanceof Object);//true
console.log(dog instanceof Object);//true
我们发现使用工厂方法所创建的对象,都是object类,这将导致我们无法区分出多种不同类型的对象。而构造函数恰好解决了这个问题
1.构造函数的创建
——构造函数的创建方式与普通函数没有区别
——构造函数将函数名首字母大写
——普通函数直接调用,构造函数使用new关键字调用
使用instanceof可以检查一个对象是否是一个类的实例
语法:
对象 instanceof 构造函数
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
通过构造函数创建的对象,称为是该类的实例
2.构造函数的执行流程
(1)创建一个新的对象
(2)将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象(即this指向新建的空对象)
(3)逐行执行函数中的代码
(4)将新建的对象作为返回值返回
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.sayName=function(){
console.log(this.name);
};
}
function Dog(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.sayName=function(){
console.log(this.name);
}
}
var person=new Person("王二",18,"男");
var dog=new Dog("旺财",2,"雄");
console.log(person);//Person {name: "孙悟空", age: 18, gender: "男", sayName: ƒ}
console.log(dog);//Dog {name: "旺财", age: 2, gender: "雄", sayName: ƒ}
console.log(person instanceof Object);//true
console.log(person instanceof Person);//true
console.log(person instanceof Dog);//false
console.log(dog instanceof Object);//true
console.log(dog instanceof Dog);//true
console.log(dog instanceof Person);//false
在上面这段代码中,我们发现:
- person、dog和Object左instanceof检查时,都返回了true,这说明所有的对象都是Object的后代,即:人是一个对象,狗也是一个对象
- 在Person构造函数和Dog构造函数中,为每一个对象都添加了一个sayName方法,该方法是在构造函数内部创建的,所以构造函数每执行一次就会创建一个新sayName方法,即所有实例的sayName都是唯一的
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.sayName=function(){
console.log(this.name);
};
}
var person=new Person("王二",18,"男");
var person2=new Person("张三",20,"男");
console.log(person.sayName==person2.sayName);//false
这导致构造函数执行一次就会创建一个新的方法,例如:执行1000次就会创建1000个新的方法,而这1000个方法都是一模一样的,这是完全没有必要的,完全可以使所有的对象共享同一个方法
解决方法一:将sayName方法放置于全局作用域
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = fun;
};
function fun(){
console.log(this.name);
}
var per=new Person("李四",25,"男");
var per2=new Person("王老五",33,"男");
per.sayName();//李四
per2.sayName();//王老五
console.log(per.sayName==per2.sayName);//true
缺点:将函数定义在全局作用域,污染了全局作用域的命名空间,且定义在全局作用域中也很不安全
解决方法二:将sayName方法添加到Person的原型对象中
- 我们创建的每一个函数都有一个prototype属性,该属性是一个指针,该指针指向了一个对象。对于我们创建的构造函数,该对象中包含可以由所有实例共享的属性和方法
- 在调用构造函数创建新的实例时,该实例的内部会自动包含一个[[Prototype]]指针属性,该指针指便指向构造函数的原型对象。注意,这个指针关联的是实例与构造函数的原型对象而不是实例与构造函数
//per是构造函数Person的实例,per有一个指针__proto__([[Prototype]]),指向Person的原型对象——Person.prototype
console.log(per.__proto__==Person.prototype);//true
console.log(per.__proto__==Person);//false
- 当我们需要读取对象的某个属性时,都会执行一次搜索。首先在该对象中查找该属性,若找到,返回该属性值;否则,到[[prototype]]指向的原型对象中继续查找。
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
};
Person.prototype.sayName = function () {
console.log(this.name);
}
var per = new Person("李四", 25, "男");
var per2 = new Person("王老五", 33, "男");
per.sayName();//李四
per2.sayName();//王老五
console.log(per.sayName==per2.sayName);//true