对象
1.对象字面量
var obj={name:'moon',age:18}
2.构造函数
var obj1 = new Object();
obj1.name="moon";
obj1.age = 18;
3.工厂模式
因为使用普通的字面量创建多个对象时,会产生大量的重复代码,为了解决这个问题,我们引入工厂模式
function newDog(name,age,gender){
var d = new Object();
d.name = name;
d.age = age;
d.gender = gender;
d.sayName = function(){
console.log("汪汪的名字是"+this.name);
}
return d;
}
var d1 = newDog('二哈',2,'男');
目的:通过将创建对象的方法封装起来,避免重复代码产生
缺点:创建的所有对象类型都是Object对象,没有办法知道对象到底是Person还是Dog。
4.构造函数模式
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.sayName=function(){
console.log(this.name);
}
}
var p1 = new Person('平菇',5,'man');
p1.sayName();//平菇
console.log(p1);
/*p1 结果
Person {
name: '平菇',
age: 5,
gender: 'man',
sayName: [Function (anonymous)]
}
*/
console.log(typeof p1);//object
构造函数的问题:
1.可以区分每个类
2.每个方法都要在每个实例上重新创建一遍,没有必要
3.虽然可以将方法提取出来,提取到全局范围内,然后将引用传递给对象中的函数属性,但是全局函数太多的话,体现不了类的封装性。
5.原型模式
function Person(){};
Person.prototype.name="moon";
Person.prototype.likes=['eat','run'];
var per1 = new Person();
console.log(per1.name);//moon
per1.likes.push('fly');//我们想要的是p1的likes加上一个fly
console.log(per1.likes)//这里fly加上了[ 'eat', 'run', 'fly' ]
var per2 = new Person();
console.log(per2.name);//moon
console.log(per2.likes);
//这里我们并不想让p2的likes改变,可是p2的likes也被改变了
//也是[ 'eat', 'run', 'fly' ],因为两个likes指向了同一地址值。
原型模式的特点:
对于共享属性,共享函数使用这种方法是非常合适的,但是对于引用类型的数据就不太好了
6.构造函数模式和原型模式结合
//在构造函数中放私有的属性
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.likes=['琵琶','笛子','卡卡']}
//在原型中放实例属性和实例方法
Person.prototype={
constructor:Person,
//这里把Person的原型写成了一个对象,注意把constructor指针指回去
sayName:function(){
console.log(this.name);
}
}
var p1 = new Person('平菇',2,'男');
p1.likes.push('魔法');
var p2 = new Person('小马',22,'女');
p2.likes.push('吃饭');
console.log(p1);
console.log(p2);
p1.sayName();
p2.sayName();
/* Person {
name: '平菇',
age: 2,
gender: '男',
likes: [ '琵琶', '笛子', '卡卡', '魔法' ]
}
Person {
name: '小马',
age: 22,
gender: '女',
likes: [ '琵琶', '笛子', '卡卡', '吃饭' ]
}
平菇
小马*/
这样就解决了构造函数模式和原型模式的问题。
继承
1.原型链继承
每个构造函数都有一个原型对象,原型对象中都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。当原型对象等于另外一个类型的实例即继承。
调用某个方法或者属性的步骤
a.搜索实例
b.搜索原型
c.搜索父类原型
//定义父类类型
function Animal(){
this.name = "animal"
}
Animal.prototype = {
constructor:Animal,
sayName : function(){
console.log(this.name);
}
}
//定义子类类型
function Dog(){
this.color = "灰色"
}
//通过将子对象的原型对象指向父对象的一个实例来完成继承
Dog.prototype = new Animal();
//子对象的方法其实是定义在了父类对象的实例上。
Dog.prototype.sayColor = function(){
console.log(this.color);
}
var dog = new Dog();
console.log(dog);//Animal { color: '灰色' }
dog.sayColor();//灰色
dog.sayName();//animal
谨慎定义方法
子类型覆盖超类型中的某个方法,或者是需要添加超类中不存在的方法,都需要将给原型添加方法的代码放在继承之后(即替换原型的语句之后)
原型链问题
1)通过原型来实现继承时,原型实际上会变成另一个类型的实例,原来的构造函数内的属性也就变成了现在的原型属性
2)在创建子类型的实例时,不能向超类型的构造函数传递参数。因此实践中很少会单独使用原型链
2.经典继承
不仅可以继承方法,也可以继承属性
也称 "伪造对象" 或 "经典继承",在子类型构造函数的内部调用超类型构造函数。函数不过是在特定环境中执行代码的对象,因此通过apply(),call()方法可以在(将来)新建对象上执行构造函数,即 在子类型对象上执行父类型函数中定义的所有对象初始化的代码。结果每个子类实例中都具有了父类型中的属性以及方法
function Animal(name){
this.name = name;
this.colors = ["red","gray"];
}
function Dog(name){
//继承了Animal
Animal.call(this,name);
this.color = "gray";
}
Animal.prototype.sayName = function(){
console.log(this.name);
}
var dog = new Dog("ling");
dog.colors.push("hhh");
console.log(dog);//Dog { name: 'ling', colors: [ 'red', 'gray', 'hhh' ], color: 'gray' }
//如果将函数定义在构造函数中,函数复用无从谈起
// dog.sayName(); //报错
//在超类型的原型中定义的方法,对于子类型而言是无法看到的
3.组合继承
原型链继承+经典继承组合起来继承构造函数的属性和方法,以及原型中的属性和方法
也称"伪经典继承",将原型链和借用构造函数的技术组合在一起。
原理是:使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对私有属性的继承。
function Animal(name){
this.name = name;
this.colors = ["red","gray"];
}
function Dog(name){
//继承了Animal(属性)
Animal.call(this,name);
this.color = "gray";
}
Animal.prototype.sayName = function(){
console.log(this.name);
}
//继承方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;//指向Dog构造函数,
var dog = new Dog();
dog.colors.push("hhh");
console.log(dog);
var animal = new Animal();
console.log(animal);
dog.sayName(); //可以调用