面向对象编程
//自己手写案例
function Fruits(){ // 构造函数 } Fruits.prototype={ // 原型函数 name:"Joe", age:16, init:function(){ console.log('hello world') } } Apple=new Fruits(); //实例对象 Apple.init();//hello world Apple.init=function(){ console.log('我是实例方法'); } Apple.init();//我是实例方法 // console.log(Apple.__proto__) //实例可以通过原型链找到原型 console.log(Apple) console.log(Fruits.prototype) // console.log(Apple.constructor == Fruits) //false(如果没有原型对象,此处会为true,应该每一个实例都有一个constructor属性,默认调用prototype对象的constructor属性)
// console.log(Apple instanceof Fruits) //true Apple是Fruits原型对象的实例对象
以上案例,包括了构造函数,原型函数和实例对象,实例对象可以通过__proto__原型链找到原型,实例对象也可以自己定义函数,比如例子中的Apple.init,此时实例对象本身和原型对象上都有init帆方法,此时实例对象会优先调用本身的 方法,如果没有,再去prototype上找。
理解原型对象
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
function New(){ } console.log(New.prototype) console.log(New.prototype.constructor==New) //true
而通过这个构造函数,我们还可以为原型对象添加其他的属性和方法。
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,至于其他方法,则都是从Object继承来的。当构造函数创建了一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
ECMA-262第5版中管这个指针交[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox,Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要
明确的真正重要的一点是,这个连接存在于实例和构造函数的原型对象之间,而不是存在于实例与构造函数之间。(换句话说,实例对象与构造函数没有直接的关系)
方法:
isPrototypeOf()
如果[[Prototype]]指向调用isPrototypeOf()方法的对象(New.prototype),那这个方法就返回true
function New(){ } New.prototype={ name:'jane', getName:function(){ console.log(this.name); } } var fn=new New(); console.log(New.prototype.isPrototypeOf(fn));//true
在ECMAScript5中新增了一个方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值,例如:
Object.getPrototypeOf(fn)==New.prototype;//true
Object.getPrototypeOf(fn).name;//jane
Object.getPrototypeOf()返回的对象实际就是这个对象的原型。使用这个方法可以方便的取得一个对象的原型。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先是从对戏那个实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中
查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
首先js中对象(函数(function)也是对象),可以访问到原型(prototype), 然后你通过对象得到的原型(prototype)中也会有一个给对象的指针(即constructor) 例如 : function Person(){}; var pro = Person.prototype;//得到对象的原型 var obj = pro.constructor ; // 这样可以得到Person对象。 对象创建对象的实例就不多说了 var p = new Person(); // 创建一个对象实例 。 对象实例中也会隐含的包含一个指向prototype的指针(在safrai ,firefox,chrome,opera等浏览器中可以使用 '__proto__'来访问) 例如: var p2 = new Person(); var proro = p2.__proto__ ;// 可以得到对象原型(prototype)的引用 , var bool = p2.__proto__ == Person.prototype; alert(bool); // true 对象的原型和实例获取的原型是同一个对象, 总结: 对象和实例都可以访问到原型 对象:Person.prototype; 实例:p.__proto__ ; 原型可以访问到对象 Person.prototype.constructor; 则实例也可以访问到对象 p.__proto__.constructor ;
读阮一峰文章总结
一 生成对象的原始模式
假设把一个猫看成一个对象,它有‘颜色’和‘名字’两个属性,
var Cat={ name:''. color:'' }
现在我们需要根据原型对象的规格,生成两个实例
var cat1 = {}; // 创建一个空对象 cat1.name = "大毛"; // 按照原型对象的属性赋值 cat1.color = "黄色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色";
好了,这就是最简单的封装了,把两个属性封装在一个对象里面。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;而是实例与原型之间,没有任何的办法,可以看出有什么联系。
二 原始模式的改进
我们可以写一个函数,解决代码重复的问题
function Cat(name,color){ return{ name:name, color:color } }
然后生成实例对象,就等于是在调用函数
var cat1=new Cat('大黄','黄色'); console.log(cat1)//{name: "大黄", color: "黄色"}
var cat2=new Cat('二黄','白色');
console.log(cat1)//{name: "二黄", color: "白色"}
这种方法的问题依然是,cat1和cat2之间没有内在的联系,不能反映出它们是同一原型对象的实例。
三 构造函数模式
为了解决从原型对象生成实例的问题,javascript提供了一个构造函数(Constructor)模式
所谓构造函数,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new 构造符,就能生成实例,并且this变量会绑定在实例对象上。
比如,猫的原型对象 现在可以这样写,
function Cat(name,color){ this.name=name; this.color=color; }
现在就可以生成实例对象了。
var cat1 = new Cat("大毛","黄色"); var cat2 = new Cat("二毛","黑色"); alert(cat1.name); // 大毛 alert(cat1.color); // 黄色
这时cat1
和cat2
会自动含有一个constructor
属性,指向它们的构造函数。
alert(cat1.constructor == Cat); //true alert(cat2.constructor == Cat); //true
Javascript还提供了一个instanceof
运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
四 构造函数的问题
构造函数方法很好用,但是存在一个浪费内存的问题。
请看,我们现在为Cat
对象添加一个不变的属性type
(种类),再添加一个方法eat
(吃)。那么,原型对象Cat
就变成了下面这样:
function Cat(name,color){ this.name = name; this.color = color; this.type = "猫科动物"; this.eat = function(){alert("吃老鼠");}; }
//还是采用同样的方法生成实例
var cat1 = new Cat("大毛","黄色"); var cat2 = new Cat ("二毛","黑色"); alert(cat1.type); // 猫科动物 cat1.eat(); // 吃老鼠
表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。那就是对于每一个实例对象,type
属性和eat()
方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样既不环保,也缺乏效率。
alert(cat1.eat == cat2.eat); //false
能不能让type
属性和eat()
方法在内存中只生成一次,然后所有实例都指向那个内存地址呢?回答是可以的。
五、 Prototype模式
Javascript规定,每一个构造函数都有一个prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这意味着,我们可以把那些不变的属性和方法,直接定义在prototype
对象上。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃老鼠")};
然后,生成实例。
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 猫科动物
cat1.eat(); // 吃老鼠
这时所有实例的type
属性和eat()
方法,其实都是同一个内存地址,指向prototype
对象,因此就提高了运行效率。
alert(cat1.eat == cat2.eat); //true
六、 Prototype模式的验证方法
为了配合prototype
属性,Javascript定义了一些辅助方法,帮助我们使用它。,
6.1 isPrototypeOf()
这个方法用来判断,某个proptotype
对象和某个实例之间的关系。
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
6.2 hasOwnProperty()
每个实例对象都有一个hasOwnProperty()
方法,用来判断某一个属性到底是本地属性,还是继承自prototype
对象的属性。
alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false
6.3 in运算符
in
运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
alert("name" in cat1); // true
alert("type" in cat1); // true
in
运算符还可以用来遍历某个对象的所有属性。
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
七 原型链
我们知道所有的函数都有一个叫做toString的方法。那么这个方法到底是在哪里的呢?
先随意声明一个函数:
function add() {}
那么我们可以用如下的图来表示这个函数的原型链。
其中add是Function对象的实例。而Function的原型对象同时又是Object原型的实例。这样就构成了一条原型链。原型链的访问,其实跟作用域链有很大的相似之处,
他们都是一次单向的查找过程。因此实例对象能够通过原型链,访问到处于原型链上对象的所有属性与方法。这也是foo最终能够访问到处于Object原型对象上的toString方法的原因。
基于原型链的特性,我们可以很轻松的实现继承。
参考:
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html