ECMAScript没有类的概念,因此面向对象与传统的静态语言有很大不同。
创建自定义对象简单的方式有两种:
var person1 = new Object(); //创建object实例 var person2 = {}; //对象字面量
然后可以给对象动态的添加需要的属性和方法,就可以获得对象的属性,调用对象的方法了。
对于这两种创建对象的方式没有什么区别。不过对象字面量表示法更受喜欢,因为代码量少,而且在创建的时候可以一次性将属性和方法定义好:
1 var person2 = { 2 name:'zhangsan', 3 sayName:function(){ 4 alert('my name is' + this.name); 5 } 6 };
但是这还是有个问题:如果要创建多个person对象,都有name属性和sayName方法,每创建一个对象都得这么写一次,太麻烦了!
好在前辈们已经总结出了几种种创建对象的方式:
1. 工厂模式
function createPerson(name){ var o = new Object(); o.name = name; o.sayName = function(){ alert('my name is' + this.name); }; return o; }; var person1 = createPerson('zhangsan'); var person2 = createPerson('lisi'); person1.sayName(); //zhangsan person2.sayName(); //lisi
感觉跟调用java方法差不多。虽然比刚开始那种方式好点,但也是优缺点并存:
优点:解决了创建相似对象的问题,减少代码量。
缺点:无法知道一个对象的类型,全部都是Object。
2. 构造函数模式
function Person(name, age){ this.name = name; this.age = age; this.sayName = function(){ alert('hello:' + name + ',' + age); } } var person1 = new Person('zhangsan',20); var person2 = new Person('lisi', 30); person1.sayName(); person2.sayName(); console.log(person1 instanceof Person); //true console.log(person2 instanceof Person); //true console.log(person1 instanceof Object); //true console.log(person2 instanceof Object); //true
Person函数的特点:
1.没有显式的创建对象,即没用new 关键字。
2.属性都赋值了给了this对象。
3.没有return.
要创建实例,必须用new关键字。new做了以下四件事情:
1.创建一个新的对象,此时对象就是个空对象。
2.将构造函数的作用域赋值给新对象,此时this就指向刚才那个空对象了。
3.执行Person函数,其实就是在给那个新对象一个一个地动态添加属性。
4.将添加完属性的这个新对象返回,所以person1,person2就指向新对象了。
创建出来的示例既是Person的实例,也是Object的实例。
构造函数终归函数函数,可以被当作普通函数调用,不过this对象指向全局的Global对象(window对象),之后就可以通过window对象来调用了。
//作为普通函数调用 Person('wangwu',30); //属性和方法都会被添加给全局的window对象 window.sayName(); //wangwu
构造函数也可以在另一个对象的作用域中调用:
//在另一个对象的作用域中调用 var obj= new Object(); var obj1 = new Object(); Person.call(obj, 'zhaoliu',40); Person.apply(obj1,['zhaoqi',50]); obj.sayHello(); obj1.sayHello();
使用构造函数的方法会有什么问题呢?它将所有的对象的属性和方法都创建了一遍,每个对象的方法都是一个Function实例。
obj.sayHello == obj1.sayHello; //false
结果是false就论证了这一点。
按道理说属性应该是私有的,方法应该是共享的才对。为了解决这个问题,原型模式该登场了。
3. 原型模式
什么是原型?
js中每个函数都有prototype(原型)属性,它是一个对象类型的指针。而这个对象包含特定类型的所有实例的共享属性和方法。
既然每个函数都有prototype属性,而且还是对象类型的,那么就可以这么使用了:
function Person(){ } Person.prototype.name = 'zhangsan'; Person.prototype.age = 20; Person.prototype.sayHello = function(){ alert('name:' + this.name + ',age:' + this.age); }; var person1 = new Person(); person1.sayHello();
Person函数定义并没有什么内容,根据new关键字的特性,所以其实person1实例自己也没什么内容的,这些属性和方法都是从原型中寻找的。
原型有以下几个特征:
1.所有的原型对象(prototype指向的这个对象)都有一个constructor属性,这个属性指向 含有prototype属性 所在函数。Person.prototype表示Person函数的原型对 象,而Person函数有还有prototype这个属性,所以Person.prototype.constructor===Person 成立。
2.当为对象实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性,这个属性只是该实例特有的。
3.代码读取某个实例的属性时,先从实例本身搜索,如果找不到,就搜索对应的原型对象。
4.原型具有动态性,每次对原型对象的修改都能立即从实例上反应出来。
第四点可以用代码验证:
function Person(){ } var person1 = new Person(); //调用构造函数,该实例会有一个指向最初原型对象的指针 Person.prototype.sayHello = function(){ alert('hello'); } person1.sayHello(); //ok Person.prototype = { //修改原型对象的指向 constructor:Person, //重写原型对象会使得constructor指向Objec构造函数,因此要动态指定 name:'lisi', age:30, sayName:function(){ alert('name:' + this.name); } }; //person1.sayName(); //Error //因为person1的原型指针仍然指向最初的原型对象,她没有sayName属性 var person2 = new Person(); person2.sayName(); //ok //person2是重新new出来的,它的原型指针指向的是新的原型对象
重写原型对象切断了现有原型与任何之前已经存在对象实例之间的关系,以前的实例仍然引用的是最初的原型。
原型对象的问题:
对所有实例共享,如果原型对象的属是引用类型的属性,如果某一实例修改了该属性,则也会影响其他实例:
解决办法:
结合构造函数和原型,属性私有,方法公有。
寄生式构造感觉就是结合和工厂和构造函数模式,new出来的对象还不用,返回一个自己创造的对象。个人感觉更接近于工厂模式。
4. 稳妥构造
特征:不用new操作符调用构造函数;创建对象的方法中没有this。
function Person(name, age, job){ var o = new Object(); o.sayName = function(){ alert(name); } //构造函数传入的值无法修改,只能访问 return o; } var person = new Person('zhangsan', '20', 'programer'); person.sayName();
这样传入构造函数的值就不修改,因为它是传入的参数,并没有属性把这个值存储起来。