js之建構函式、原型模式
目錄
單例模式
把描述同一事物的屬性放進同一個空間地址下,避免了全域性變數的干擾,這種開發的模式就是單例模式。
var singleton1={ fn:function{ } } var singleton2={ fn:function{ } } singleton1.fn(); singleton2.fn();
高階單例模式
採用了自執行函式閉包的作用,保護裡面的私有變數不受外界的干擾;同時,如果在閉包外面使用裡面的函式時,可以把其return作為返回值。
var niu = (function () { var num = 1; var niuCode = { fn:function () { console.log(num); }, sum:function () { console.log(num); } } return niuCode;//niuCode只是一個空間地址,所以定義一個niu來接收它。 })(); box.onclick = niu.fn;
工廠模式
把實現同一功能的程式碼放到一個函式中,當想實現這個功能時,執行這個函式即可;減少了程式碼的冗餘,“高內聚,低耦合”,實現了函式的封裝。
function createPerson(name,age) { var obj ={};// 建立一個物件 obj.name = name;// name獲取形參的值 obj.age = age; return obj;//return 出一個物件 } // p1接收了createPerson 的返回值; var p1 = createPerson("張三",12); var p2 = createPerson("王五",30);
建構函式
new
:操作符,在函式執行前加new
- 形成一個私有作用域之後,程式碼執行之前,會預設建立一個空的物件(堆記憶體);
- 並且改變了當前作用域下的this指向,讓其指向那個空物件;
- 當代碼執行完,把這個物件返回,這是瀏覽器預設返回的。
類:內建類和自定義類;所有的類都是函式;
例項:通過建構函式或者類new出來的都是一個例項。
普通函式和建構函式區別
- 普通函式 形成私有作用域-->形參賦值-->變數提升-->程式碼從上到下執行-->作用域是否銷燬
- 建構函式 形成私有作用域-->形參賦值-->變數提升-->建立物件,讓this指向這個物件-->程式碼從上到下執行-->return這個物件-->作用域是否銷燬
建構函式的特徵
- 建構函式中的this永遠指向例項
- 建構函式的函式名一般都是大寫,為了區分普通的函式。
- 如果建構函式,並且不需要引數的情況下,可以省略小括號。
- 如果return一個基本資料型別值,不能覆蓋內建的(即預設的返回的物件或者例項);如果return一個引用的資料型別值,會覆蓋預設的return的物件。
instanceof
:檢測當前例項是否屬於這個類的方法,屬於返回true,不屬於返回false。hasOwnProperty
:檢測當前屬性是否是私有屬性,如果是返回true;不是返回false
例項.hasOwnProperty(屬性)
//封裝一個檢測共有屬性的方法
function hasPublicProperty(obj,attr){
if(obj[attr}){//由於傳入的引數是字串,所以不能用obj.attr,可以用obj[attr]或者attr in obj;
if(obj.hasOwnProperty(attr)===false){
return true;
}else{
return false;
}
else{
return false;
}
}
}
hasPublicProperty(obj,"tostring")
//優化以後
function hasPublicProperty(obj,attr){
return attr in obj && !obj.hasOwnProperty(attr)?true:false;
}
hasPublicProperty(obj,"tostring")
原型模式
- 所有的函式資料型別都天生自帶一個屬性:prototype(原型),這個屬性的值是一個物件:儲存了當前類供例項調取使用的共有屬性和方法。瀏覽器會預設給它開闢一個堆記憶體。
- 在瀏覽器預設給prototype開闢的堆記憶體中,即prototype屬性中有一個天生自帶的屬性:constructor,這個屬性儲存的值是當前原型所屬的類(當前函式本身)。
- 所有的物件資料型別都天生自帶一個
__proto__
的屬性,這個屬性的屬性值指向當前例項所屬類的prototype(如果不能確定它是誰的例項,都是object的例項,即object.prototype) - 所有的函式,包括普通函式、類(內建類、自定義類)都是Function的一個例項。
(理解這四句話,基本上原型模式就算理解了)
原型鏈
它是一種基於
__proto__
向上查詢的機制。當我們操作例項的某個屬性或者方法的時候,首先找自己空間中私有的屬性或者方法
- 找到了,則結束查詢,使用自己私有的即可
- 沒有找到的話,則基於
__proto__
找所屬類的prototype,如果找到,就用這個共有的,如果沒找到,基於原型上的__proto__
繼續向上查詢,一直找到Object.prototype的原型為止,如果再沒有,操作的屬性或者方法不存,得到undefined。這樣通過__proto__向上查詢就會形成一個原型鏈。
原型重定向
在實際專案基於面向物件開發的時候(構造原型設計模式),我們根據需要,很多時候會重定向類的原型(讓類的原型指向自己開闢的堆記憶體)
當我們需要給類的原型批量設定屬性和方法的時候,一般都是讓原型重定向到自己建立的物件中
Fn.prototype={
aa:function()
}
存在的問題:
自己開闢的堆記憶體中沒有consturctor屬性,導致類的原型建構函式缺失。
解決:手動在堆記憶體增加constructor屬性
- 當原型重定向後,瀏覽器預設開闢的那個原型堆記憶體會被釋放掉,如果之前已經儲存了一些方法和屬性,這些東西都會丟失。(所以:內建類的原型不允許重定向到自己開闢的堆記憶體,因為內建類原型上自帶很多屬性和方法,重定向後都沒了,這樣是不被允許的)
內建類:內建類擴充套件時,如果和內建的方法名相同,會對其進行覆蓋。但是內建類原型的空間地址不可以被修改,只能向其中新增一些方法。所以
類.prototype={}只適用於自定義類,不能用於內建類,比如Array等這些內建類。
零碎知識點
私有屬性和公有屬性
- 私有屬性:自己堆記憶體中儲存的屬性相對自己來說是私有的
- 公有屬性:自己基於
__proto__
找到的屬性,想對自己來說是公有的
基於內建類的原型擴充套件方法,供它的例項調取使用
//實現陣列的去重
Array.prototype.myUnique=function myUnique(){//後邊函式的名字可加可不加
//方法中的this一般都是當前類的例項(也就是我們要操作的陣列)
var obj={};
for(var i=0;i<this.length;i++){
var item=this[i];
obj.hasOwnProperty(item)? (this[i]=this[this.length-1],this.length--,i--):obj[item]=item;
}
obj=null;//釋放堆記憶體
return this;
};
ary.myUnique();
//鏈式寫法求陣列最大值
var max=ary.myUnique().sort(function(a,b){return a-b}).pop();
- 我們增加的方法最好設定“my”字首(字首自己定),防止把內建方法重寫
JS的鏈式寫法
保證每一個方法執行返回的結果依然是當前類的例項,這樣就可以繼續調取方法使用了
ary.sort(function(a,b){return a-b}).reverse().pop();
//執行完返回的結果是刪除的那一項的值,所在再呼叫陣列方法會報錯,也可以接著使用其它內建類方法
建立變數之字面量建立和例項建立
var a=1;
var a1=new Number(1);
- 通過字面量方式建立的例項(基本資料型別的例項)不是一個標準的例項,不能使用
instanceof
進行檢測 - 通過new建立的例項不能通過
typeof
進行資料型別的檢測,都返回"object"
可列舉屬性和不可列舉屬性
用for...in...遍歷會輸出可列舉的屬性
- 可列舉屬性:私有的屬性和自定義的共有屬性。
- 不可列舉屬性:是公有屬性並且內建的屬性。
call、apply、bind
call
[Fn].call([this],[param]...)
fn.call:當前例項(函式Fn)通過原型鏈的查詢機制,找到Function.prototype上的call方法
fn.call():把找到的call方法執行
當call執行的時候,內部處理了一些事情
- 首先把要操作函式中的this關鍵字變為call方法第一個傳遞的引數值
- 把call方法第二個及第二個以後的實參獲取到
- 把要操作的函式執行,並且把第二個以後的傳遞進來的實參傳給函式
call中的細節(適用於apply和bind)
- 非嚴格模式下,如果引數不傳,或者第一個引數是null、undefined,this都指向window;
- 嚴格模式下,第一個引數是誰,this就指向誰(包括null、undefined),不傳this是undefined。
apply
apply
:和call基本上一模一樣,唯一區別在於傳參的方式。apply把需要傳遞給FN的引數放到一個數組(或者類陣列)中傳遞進去,雖然寫的是一個數組,但是也相當於FN接受的是陣列的每一項,而不是整個陣列。
bind
bind
:預處理this;提前改變this指向,但是FN不立即執行;bind的傳參和call方式傳參一致,需要一個個向裡傳。
fn.call(obj,10,20); //改變fn中的this,並且把fn立即執行
fn.bind(obj,10,20); //改變fn中的this,此時的fn並沒有執行(不相容IE6~8)