工厂方法创建对象
我们之前已经学习了如何创建一个对象,那我们要是想要创建多个对象又该怎么办?聪明的同学可能会说,直接在写几个对象不就好了吗?比如下边的代码:
var person1 = {name: "孙悟空",age: 18,sayName: function () {console.log(this.name);}
};var person2 = {name: "猪八戒",age: 19,sayName: function () {console.log(this.name);}
};var person3 = {name: "沙和尚",age: 20,sayName: function () {console.log(this.name);}
};console.log(person1);
console.log(person2);
console.log(person3);
的确,上述代码确实可以创建多个对象,但是,这样的解决方案真的好吗?对于少量对象可能使用,我们假设说,要用循环创建1000个对象,那你这种办法似乎就没用了,我们可以这么做,如下代码:
//使用工厂模式建立对象
function createdPerson(){ //创建对象var obj =new Object();//设置对象属性obj.name="孙悟空",obj.age="18",obj.sayName=function(){console.log(this.name); };//返回对象return obj }var person1 = createPerson("孙悟空", 18);
var person2 = createPerson("猪八戒", 19);
var person3 = createPerson("沙和尚", 20);console.log(person1);
console.log(person2);
console.log(person3);
现在再看上述代码,发现好像已经完美的解决了创建多个对象的难题,那我们是不是可以用循环批量创建1000个对象了呢?那我们就来试试:
// 使用工厂模式创建对象
function createPerson(name, age) {// 创建新的对象var obj = new Object();// 设置对象属性obj.name = name;obj.age = age;// 设置对象方法obj.sayName = function () {console.log(this.name);};//返回新的对象return obj;
}for (var i = 1; i <= 1000; i++) {var person = createPerson("person" + i, 18);console.log(person);
}
这样我们就实现了批量创建对象的功能,至于对象的名称和年龄,我们可以通过名称数组和年龄数组来获取,但这并不是我们本小节的重点,我们就忽略了。
用构造函数创建对象
你会发现我们所创建的对象类型都是Object,具体代码如下:
// 使用工厂模式创建对象
function createPerson(name, age) {// 创建新的对象var obj = new Object();// 设置对象属性obj.name = name;obj.age = age;// 设置对象方法obj.sayName = function () {console.log(this.name);};//返回新的对象return obj;
}for (var i = 1; i <= 1000; i++) {var person = createPerson("person" + i, 18);console.log(typeof person);
}
那这有问题吗?看起来有,看起来好像又没有,每创建一个都是对象,但是在实际生活中,人应该是一个确定的类别,属于人类,对象是一个笼统的称呼,万物皆对象,它并不能确切的指明当前对象是人类,那我们要是既想实现创建对象的功能,同时又能明确所创建出来的对象是人类,那么似乎问题就得到了解决,这就用到了构造函数,每一个构造函数你都可以理解为一个类别,用构造函数所创建的对象我们也成为类的实例,那我们来看看是如何做的:
// 使用构造函数来创建对象
function Person(name, age) {// 设置对象的属性this.name = name;this.age = age;// 设置对象的方法this.sayName = function () {console.log(this.name);};
}var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);console.log(person1);
console.log(person2);
console.log(person3);
那这构造函数到底是什么呢?我来解释一下:
构造函数:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
那构造函数是怎么执行创建对象的过程呢?我再来解释一下:
调用构造函数,它会立刻创建一个新的对象
将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
逐行执行函数中的代码
将新建的对象作为返回值返回
你会发现构造函数有点类似工厂方法,但是它创建对象和返回对象都给我们隐藏了,使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例。
现在,this又出现了一种新的情况,为了不让大家混淆,我再来梳理一下:
当以函数的形式调用时,this是window
当以方法的形式调用时,谁调用方法this就是谁
当以构造函数的形式调用时,this就是新创建的那个对象
我们可以使用 instanceof 运算符检查一个对象是否是一个类的实例,它返回true或false
语法格式:
对象 instanceof 构造函数
console.log(person1 instanceof Person);
原型
使用构造函数的方式进行创建对象,但是,它还是存在一个问题,那就是,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数,在构造函数中直接引用就可以了,上代码:
// 使用构造函数来创建对象
function Person(name, age) {// 设置对象的属性this.name = name;this.age = age;// 设置对象的方法this.sayName = sayName
}// 抽取方法为全局函数
function sayName() {console.log(this.name);
}var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);person1.sayName();
person2.sayName();
person3.sayName();
但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?答案肯定是有的,这就需要原型对象了,我们先看看怎么做的,然后在详细讲解原型对象。
// 使用构造函数来创建对象
function Person(name, age) {// 设置对象的属性this.name = name;this.age = age;
}// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {console.log(this.name);
};var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);person1.sayName();
person2.sayName();
person3.sayName();
那原型(prototype)到底是什么呢?
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
原型链
访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。
我们使用一张图来梳理一下上一节原型案例的代码:
// 使用构造函数来创建对象
function Person(name, age) {// 设置对象的属性this.name = name;this.age = age;
}Person.prototype.height =10;
var a = new Person();a._proto_=== Person.prototype // true
Person===Person.protype.constructor // true
Object.getPrototype(person1) === Person.prototype //true
当我们使用 a 的
注意:Object对象是所有对象的祖宗,Object的原型对象指向为null,也就是没有原型对象