javascript學習筆記(五)原型和原型鏈詳解
私有變量和函數(shù)
在函數(shù)內部定義的變量和函數(shù),如果不對外提供接口,外部是無法訪問到的,也就是該函數(shù)的私有的變量和函數(shù)。
<script type="text/javascript">
function Test(){
var color = "blue";//私有變量
var fn = function() //私有函數(shù)
{
}
}
</script>
這樣在函數(shù)對象Test外部無法訪問變量color和fn,他們就變成私有的了:
var obj = new Test();
alert(obj.color);//彈出 undefined
alert(obj.fn);//同上
靜態(tài)變量和函數(shù)
當定義一個函數(shù)后通過點號 “.”為其添加的屬性和函數(shù),通過對象本身仍然可以訪問得到,但是其實例卻訪問不到,這樣的變量和函數(shù)分別被稱為靜態(tài)變量和靜態(tài)函數(shù)。
<script type="text/javascript">
function Obj(){
}
Obj.num = 72;//靜態(tài)變量
Obj.fn = function() //靜態(tài)函數(shù)
{
}
alert(Obj.num);//72
alert(typeof Obj.fn)//function
var t = new Obj();
alert(t.name);//undefined
alert(typeof t.fn);//undefined
</script>
實例變量和函數(shù)
在面向對象編程中除了一些庫函數(shù)我們還是希望在對象定義的時候同時定義一些屬性和方法,實例化后可以訪問,JavaScript也能做到這樣
<script type="text/javascript">
function Obj(){
this.a=[]; //實例變量
this.fn=function(){ //實例方法
}
}
console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined
var o=new Obj();
console.log(typeof o.a); //object
console.log(typeof o.fn); //function
</script>
為實例變量和方法添加新的方法和屬性
<script type="text/javascript">
function Obj(){
this.a=[]; //實例變量
this.fn=function(){ //實例方法
}
}
var o1=new Obj();
o1.a.push(1);
o1.fn={};
console.log(o1.a); //[1]
console.log(typeof o1.fn); //object
var o2=new Obj();
console.log(o2.a); //[]
console.log(typeof o2.fn); //function
</script>
在o1中修改了a和fn,而在o2中沒有改變,由于數(shù)組和函數(shù)都是對象,是引用類型,這就說明o1中的屬性和方法與o2中的屬性與方法雖然同名但卻不是一個引用,而是對Obj對象定義的屬性和方法的一個復制。
這個對屬性來說沒有什么問題,但是對于方法來說問題就很大了,因為方法都是在做完全一樣的功能,但是卻又兩份復制,如果一個函數(shù)對象有上千和實例方法,那么它的每個實例都要保持一份上千個方法的復制,這顯然是不科學的,這可腫么辦呢,prototype應運而生。
基本概念
我們創(chuàng)建的每個函數(shù)都有一個prototype屬性,這個屬性是一個指針,指向一個對象,這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。那么,prototype就是通過調用構造函數(shù)而創(chuàng)建的那個對象實例的原型對象。
使用原型的好處是可以讓對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數(shù)中添加定義對象信息,而是可以直接將這些信息添加到原型中。使用構造函數(shù)的主要問題就是每個方法都要在每個實例中創(chuàng)建一遍。
在JavaScript中,一共有兩種類型的值,原始值和對象值。每個對象都有一個內部屬性 prototype ,我們通常稱之為原型。原型的值可以是一個對象,也可以是null。如果它的值是一個對象,則這個對象也一定有自己的原型。這樣就形成了一條線性的鏈,我們稱之為原型鏈。
含義
函數(shù)可以用來作為構造函數(shù)來使用。另外只有函數(shù)才有prototype屬性并且可以訪問到,但是對象實例不具有該屬性,只有一個內部的不可訪問的__proto__屬性。__proto__是對象中一個指向相關原型的神秘鏈接。按照標準,__proto__是不對外公開的,也就是說是個私有屬性,但是Firefox的引擎將他暴露了出來成為了一個共有的屬性,我們可以對外訪問和設置。
<script type="text/javascript">
var Browser = function(){};
Browser.prototype.run = function(){
alert("I'm Gecko,a kernel of firefox");
}
var Bro = new Browser();
Bro.run();
</script>
當我們調用Bro.run()方法時,由于Bro中沒有這個方法,所以,他就會去他的__proto__中去找,也就是Browser.prototype,所以最終執(zhí)行了該run()方法。(在這里,函數(shù)首字母大寫的都代表構造函數(shù),以用來區(qū)分普通函數(shù))
當調用構造函數(shù)創(chuàng)建一個實例的時候,實例內部將包含一個內部指針(__proto__)指向構造函數(shù)的prototype,這個連接存在于實例和構造函數(shù)的prototype之間,而不是實例與構造函數(shù)之間。
<script type="text/javascript">
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
</script>
Person的實例person1中包含了name屬性,同時自動生成一個__proto__屬性,該屬性指向Person的prototype,可以訪問到prototype內定義的printName方法,大概就是這個樣子的:

再舉個栗子:
<script type="text/javascript">
function Animal(name) //積累構造函數(shù)
{
this.name = name;//設置對象屬性
}
Animal.prototype.behavior = function() //給基類構造函數(shù)的prototype添加behavior方法
{
alert("this is a "+this.name);
}
var Dog = new Animal("dog");//創(chuàng)建Dog對象
var Cat = new Animal("cat");//創(chuàng)建Cat對象
Dog.behavior();//通過Dog對象直接調用behavior方法
Cat.behavior();//output "this is a cat"
alert(Dog.behavior==Cat.behavior);//output true;
</script>
可以從程序運行結果看出,構造函數(shù)的prototype上定義的方法確實可以通過對象直接調用到,而且代碼是共享的。(可以試一下將Animal.prototype.behavior 中的prototype屬性去掉,看看還能不能運行。)在這里,prototype屬性指向Animal對象。
數(shù)組對象實例
再看個數(shù)組對象的實例。當我們創(chuàng)建出array1這個對象的時候,array1實際在Javascript引擎中的對象模型如下:
var array1 = [1,2,3];

array1對象具有一個length屬性值為3,但是我們可以通過如下的方法來為array1增加元素:
array1.push(4);
push這個方法來自于array1的__proto__成員指向對象的一個方法(Array.prototye.push())。正是因為所有的數(shù)組對象(通過[]來創(chuàng)建的)都包含有一個指向同一個具有push,reverse等方法對象(Array.prototype)的__proto__成員,才使得這些數(shù)組對象可以使用push,reverse等方法。
函數(shù)對象實例
function Base() {
this.id = "base"
}

var obj = new Base();
這樣代碼的結果是什么,我們在Javascript引擎中看到的對象模型是:

new操作符具體干了什么呢?其實很簡單,就干了三件事情。
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
原型鏈
原型鏈:當從一個對象那里調取屬性或方法時,如果該對象自身不存在這樣的屬性或方法,就會去自己關聯(lián)的prototype對象那里尋找,如果prototype沒有,就會去prototype關聯(lián)的前輩prototype那里尋找,如果再沒有則繼續(xù)查找Prototype.Prototype引用的對象,依次類推,直到Prototype.….Prototype為undefined(Object的Prototype就是undefined)從而形成了所謂的“原型鏈”。
<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}
TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();
</script>
這里,用構造器Shape()新建了一個實體,然后用它去覆蓋該對象的原型。
<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}
TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();
TwoShape.prototype.constructor = TwoShape;
Triangle.prototype.constructor = Triangle;
var my = new Triangle(5,10);
my.getArea();
my.toString();//Triangle
my.constructor;//Triangle(side,height)
</script>
原型繼承
原型繼承:在原型鏈的末端,就是Object構造函數(shù)prototype屬性指向的那個原型對象。這個原型對象是所有對象的祖先,這個老祖宗實現(xiàn)了諸如toString等所有對象天生就該具有的方法。其他內置構造函數(shù),如Function,Boolean,String,Date和RegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現(xiàn)出各自宗族的那些特征。
ECMAScript中,實現(xiàn)繼承的方法就是依靠原型鏈實現(xiàn)的。
<script type="text/javascript">
function Box(){ //被繼承的函數(shù)叫做超類型(父類,基類)
this.name = "Jack";
}
function Tree(){ //繼承的函數(shù)叫做子類型(子類,派生類)
this.age = 300;
}
//通過原型鏈繼承,賦值給子類型的原型屬性
//new Box()會將box構造里的信息和原型里的信息都交給Tree
Tree.prototype = new Box();//Tree繼承了Box,通過原型,形成鏈條
var tree = new Tree();
alert(tree.name);//彈出 Jack
</script>
原型鏈的問題:原型鏈雖然很強大,可以用它來實現(xiàn)繼承,但它也存在一些問題。其中最主要的問題來自包含引用類型的值原型。包含引用類型的原型屬性會被所有實例共享;而這也正是為什么要在構造函數(shù)中,而不是在原型對象中定義屬性的原因。在通過原型來實現(xiàn)繼承時,原型實際上回變成另一個類型的實例。于是,原先的實例屬性也就變成了原型的屬性。
在創(chuàng)建子類型的實例時,不能向超類型的構造函數(shù)中傳遞參數(shù)。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數(shù)傳遞參數(shù)。再加上剛剛討論的由于原型中包含引用類型值所帶來的問題,實踐中很少會單獨使用原型鏈。
再舉個栗子:
<script type="text/javascript">
function Person(name)
{
this.name = name;//設置對象屬性
};
Person.prototype.company = "Microsoft";//設置原型的屬性
Person.prototype.SayHello = function() //原型的方法
{
alert("Hello,I'm "+ this.name+ " of " + this.company);
};
var BillGates = new Person("BillGates");//創(chuàng)建person對象
BillGates.SayHello();//繼承了原型的內容,輸出"Hello,I'm BillGates of Microsoft"
var Jobs = new Person("Jobs");
Jobs.company = "Apple";//設置自己的company屬性,掩蓋了原型的company屬性
Jobs.SayHello = function()
{
alert("Hi,"+this.name + " like " + this.company);
};
Jobs.SayHello();//自己覆蓋的屬性和方法,輸出"Hi,Jobs like Apple"
BillGates.SayHello();//Jobs的覆蓋沒有影響原型,BillGates還是照樣輸出
</script>
看下面一個原型鏈例子:
<script type="text/javascript">
function Year(){
this.value = 21;
}
Year.prototype = {
method:function(){
}
};
function Hi(){
};
//設置Hi的prototype屬性為Year的實例對象
Hi.prototype = new Year();
Hi.prototype.year = 'Hello World';
Hi.prototype.constructor = Hi;
var test = new Hi();//創(chuàng)建一個Hi的新實例
//原型鏈
test [Hi的實例]
Hi.prototype [Year的實例]
{year:'Hello World'}
Year.prototype
{method:……};
object.prototype
{toString:...};
</script>
從上面例子中,test對象從Hi.prototype和Year.prototype中繼承下來;因此他能訪問Year的原型方法method,同時他能訪問實例屬性value
__ptoto__屬性
__ptoto__屬性(IE瀏覽器不支持)是實例指向原型對象的一個指針,它的作用就是指向構造函數(shù)的原型屬性constructor,通過這兩個屬性,就可以訪問原型里的屬性和方法了。
Javascript中的對象實例本質上是由一系列的屬性組成的,在這些屬性中,有一個內部的不可見的特殊屬性——__proto__,該屬性的值指向該對象實例的原型,一個對象實例只擁有一個唯一的原型。
<script type="text/javascript">
function Box(){ //大寫,代表構造函數(shù)
Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}
var box1 = new Box();
var box2 = new Box();
alert(box1.constructor);//構造屬性,可以獲取構造函數(shù)本身,
//作用是被原型指針定位,然后得到構造函數(shù)本身
</script>
__proto__屬性和prototype屬性的區(qū)別
prototype是function對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__ptoto__實際上是某個實體對象的屬性,而prototype則是屬于構造函數(shù)的屬性。__ptoto__只能在學習或調試的環(huán)境下使用。
原型模式的執(zhí)行流程
1.先查找構造函數(shù)實例里的屬性或方法,如果有,就立即返回。
2.如果構造函數(shù)的實例沒有,就去它的原型對象里找,如果有,就立即返回
原型對象的
<script type="text/javascript">
function Box(){ //大寫,代表構造函數(shù)
Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}
var box1 = new Box();
alert(box1.name);//trigkit4,原型里的值
box1.name = "Lee";
alert(box1.name);//Lee,就進原則
var box2 = new Box();
alert(box2.name);//trigkit4,原型的值,沒有被box1修改
</script>
構造函數(shù)的
<script type="text/javascript">
function Box(){
this.name = "Bill";
}
Box.prototype.name = "trigkit4";//原型屬性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
var box1 = new Box();
alert(box1.name);//Bill,原型里的值
box1.name = "Lee";
alert(box1.name);//Lee,就進原則
</script>
相關文章
js中一維數(shù)組和二位數(shù)組中的幾個問題示例說明
這篇文章主要介紹了js中一維數(shù)組和二位數(shù)組中的幾個問題,并給出對應的解決方法,需要的朋友可以參考下2014-07-07
JavaScript高級程序設計(第3版)學習筆記3 js簡單數(shù)據(jù)類型
數(shù)據(jù)類型是編程語言的磚瓦,是所有你能想象到的復雜抽象的基礎,在現(xiàn)代編程語言中,除了語言本身內置的一些簡單數(shù)據(jù)類型外,基本上都提供了用于自定義數(shù)據(jù)類型的語言機制(在C中也可以利用結構體來實現(xiàn)),這些機制在一定程度上也決定了該語言的流行度和生命力2012-10-10
javascript設計模式之對象工廠函數(shù)與構造函數(shù)詳解
這篇文章主要介紹了javascript設計模式之對象工廠函數(shù)與構造函數(shù)詳解,使用對象字面量,或者向空對象中動態(tài)地添加新成員,是最簡單易用的對象創(chuàng)建方法,除了這兩種常用的對象創(chuàng)建方式,JavaScript還提供了其他方法創(chuàng)建對象,需要的朋友可以參考下2015-07-07

