[toc]
##私有變量和函數
在函數內部定義的變量和函數,如果不對外提供接口,外部是無法訪問到的,也就是該函數的私有的變量和函數。
```
<script type="text/javascript">
function Box(){
var color = "blue";//私有變量
var fn = function() //私有函數
{
}
}
</script>
```
這樣在函數對象`Box`外部無法訪問變量`color`和`fn`,他們就變成私有的了:
```
var obj = new Box();
alert(obj.color);//彈出 undefined
alert(obj.fn);//同上
```
##靜態變量和函數
當定義一個函數后通過點號 `“.”`為其添加的屬性和函數,通過對象本身仍然可以訪問得到,但是其實例卻訪問不到,這樣的變量和函數分別被稱為`靜態變量`和`靜態函數`。
```
<script type="text/javascript">
function Obj(){};
Obj.num = 72;//靜態變量
Obj.fn = function() //靜態函數
{
}
alert(Obj.num);//72
alert(typeof Obj.fn)//function
var t = new Obj();
alert(t.name);//undefined
alert(typeof t.fn);//undefined
</script>
```
##實例變量和函數
在面向對象編程中除了一些庫函數我們還是希望在對象定義的時候同時定義一些屬性和方法,實例化后可以訪問,`js`也能做到這樣
```
<script type="text/javascript">
function Box(){
this.a=[]; //實例變量
this.fn=function(){ //實例方法
}
}
console.log(typeof Box.a); //undefined
console.log(typeof Box.fn); //undefined
var box=new Box();
console.log(typeof box.a); //object
console.log(typeof box.fn); //function
</script>
```
為實例變量和方法添加新的方法和屬性
```
<script type="text/javascript">
function Box(){
this.a=[]; //實例變量
this.fn=function(){ //實例方法
}
}
var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object
var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function
</script>
```
在`box1`中修改了`a`和`fn`,而在`box2`中沒有改變,由于數組和函數都是對象,是引用類型,這就說明`box1`中的屬性和方法與`box2`中的屬性與方法雖然同名但卻不是一個引用,而是對`Box`對象定義的屬性和方法的一個復制。
這個對屬性來說沒有什么問題,但是對于方法來說問題就很大了,因為方法都是在做完全一樣的功能,但是卻又兩份復制,如果一個函數對象有上千和實例方法,那么它的每個實例都要保持一份上千個方法的復制,這顯然是不科學的,這可腫么辦呢,`prototype`應運而生。
##基本概念
我們創建的每個函數都有一個`prototype`屬性,這個屬性是一個指針,指向一個對象,這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。那么,`prototype`就是通過調用構造函數而創建的那個對象實例的原型對象。
使用原型的好處是可以讓對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中添加定義對象信息,而是可以直接將這些信息添加到原型中。使用構造函數的主要問題就是每個方法都要在每個實例中創建一遍。
在`JavaScript`中,一共有兩種類型的值,原始值和對象值。每個對象都有一個內部屬性 `prototype` ,我們通常稱之為原型。原型的值可以是一個對象,也可以是`null`。如果它的值是一個對象,則這個對象也一定有自己的原型。這樣就形成了一條線性的鏈,我們稱之為`原型鏈`。
### 含義
函數可以用來作為構造函數來使用。另外只有函數才有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`,所以最終執行了該run()方法。(在這里,函數首字母大寫的都代表構造函數,以用來區分普通函數)
當調用構造函數創建一個實例的時候,實例內部將包含一個內部指針(`__proto__`)指向構造函數的`prototype`,這個連接存在于實例和構造函數的`prototype`之間,而不是實例與構造函數之間。
```
<script type="text/javascript">
function Person(name){ //構造函數
this.name=name;
}
Person.prototype.printName=function() //原型對象
{
alert(this.name);
};
var person1=new Person('Byron');//實例化對象
console.log(person1.__proto__);//Person
console.log(person1.constructor);//自己試試看會是什么吧
console.log(Person.prototype);//指向原型對象Person
var person2=new Person('Frank');
</script>
```
`Person`的實例person1中包含了name屬性,同時自動生成一個`__proto__`屬性,該屬性指向Person的prototype,可以訪問到`prototype`內定義的`printName`方法,大概就是這個樣子的:

每個`JavaScript`函數都有`prototype`屬性,這個屬性引用了一個對象,這個對象就是原型對象。原型對象初始化的時候是空的,我們可以在里面自定義任何屬性和方法,這些方法和屬性都將被該構造函數所創建的對象繼承。
那么,現在問題來了。構造函數、實例和原型對象三者之間有什么關系呢?
##構造函數、實例和原型對象的區別
實例就是通過構造函數創建的。實例一創造出來就具有`constructor`屬性(指向構造函數)和`__proto__`屬性(指向原型對象),
構造函數中有一個`prototype`屬性,這個屬性是一個指針,指向它的原型對象。
原型對象內部也有一個指針(`constructor`屬性)指向構造函數:`Person.prototype.constructor = Person;`
實例可以訪問原型對象上定義的屬性和方法。
在這里person1和person2就是實例,`prototype`是他們的原型對象。
再舉個栗子:
```
<script type="text/javascript">
function Animal(name) //積累構造函數
{
this.name = name;//設置對象屬性
}
Animal.prototype.behavior = function() //給基類構造函數的prototype添加behavior方法
{
alert("this is a "+this.name);
}
var Dog = new Animal("dog");//創建Dog對象
var Cat = new Animal("cat");//創建Cat對象
Dog.behavior();//通過Dog對象直接調用behavior方法
Cat.behavior();//output "this is a cat"
alert(Dog.behavior==Cat.behavior);//output true;
</script>
```
可以從程序運行結果看出,構造函數的`prototype`上定義的方法確實可以通過對象直接調用到,而且代碼是共享的。(可以試一下將`Animal.prototype.behavior` 中的`prototype`屬性去掉,看看還能不能運行。)在這里,prototype屬性指向Animal對象。
##數組對象實例
再看個數組對象的實例。當我們創建出array1這個對象的時候,array1實際在Javascript引擎中的對象模型如下:
```
var array1 = [1,2,3];
```

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

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

new操作符具體干了什么呢?其實很簡單,就干了三件事情。
```
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
```
##原型鏈
原型鏈:當從一個對象那里調取屬性或方法時,如果該對象自身不存在這樣的屬性或方法,就會去自己關聯的`prototype`對象那里尋找,如果`prototype`沒有,就會去`prototype`關聯的前輩`prototype`那里尋找,如果再沒有則繼續查找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`構造函數`prototype`屬性指向的那個原型對象。這個原型對象是所有對象的祖先,這個老祖宗實現了諸如`toString`等所有對象天生就該具有的方法。其他內置構造函數,如`Function,Boolean,String,Date`和`RegExp`等的`prototype`都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現出各自宗族的那些特征。
`ECMAScript`中,實現繼承的方法就是依靠原型鏈實現的。
```
<script type="text/javascript">
function Father(){ //被繼承的函數叫做超類型(父類,基類)
this.name = "Jack";
}
function Son(){ //繼承的函數叫做子類型(子類,派生類)
this.age = 10;
}
//通過原型鏈繼承,賦值給子類型的原型屬性
//new Father()會將father構造里的信息和原型里的信息都交給Son
Son.prototype = new Father();//Son繼承了Father,通過原型,形成鏈條
var son = new Son();
alert(son.name);//彈出 Jack
</script>
```
原型鏈的問題:原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中最主要的問題來自包含引用類型的值原型。包含引用類型的原型屬性會被所有實例共享;而這也正是為什么要在構造函數中,而不是在原型對象中定義屬性的原因。在通過原型來實現繼承時,原型實際上回變成另一個類型的實例。于是,原先的實例屬性也就變成了原型的屬性。
在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。再加上剛剛討論的由于原型中包含引用類型值所帶來的問題,實踐中很少會單獨使用原型鏈。
再舉個栗子:
```
<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");//創建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>
```
##__ptoto__屬性
`__ptoto__`屬性(IE瀏覽器不支持)是實例指向原型對象的一個指針,它的作用就是指向構造函數的原型屬性`constructor`,通過這兩個屬性,就可以訪問原型里的屬性和方法了。
`Javascript`中的對象實例本質上是由一系列的`屬性`組成的,在這些屬性中,有一個內部的不可見的特殊屬性——`__proto__`,該屬性的值指向該對象實例的原型,一個對象實例只擁有一個唯一的原型。
```
<script type="text/javascript">
function Box(){ //大寫,代表構造函數
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);//構造屬性,可以獲取構造函數本身,
//作用是被原型指針定位,然后得到構造函數本身
</script>
```
### `__proto__`屬性和prototype屬性的區別
`prototype`是`function`對象中專有的屬性。
`__proto__`是普通對象的隱式屬性,在new的時候,會指向`prototype`所指的對象;
`__ptoto__`實際上是某個實體對象的屬性,而`prototype`則是屬于構造函數的屬性。`__ptoto__`只能在學習或調試的環境下使用。
##原型模式的執行流程
1.先查找構造函數實例里的屬性或方法,如果有,就立即返回。
2.如果構造函數的實例沒有,就去它的原型對象里找,如果有,就立即返回
### 原型對象的
```
<script type="text/javascript">
function Box(){ //大寫,代表構造函數
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>
```
### 構造函數的
```
<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>
```
綜上,整理一下:
```
<script type="text/javascript">
function Person(){};
Person.prototype.name = "trigkit4";
Person.prototype.say = function(){
alert("?Hi");
}
var p1 = new Person();//prototype是p1和p2的原型對象
var p2 = new Person();//p2為實例化對象,其內部有一個__proto__屬性,指向Person的prototype
console.log(p1.prototype);//undefined,這個屬性是一個對象,訪問不到
console.log(Person.prototype);//Person
console.log(Person.prototype.constructor);//原型對象內部也有一個指針(constructor屬性)指向構造函數
console.log(p1.__proto__);//這個屬性是一個指針指向prototype原型對象
p1.say();//實例可以訪問到在原型對象上定義的屬性和方法
</script>
```
```
構造函數.prototype = 原型對象
原型對象.constructor = 構造函數(模板)
原型對象.isPrototypeof(實例對象) 判斷實例對象的原型 是不是當前對象
```
##工廠模式
```
function createObject(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
```
工廠模式解決了實例化對象大量重復的問題,但還有一個問題,那就是根本無法搞清楚他們到底是哪個對象的實例。
使用構造函數的方法,既解決了重復實例化的問題,又解決了對象識別的問題。
使用構造函數的方法和工廠模式的不同之處在于:
```
1.構造函數方法沒有顯示的創建對象(new Object());
2.直接將屬性和方法賦值給this對象
3.沒有return 語句
```
當使用了構造函數,并且`new` 構造函數(),那么就在后臺執行了`new Object()`;
函數體內的`this`代表了`new Object()`出來的對象
```
1.判斷屬性是在構造函數的實例里,還是在原型里,可以使用`hasOwnProperty()`函數
2.字面量創建的方式使用constructor屬性不會指向實例,而會指向Object,構造函數創建的方式則相反
為什么指向Object?因為Box.prototype = {};這種寫法其實就是創建了一個新對象。
而每創建一個函數,就會同時創建它的prototype,這個對象也會自動獲取constructor屬性
3.如果是實例方法,不同的實例化,他們的方法地址是不一樣的,是唯一的
4.如果是原型方法,那么他們的地址的共享的
```
##[擴展閱讀]JS閉包
###閉包的特性
閉包有三個特性:
1. 函數嵌套函數
2. 函數內部可以引用外部的參數和變量
3. 參數和變量不會被垃圾回收機制回收
###閉包的定義及其優缺點
閉包 是指有權訪問另一個函數作用域中的變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量
閉包的缺點就是常駐內存,會增大內存使用量,使用不當很容易造成內存泄露。
閉包是javascript語言的一大特點,主要應用閉包場合主要是為了:設計私有的方法和變量。
一般函數執行完畢后,局部活動對象就被銷毀,內存中僅僅保存全局作用域。但閉包的情況不同!
###嵌套函數的閉包
```
function aaa() {
var a = 1;
return function(){
alert(a++)
};
}
var fun = aaa();
fun();// 1 執行后 a++,,然后a還在~
fun();// 2
fun = null;//a被回收!!
```
閉包會使變量始終保存在內存中,如果不當使用會增大內存消耗。
###javascript的垃圾回收原理
(1)、在javascript中,如果一個對象不再被引用,那么這個對象就會被GC回收;
(2)、如果兩個對象互相引用,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收。
###使用閉包的好處
那么使用閉包有什么好處呢?使用閉包的好處是:
1. 希望一個變量長期駐扎在內存中
2. 避免全局變量的污染
3. 私有成員的存在
###一、全局變量的累加
```
<script>
var a = 1;
function abc(){
a++;
alert(a);
}
abc(); //2
abc(); //3
</script>
```
###二、局部變量
```
<script>
function abc(){
var a = 1;
a++;
alert(a);
}
abc(); //2
abc(); //2
</script>
```
那么怎么才能做到變量a既是局部變量又可以累加呢?
###三、局部變量的累加
```
<script>
function outer(){
var x=10;
return function(){ //函數嵌套函數
x++;
alert(x);
}
}
var y = outer(); //外部函數賦給變量y;
y(); //y函數調用一次,結果為11,相當于outer()();
y(); //y函數調用第二次,結果為12,實現了累加
</script>
```
函數聲明與函數表達式
在js中我們可以通過關鍵字function來聲明一個函數:
```
<script>
function abc(){
alert(123);
}
abc();
</script>
```
我們也可以通過一個"()"來將這個聲明變成一個表達式:
```
<script>
(function (){
alert(123);
})(); //然后通過()直接調用前面的表達式即可,因此函數可以不必寫名字;
</script>
```
###四、模塊化代碼,減少全局變量的污染
```
<script>
var abc = (function(){ //abc為外部匿名函數的返回值
var a = 1;
return function(){
a++;
alert(a);
}
})();
abc(); //2 ;調用一次abc函數,其實是調用里面內部函數的返回值
abc(); //3
</script>
```
###五、私有成員的存在
```
<script>
var aaa = (function(){
var a = 1;
function bbb(){
a++;
alert(a);
}
function ccc(){
a++;
alert(a);
}
return {
b:bbb, //json結構
c:ccc
}
})();
aaa.b(); //2
aaa.c(); //3
</script>
```
###六.使用匿名函數實現累加
//使用匿名函數實現局部變量駐留內存中,從而實現累加
```
<script type="text/javascript">
function box(){
var age = 100;
return function(){ //匿名函數
age++;
return age;
};
}
var b = box();
alert(b());
alert(b()); //即alert(box()());
alert(b());
alert(b); // function () {
// age++;
// return age;
// }
b = null; //解除引用,等待垃圾回收
</script>
```
過度使用閉包會導致性能的下降。函數里放匿名函數,則產生了閉包
###七、在循環中直接找到對應元素的索引
```
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title></title>
<script>
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){ //當點擊時for循環已經結束
alert(i);
};
}
}
</script>
</head>
<body>
<ul>
<li>123</li>
<li>456</li>
<li>789</li>
<li>010</li>
</ul>
</body>
</html>
```
###八、使用閉包改寫上面代碼
```
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title></title>
<script>
window.onload = function(){
var aLi = document.getElementsByTagName('li');
for (var i=0;i<aLi.length;i++){
(function(i){
aLi[i].onclick = function(){
alert(i);
};
})(i);
}
};
</script>
</head>
<body>
<ul>
<li>123</li>
<li>456</li>
<li>789</li>
</ul>
</body>
</html>
```
###九.內存泄露問題
由于IE的js對象和DOM對象使用不同的垃圾收集方法,因此閉包在IE中會導致內存泄露問題,也就是無法銷毀駐留在內存中的元素
```
function closure(){
var oDiv = document.getElementById('oDiv');//oDiv用完之后一直駐留在內存中
oDiv.onclick = function () {
alert('oDiv.innerHTML');//這里用oDiv導致內存泄露
};
}
closure();
//最后應將oDiv解除引用來避免內存泄露
function closure(){
var oDiv = document.getElementById('oDiv');
var test = oDiv.innerHTML;
oDiv.onclick = function () {
alert(test);
};
oDiv = null;
}
```
- JavaScript手冊
- Array函數
- String函數
- Date函數
- Mach函數
- Regexp函數
- Location函數
- Window 函數
- Other函數
- History函數
- Navigator函數
- Event函數
- Dom函數
- Json函數
- Sea.js手冊
- JavaScript學習總結
- 1.基礎部分
- 2.對象部分
- 3.BOM和DOM詳解
- 4.function函數部分
- 5.原型和原型鏈詳解
- 6.數據類型和JSON格式
- 7.Ajax和Http狀態字
- 8.正則表達式
- 9.事件詳解
- 前端相關網址
- 前端干貨文章
- JavaScript字符串常用的一些方法
- 前端開發知識體系
- JavaScript速成課
- 移動端開發技巧
- 移動端Web頁面問題解決方案
- 20個常用的CSS技巧
- 學習JavaScript設計模式
- 前端開發學習總結