面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可 以创建任意多个具有相同属性和方法的对象。前面提到过,ECMAScript 中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
ECMA把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
我们之前在学习创建对象时,使用字面量方式和Object构造函数的方式都可以进行创建对象,但是我们也发现了这两种方式只适合用来创建单个对象,想要创建大量对象时,一份代码就要书写多次,显然的产生了代码冗余。接下来来介绍几种其他创建对象的方式。
工厂模式创建对象:
上面我们说使用Object构造函数的方式进行创建多个对象时,会产生大量重复代码,那自然而然我们就会想到将重复性的代码封装成为一个函数,这个函数就是工厂函数。
- // 工厂模式实际上就是封装函数
- function createStar(name, age, sex){
- // 1.创建一个空对象(原材料)
- var obj = {};
- // 2.添加属性和方法(加工)
- obj.name = name;
- obj.age = age;
- obj.sex = sex;
- obj.skill = function(){
- console.log('sing dance');
- }
- // 3.返回出对象(出厂)
- return obj
- }
- // 创建对象
- var star1 = createStar('何美男',25,'boy');
- console.log(star1);
- star1.skill(); // sing dance
使用工厂模式创建对象虽然可以实现批量创建对象,但是却出现了新的问题,即创建出的对象指向不明确。使用instanceof操作符检测对象类型时,都是属于object。
- var star2 = createStar('大黄', 3, 'boy');
- console.log(star2);
- star2.skill();
- console.log(typeof star1, typeof star2); // object object
- console.log(star2 instanceof Object); // true
构造函数创建对象:
构造函数实际上就是一个函数,只是这个函数是专门用来创建对象的,他的特点:
1.构造函数首字母大写,为了区分普通函数;
2.不需要创建对象,属性和方法直接添加在this上,不需要return返回
3.构造函数调用时,一定要使用new;
- // 1.声明构造函数
- function Teacher(name, age, sex){
- // 添加属性
- this.name = name;
- this.age = age;
- this.sex = sex;
- // 添加方法
- this.skill = function(){
- console.log('布置作业');
- }
- }
- // 2.实例化对象
- var t1 = new Teacher('小王',20,'girl');
- console.log(t1);
- t1.skill(); // 布置作业
- var t2 = new Teacher('小李', 25, 'boy');
- console.log(t2);
- t2.skill(); // 布置作业
我们需要明确 new操作符做了什么?
1.隐式的创建了一个对象;
2.让this指向这个空对象;
3.让实例对象的__ proto __指向构造函数的prototype;
4.执行代码,给this添加属性和方法;
5.隐式的返回创建好的对象;
所以在使用构造函数创建对象时一定要使用new操作符。
使用构造函数创建对象解决了创建出的对象指向不明确,构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,t1 和 person2 都有一个名为 skill()的方法,但那两个方法不是同一个 Function 的实例。创建两个完成同样任务的 Function 实例的确没有必要,我们可以通过代码证实:
- console.log(t1.skill() == t2.skill()); // false-比较的是地址
原型创建对象:
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中,如下面的例子所示。
- function Stu(){}
- // 2.添加属性和方法
- Stu.prototype.name = '小明';
- Stu.prototype.age = 18;
- Stu.prototype.skill = function(){
- console.log('沉迷敲代码');
- }
- // 3.实例化对象
- var s1 = new Stu();
发现对象里边是空的,打开可以看到所有的属性和方法都在__proto__原型属性上
理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部 属性),指向构造函数的原型对象。__proto__这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
针对上述原型模式创建对象的代码,我们可以画出它的图例关系: