javascript中的面向对象
面向对象语言的的特性就是有类(class)的概念,通过类可以创建任意多个具有相同属性或方法的对象,然而 javascript 中没有类(class)的概念,所以它不是真正的面向对象
对象
由N组键值对组合起来的无序属性顺序的结合,其属性值可以是任意类型
//采用生成一个 Object 的实例来创建一个对象
var obj = new Object();
obj.name = "大毛";
obj.eat = function(){
console.log("吃鱼")
}
//采用字面量方式创建新对象
var obj = {
name: "大毛",
eat: function(){
console.log("吃鱼")
}
}
以上我我们利用 Object 的构造函数和字面量方式创建了单个对象,但,这种模式创建出来的对象有个明显的缺陷:
同一个接口创建多个对象,会有很多重复代码,那如何解决呢?带着这个问题,我们继续探究...
工厂模式
利用函数封装,减少代码重复问题
function Cat(name,color){
var o = new Object();
o.name = name;
o.color = color;
return o;
}
//相当于调用函数 Cat
var cat1 = Cat("大毛","黄色");
var cat2 = Cat("2毛","白色");
cat1.name //"大毛"
cat2.name //"2毛"
缺陷:2个实例之间没有关联,并且不能反映出 2 个实例出自同一个原型对象
构造函数模式
所谓构造函数,就是普通函数,内部采用 this 变量,使用 new 运算符生成一个实例,建议函数名首字母大写
//猫的原型对象
function Cat(name,color){
this.name = name;
this.color = color
}
//生成2个实例
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
console.log(cat1.name); //"大毛"
console.log(cat2.name); //"2毛"
每一个实例,都将自动生成一个名为 constructor 的属性,指向他们的构造函数
每个实例,都有一个 __proto__ ,这个是链接实例与构造函数的原型对象的而非是构造函数
function Cat(name,color){
this.name = name;
this.color = color
}
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
//实例的 constructor 实例的构造函数
cat1.constructor === Cat //true
cat2.constructor === Cat //true
cat1.constructor === cat2.constructor //true
cat1.__proto__ === Cat.prototype //true
cat1.__proto__ === Cat //false
instanceof 运算符,验证实例与原型之间的关系,返回 true/false
function Cat(name,color){
this.name = name;
this.color = color
}
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
cat1 instanceof Cat //true
cat2 instanceof Cat //true
构造函数存在的缺陷:浪费内存
造成原因:当我们为构造函数添加不变的属性或方法时,生成实例时,会为每个实例都生成一模一样的属性或方法,因此,占用内存,也缺乏运行效率
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = function(){
console.log("吃鱼");
}
}
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
cat1.eat == cat2.eat // false
因此,我们需要将构造函数中不变的属性或方法,在内存中只生成一次,然后所有的实例都指向这个内存地址,结果请看下面的原型链模式
原型链模式(prototype)
javascript规定,每次创建了函数,都会为函数添加一个 prototype 属性,指向函数的原型对象,默认情况下该原型的对象会自动有一个 constructor (构造函数) 属性,该属性指向构造函数,而原型对象中的所有属性和方法都被实例共享
我们将共同的属性或方法定义在prototype属性上,这样,所有的构造函数实例,都将直接继承原型链的属性或方法,减少了每次生成实例所产生的内存,同时有保证了,所有实例的这些属性或方法都执行同一个地址,提高运行效率
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.eat = function(){
console.log("吃鱼");
}
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
//所有实例的 eat 都指向同一个内存地址,指向 prototype 对象
cat1.eat == cat2.eat //true
结合下图理解原型链模式
原型链的验证方法
isPrototypeOf(),判断某个实例是否存在于某个 prototype 对象,返回 true/false
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.eat = function(){
console.log("吃鱼");
}
var cat1 = new Cat("大毛","白色");
var cat2 = new Cat("2毛","黑色");
//使用 isPrototypeOf 判断实例是否是某个 prototype 对象
Cat.prototype.isPrototypeOf(cat1); //true
hasOwnProperty(),判断某个属性是否是本地属性,返回true/false
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.eat = function(){
console.log("吃鱼");
}
Cat.prototype.type = "猫科";
var cat1 = new Cat("大毛","白色");
cat1.hasOwnProperty("name");//true
cat1.hasOwnProperty("type");//false
in,运算符,判断某个属性是否存在,不管是本地还是来自继承,返回 true/false
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.eat = function(){
console.log("吃鱼");
}
Cat.prototype.type = "猫科";
var cat1 = new Cat("大毛","白色");
"name" in cat1 // true
"type" in cat1 // true
注意! in,还可遍历一个对象是所有属性