初始方式
- 创建一个Object的实例
var person = new Object();
person.name = 'liushuang';
person.age = 32;
person.job = 'dancer';
person.sayName = function(){
console.log(this.name)
}
- 对象字面量
var person = {
name : 'liushuang';
age : 32;
job : 'dancer';
sayName: function(){
console.log(this.name)
}
};
创建对象
工厂模式
Object构造函数和对象字面量创建对象的缺点:使用同一个接口创建很多对象,会产生大量的代码。工厂模式可以解决这个问题。
工厂模式抽象了创建具体对象的过程。ES中无法创建类,开发人员发明了一种函数,用函数来封装以特定接口创建对象的细节。
function createPerson(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function (){
console.log(this.name);
}
return obj;
}
var person1 = createPerson('xzq',20,'singer');
var person2 = createPerson('ls',32,'dancer');
缺点:无法知道一个对象的类型
构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name)
}
}
var person1 = new person('xzq',20,'singer');
var person2 = new person('ls',32,'dancer');
优点:它的实例标识为一种特定的类型。
缺点: 每个方法都要在实例中创建一遍。
上例中的person1和person2中都有一个sayName的方法,这两个方法不是同一个Function的实例,但实际上两个函数在做同一件事情。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function('console.log(this.name)')
}
可以通过把sayName转移到构造函数外部来解决这个问题。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
console.log(this.name)
}
新缺点:
- 在全局作用域中定义的函数只能被某个对象调用,这让全局作用域优点名不副实;
- 如果对象需要定义很多函数,就得定义很多个全局函数,那么这个自定义的引用类型就没什么封装性可言了。
原型模式
function Person(){}
Person.prototype.name = 'ls';
Person.prototype.age = 32;
Person.prototype.job = 'dancer';
Person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();
Person的这些属性和方法由所有实例共享。也就是说,person1和person2访问的都是同一组属性和同一个sayName()函数。
缺点:因为属性被所有实例共享,引用类型的属性被某一个实例修改,其他实例取到的实例也将是被修改后的。
组合使用构造函数模式和原型模式
这是创建自定义类型最常见的方式。构造函数用于定义实例属性,原型用于定义方法和共享的属性。
优点:每个实例都有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor:Person,
sayName: function(){
console.log(this.name)
}
}
var person1 = new Person(‘xzq’,20,‘singer’);
var person2 = new Person(‘ls’,32 ,‘dancer’);
动态原型模式
独立的构造函数和原型,不便于理解。动态原型模式是解决这一个问题的方案。他把所有信息封装在构造函数中,通过在构造函数中初始化原型(仅在有必要的情况下),又保持了同时使用构造函数的特点和原型的特点。
function Person(name,age.job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var person1 = new Person(‘xzq’,20,‘singer’);
var person2 = new Person(‘ls’,32 ,‘dancer’);
寄生构造函数模式
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
缺点:不能依赖instanceof来确定对象类型
稳妥构造函数模式
使用场景:在一些安全的环境中(禁止使用this和new),或者防止数据被其他程序改动。
function Person(name,age,job){
var o = new Object();
//定义私有变量和函数
o.sayName = function(){
console.log(name);
}
return o;
}
除了sayName()没有其他方式访问其成员变量。
缺点:不能依赖instanceof来确定对象类型
继承
原型链
function Parent(name){
this.name = name;
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){
}
//继承
Child.prototype = new Parent();
var child1 = new Child();
缺点:
- 引用类型的属性被所有实例共享
- 在创建子类型的实例时,不能向父类中传递参数。
借用构造函数
function Parent(){
this.names = ['xzq','ls'];
}
function Child(){
Parent.call(this);
}
var child1 = new Child();
child1.names.push('lh');
console.log(child1.names); //['xzq','ls','lh']
var child2 = new Child();
console.log(child1.names); //['xzq','ls']
缺点:
方法都在构造函数中创建,每创建一次实例都会创建一遍方法。(方法无法复用)
组合继承
function Parent(age){
this.names = ['xzq','ls'];
this.age = age;
}
Parent.prototype.getAge = function(){
console.log(this.age);
}
function Child(age,job){
Parent.call(this,age); //第二次调用Person()
this.name = name;
}
Child.prototype.getJob = function(){
console.log(this.job);
}
Child.prototype = new Person(); //第一次调用Person()
child.prototype.constructor = Child;
var child1 = new Child(26,'singer');
child1.names.push('lh');
console.log(child1.names); // ['xzq','ls','lh']
child1.getAge();//26
child1.getJob();//'singer'
var child2 = new Child(16,'dancer');
console.log(child2.names); // ['xzq','ls']
child2.getAge();//16
child2.getJob();//'dancer'
优点:既通过构造函数保证每个实例有自己的属性,又通过在原型上定义方法实现了方法复用。
缺点:会调用两次父类型构造函数,第一次是在创建子类型原型时;第二次是在子类型构造函数内部。这样子会创建两组属性,一组在子类型原型对象上,一组在实例中。
原型式继承
使用场景:没有必要创建构造函数,只是想让一个对象和另一个对象保持类似的情况。
function createObject(o){
function Foo(){}
Foo.prototype = o;
return new Foo();
}
就是 ES5 Object.create() 的模拟实现,将传入的对象作为创建的对象的原型。
缺点: 引用类型的属性被共享。与原型链继承的缺点一样。
var person = {
name: 'xzq',
friends: ['zs','ls']
}
var person1 = createObject(person);
person1.name = 'cici';
person1.friends.push('steve');
var person2 = createObject(person);
person1.name = 'kiki';
console.log(person.name);//'xzq
console.log(person.friends);//['zs', 'ls', 'steve']
console.log(person2.friends);//['zs', 'ls', 'steve']
注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1和person2有独立的 name 值,而是因为person1.name = 'cici',给person1添加了 name 值,并非修改了原型上的 name 值。
寄生式继承
使用场景:主要考虑对象,而不是自定义类型和构造函数的情况。
function creatAnother(original){
var clone = Object(original);
clone.sayHi = function(){
console.log('hi');
}
return clone;
}
var person = {
name: 'xzq',
friends: ['zs','ls']
}
var person1 = creatAnother(person);
person1.sayHi(); //"hi"
缺点:每次创建对象都会创建一遍函数,不能做到函数复用,与构造函数模式类似。
寄生组合式继承
使用场景:解决组合式继承会调用两次父类型构造函数的问题。
思路:使用寄生式继承来继承父类型的原型,并将结果指定给子类型的原型。
function inheritPrototype(child,parent){
var clone = Object(parent.prototype); //创建对象
clone.constructor = child; // 增强对象
child.prototype = clone; //指定对象
}
示例:
function Parent(name){
this.name = name;
this.frineds = ['zs','ls'];
}
Parent.prototype.sayName = function(){
console.log(this.name)
}
function Child(name,age){
Parent.call(this,name);
this.age = age;
}
Child.prototype.sayAge = function(){
console,log(this.age);
}
inheritPrototype(Child,Parent);
var child1 = new Child('kiki',29);
child1.sayName(); // 'kiki'
参考《JavaScript高级程序设计》
总结:有时间再来完善