1.构造函数其实就是一个普通函数,但是内部使用了this变量
。对构造函数使用new
运算符,就能生成实例,并且this
变量会绑定在实例对象上。
function Cat(name,color){
this.name=name;
this.color=color;
this.type = "猫科动物";
this.eat = function(){console.log("吃老鼠");};
}
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
console.log(cat1.name); // 大毛
console.log(cat1.color); // 黄色
//这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数。
console.log(cat1.constructor == Cat); //true
console.log(cat2.constructor == Cat); //true
//instanceof运算符,验证原型对象与实例对象之间的关系。
console.log(cat1 instanceof Cat); //true
console.log(cat2 instanceof Cat); //true
console.log(cat1.type); // 猫科动物
cat1.eat(); // 吃老鼠
这样写的弊端:对于每一个实例对象,type
属性和eat()
方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样既不环保,也缺乏效率。
console.log(cat1.eat == cat2.eat); //false
所以对于相同的属性和方法等,要用prototype去定义。上面的那两个公共部分应该如下写:
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){console.log("吃老鼠")};
此时:
console.log(cat1.eat == cat2.eat); //true
其实这也是JavaScript继承机制的设计思想,所有实例对象需要共享的属性和方法,都放在prototype对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
一些常用属性:
console.log(Cat.prototype.isPrototypeOf(cat1)); //true
console.log(Cat.prototype.isPrototypeOf(cat2)); //true
//本地属性才为true,prototype继承的属性为false
console.log(cat1.hasOwnProperty("name")); // true
console.log(cat1.hasOwnProperty("type")); // false
console.log("name" in cat1); // true
console.log("type" in cat1); // true
for(var prop in cat1) { //遍历属性,包括继承的
console.log("cat1[" + prop + "]=" + cat1[prop]);
}
2.构造函数的继承
一、 构造函数绑定。使用call或apply方法,将父对象的构造函数绑定在子对象上。
function Animal(){
this.species = "动物";
}
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
二、 prototype模式
function Animal(){
this.species = "动物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
console.log(Cat.prototype); //Object
Cat.prototype = new Animal();
console.log(Cat.prototype); //Animal
//Cat的prototype对象指向一个Animal的实例,所以Cat也拥有constructor属性
console.log(Cat.prototype.constructor);//function Animal(){this.species = "动物";}
console.log(Cat.prototype.constructor == Animal);//true
Cat.prototype.constructor=Cat;//如果没有这一句,cat1的构造函数就是上面的Animal,所以要把它更正为Cat
console.log(Cat.prototype.constructor);//function Cat(name,color){this.name = name;this.color = color;}
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
console.log(cat1.constructor); //function Cat(name,color){this.name = name;this.color = color;}
//也就是说,当替换了prototype对象
//o.prototype = {};
//必须要修正constructor
//o.prototype.constructor = o;
前面两种方法是把属性直接定义在构造函数里,一般公共属性的话一般放在prototype,继承的方式如下
三、 直接继承prototype(不够完善)
function Animal(){}
Animal.prototype.species = "动物";
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
console.log(Cat.prototype.constructor);//function Cat(name,color){this.name = name;this.color = color;}
console.log(Animal.prototype.constructor);//function Cat(name,color){this.name = name;this.color = color;}这样很混乱
//与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。
//缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,Cat.prototype与Animal.prototype永远保持一致,属性也是。
四、 利用空对象作为中介
function Animal(){}
Animal.prototype.species = "动物";
function Cat(name,color){
this.name = name;
this.color = color;
}
function extend(Child, Parent) { //封装好
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F(); //用空对象作为中介,避免了直接继承的弊端
Child.prototype.constructor = Child;
//Child.uber = Parent.prototype;//使之可以调用父对象的方法,实现继承的完备性
}
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
五、 拷贝继承
function Animal(){}
Animal.prototype.species = "动物";
function Cat(name,color){
this.name = name;
this.color = color;
}
function extend2(Child, Parent) {//封装好
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
console.log(i);
console.log(p[i]);
c[i] = p[i];
}
//c.uber = p;
}
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物