1、原型、原型鏈javascript
原型對象
在JavaScript 中,每當定義一個對象(函數)時候,對象中都會包含一些預約義的屬性。其中函數對象的一個屬性就是原型對象 prototype。注:普通對象沒有prototype,但有__proto__屬性。
原型對象其實就是普通對象(Function.prototype除外,它是函數對象,但它很特殊,他沒有prototype屬性(前面說道函數對象都有prototype屬性))。看下面的例子:
function f1(){};
console.log(f1.prototype) //f1{}
console.log(typeof f1. prototype) //Object
console.log(typeof Function.prototype) // Function,這個特殊
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
從這句console.log(f1.prototype) //f1 {} 的輸出就結果能夠看出,f1.prototype就是f1的一個實例對象。就是在f1建立的時候,建立了一個它的實例對象並賦值給它的prototype,基本過程以下:
var temp = new f1();
f1. prototype = temp;
因此,Function.prototype爲何是函數對象就迎刃而解了,上文提到凡是new Function ()產生的對象都是函數對象,因此temp1是函數對象。
var temp1 = new Function ();
Function.prototype = temp1;
那原型對象是用來作什麼的呢?主要做用是用於繼承。舉了例子:
var person = function(name){
this.name = name
};
person.prototype.getName = function(){
return this.name;
}
var zjh = new person(‘zhangjiahao’);
zjh.getName(); //zhangjiahao
從這個例子能夠看出,經過給person.prototype設置了一個函數對象的屬性,那有person實例(例中:zjh)出來的普通對象就繼承了這個屬性。具體是怎麼實現的繼承,就要講到下面的原型鏈了。
三.原型鏈
JS在建立對象(不管是普通對象仍是函數對象)的時候,都有一個叫作__proto__的內置屬性,用於指向建立它的函數對象的原型對象prototype。以上面的例子爲例:
console.log(zjh.__proto__ === person.prototype) //true
一樣,person.prototype對象也有__proto__屬性,它指向建立它的函數對象(Object)的prototype
console.log(person.prototype.__proto__ === Object.prototype) //true
繼續,Object.prototype對象也有__proto__屬性,但它比較特殊,爲null
console.log(Object.prototype.__proto__) //null
咱們把這個有__proto__串起來的直到Object.prototype.__proto__爲null的鏈叫作原型鏈。以下圖:
四.內存結構圖
爲了更加深刻和直觀的進行理解,下面咱們畫一下上面的內存結構圖:
畫圖約定:
疑點解釋:
1.Object.__proto__ === Function.prototype // true
Object是函數對象,是經過new Function()建立,因此Object.__proto__指向Function.prototype。
2.Function.__proto__ === Function.prototype // true
Function 也是對象函數,也是經過new Function()建立,因此Function.__proto__指向Function.prototype。
本身是由本身建立的,好像不符合邏輯,但仔細想一想,現實世界也有些相似,你是怎麼來的,你媽生的,你媽怎麼來的,你姥姥生的,……類人猿進化來的,那類人猿從哪來,一直追溯下去……,就是無,(NULL生萬物)
正如《道德經》裏所說「無,名天地之始」。
3.Function.prototype.__proto__ === Object.prototype //true
其實這一點我也有點困惑,不過也能夠試着解釋一下。
Function.prototype是個函數對象,理論上他的__proto__應該指向 Function.prototype,就是他本身,本身指向本身,沒有意義。
JS一直強調萬物皆對象,函數對象也是對象,給他認個祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保證原型鏈可以正常結束。
五.constructor
原型對象prototype中都有個預約義的constructor屬性,用來引用它的函數對象。這是一種循環引用
person.prototype.constructor === person //true
Function.prototype.constructor === Function //true
Object.prototype.constructor === Object //true
完善下上面的內存結構圖:
有兩點須要注意:
(1)注意Object.constructor===Function;//true 自己Object就是Function函數構造出來的
(2)如何查找一個對象的constructor,就是在該對象的原型鏈上尋找碰到的第一個constructor屬性所指向的對象
六.總結
1.原型和原型鏈是JS實現繼承的一種模型。
2.原型鏈的造成是真正是靠__proto__ 而非prototypejava
2、閉包
安全
1、什麼是閉包? 閉包
官方」的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。
相信不多有人能直接看懂這句話,由於他描述的太學術。其實這句話通俗的來講就是:JavaScript中全部的function都是一個閉包。不過通常來講,嵌套的function所產生的閉包更爲強大,也是大部分時候咱們所謂的「閉包」。看下面這段代碼:函數
function a() {
var i = 0;
function b() { alert(++i); }
return b;
}
var c = a();
c();
這段代碼有兩個特色:性能
一、函數b嵌套在函數a內部;this
二、函數a返回函數b。spa
引用關係如圖:prototype
這樣在執行完var c=a()後,變量c其實是指向了函數b,再執行c()後就會彈出一個窗口顯示i的值(第一次爲1)。這段代碼其實就建立了一個閉包,爲何?由於函數a外的變量c引用了函數a內的函數b,就是說:code
當函數a的內部函數b被函數a外的一個變量引用的時候,就建立了一個閉包。
讓咱們說的更透徹一些。所謂「閉包」,就是在構造函數體內定義另外的函數做爲目標對象的方法函數,而這個對象的方法函數反過來引用外層函數體中的臨時變量。這使得只要目標 對象在生存期內始終能保持其方法,就能間接保持原構造函數體當時用到的臨時變量值。儘管最開始的構造函數調用已經結束,臨時變量的名稱也都消失了,但在目 標對象的方法內卻始終能引用到該變量的值,並且該值只能通這種方法來訪問。即便再次調用相同的構造函數,但只會生成新對象和方法,新的臨時變量只是對應新 的值,和上次那次調用的是各自獨立的。
2、閉包有什麼做用?
簡而言之,閉包的做用就是在a執行完並返回後,閉包使得Javascript的垃圾回收機制GC不會收回a所佔用的資源,由於a的內部函數b的執行須要依賴a中的變量。這是對閉包做用的很是直白的描述,不專業也不嚴謹,但大概意思就是這樣,理解閉包須要按部就班的過程。
在上面的例子中,因爲閉包的存在使得函數a返回後,a中的i始終存在,這樣每次執行c(),i都是自加1後alert出i的值。
那 麼咱們來想象另外一種狀況,若是a返回的不是函數b,狀況就徹底不一樣了。由於a執行完後,b沒有被返回給a的外界,只是被a所引用,而此時a也只會被b引 用,所以函數a和b互相引用但又不被外界打擾(被外界引用),函數a和b就會被GC回收。(關於Javascript的垃圾回收機制將在後面詳細介紹)
3、閉包內的微觀世界
若是要更加深刻的瞭解閉包以及函數a和嵌套函數b的關係,咱們須要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、做用域(scope)、做用域鏈(scope chain)。以函數a從定義到執行的過程爲例闡述這幾個概念。
到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的做用域鏈包含了對函數a的活動對象的引用,也就是說b能夠訪問到a中定義的全部變量和函數。函數b被c引用,函數b又依賴函數a,所以函數a在返回後不會被GC回收。
當函數b執行的時候亦會像以上步驟同樣。所以,執行時b的做用域鏈包含了3個對象:b的活動對象、a的活動對象和window對象,以下圖所示:
如圖所示,當在函數b中訪問一個變量的時候,搜索順序是:
小結,本段中提到了兩個重要的詞語:函數的定義與執行。文中提到函數的做用域是在定義函數時候就已經肯定,而不是在執行的時候肯定(參看步驟1和3)。用一段代碼來講明這個問題:
function f(x) {
var g = function () { return x; }
return g;
}
var h = f(1);
alert(h());
這段代碼中變量h指向了f中的那個匿名函數(由g返回)。
若是第一種假設成立,那輸出值就是undefined;若是第二種假設成立,輸出值則爲1。
運行結果證實了第2個假設是正確的,說明函數的做用域確實是在定義這個函數的時候就已經肯定了。
4、閉包的應用場景
保護函數內的變量安全。以最開始的例子爲例,函數a中i只有函數b才能訪問,而沒法經過其餘途徑訪問到,所以保護了i的安全性。
function Constructor(...) {
var that = this;
var membername = value;
function membername(...) {...}
}
以上3點是閉包最基本的應用場景,不少經典案例都源於此。
3、繼承
既然要實現繼承,那麼首先咱們得有一個父類,代碼以下:
// 定義一個動物類 function Animal (name) { // 屬性 this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); };
核心: 將父類的實例做爲子類的原型
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat.sleep()); console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
特色:
缺點:
new Animal()
這樣的語句以後執行,不能放到構造器中
核心:使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(沒用到原型)
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特色:
缺點:
核心:爲父類實例添加新特性,做爲子類實例返回
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false
特色:
new 子類()
仍是子類()
,返回的對象具備相同的效果
缺點:
function Cat(name){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特色:
缺點:
核心:經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
特色:
缺點:
核心:經過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 建立一個沒有實例方法的類 var Super = function(){}; Super.prototype = Animal.prototype; //將實例做爲子類的原型 Cat.prototype = new Super(); })(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true
特色:
缺點: