函數(shù)
JS設(shè)計(jì)得最出色的就是它的函數(shù)的實(shí)現(xiàn)蛇券。它幾乎接近完美。但是樊拓,想必你也能預(yù)料到纠亚,JS的函數(shù)也存在瑕疵。
所謂編程骑脱,就是將一組需求分解成一組函數(shù)與數(shù)據(jù)結(jié)構(gòu)的技能菜枷;
1. 函數(shù)對(duì)象
每個(gè)函數(shù)在創(chuàng)建時(shí)會(huì)附加兩個(gè)隱藏屬性:函數(shù)的上下文和實(shí)現(xiàn)函數(shù)行為的代碼;
函數(shù)的與眾不同之處在于它們可以被調(diào)用叁丧;
2. 函數(shù)字面量
3. 調(diào)用
調(diào)用一個(gè)函數(shù)會(huì)暫停當(dāng)前函數(shù)的執(zhí)行,傳遞控制權(quán)和參數(shù)給新函數(shù)岳瞭;
JS中一共有4種調(diào)用模式拥娄,方法調(diào)用模式,函數(shù)調(diào)用模式瞳筏,構(gòu)造器調(diào)用模式稚瘾,apply調(diào)用模式,這些模式在如何初始化關(guān)鍵參數(shù)this上存在差異姚炕;
4. 方法調(diào)用模式
當(dāng)一個(gè)函數(shù)被保存為對(duì)象的一個(gè)屬性時(shí)摊欠,我們稱它為一個(gè)方法。
當(dāng)一個(gè)方法被調(diào)用時(shí)柱宦,this被綁定到該對(duì)象些椒。
var myObject = {
value: 0,
increment: function( inc ){
this.value += typeof inc === 'number' ? inc : 1;
}
}
myObject.increment();
myObject.value ==> 1
myObject.increment(2);
myObject.value ==> 2
5. 函數(shù)調(diào)用模式
當(dāng)一個(gè)函數(shù)并非一個(gè)對(duì)象的屬性時(shí),那么它就是被當(dāng)做一個(gè)函數(shù)來(lái)調(diào)用的掸刊;
var add = function(a, b){
return a + b;
}
var sum = add(3, 4);
以此模式調(diào)用函數(shù)時(shí)免糕,this被綁定到全局對(duì)象。這是語(yǔ)言設(shè)計(jì)上的一個(gè)錯(cuò)誤S遣唷Jぁ!蚓炬。
倘若語(yǔ)言設(shè)計(jì)正確松逊,那么當(dāng)內(nèi)部函數(shù)被調(diào)用時(shí),this應(yīng)該仍然綁定到外部函數(shù)的this變量肯夏。
這個(gè)設(shè)計(jì)錯(cuò)誤的后果就是方法不能利用內(nèi)部函數(shù)來(lái)幫助它工作经宏,因?yàn)閮?nèi)部函數(shù)的this被綁定了錯(cuò)誤的值楼咳,所以不能共享該方法對(duì)對(duì)象的訪問(wèn)權(quán)!
var add = function(a,b){
console.log(this) //此處的this指向window對(duì)象V蛐簟D噶!
}
var obj = {
age: 21,
value: 2,
double: function(){
var helper = function(){
this.value = this.age;
console.log(this)
}
helper()
}
}
console.log(obj.double()) ==> undefined;
//同樣
var obj = {
age: 21,
value: 2,
double: function(){
function helper(){
this.value = this.age;
console.log(this)
}
helper()
}
}
console.log(obj.double()) ==> undefined;
得到undefined的原因:以函數(shù)調(diào)用模式調(diào)用函數(shù)時(shí)缚柏,this被綁定到了全局對(duì)象苹熏,在helper內(nèi)部的this是window
注意:之前我一直以為helper內(nèi)部的this是其內(nèi)部作用域,所以this只能代表其內(nèi)部币喧,而不能去訪問(wèn)父元素轨域,今天才知道這個(gè)this竟然是一個(gè)全局變量!I辈汀干发!
解決辦法:
var obj = {
age: 21,
value: 2,
double: function(){
var that = this;
var helper = function(){
that.value = that.age;
console.log(that.value)
}
helper()
}
}
console.log(obj.double()) ==> 21;
6. 構(gòu)造器調(diào)用模式
JS是一門(mén)基于原型繼承的語(yǔ)言。這意味著對(duì)象可以直接從其他對(duì)象繼承屬性史翘。該語(yǔ)言是無(wú)類型的枉长;
如果在一個(gè)函數(shù)面前帶上new來(lái)調(diào)用,那么背地里將會(huì)創(chuàng)建一個(gè)連接到該函數(shù)的prototype成員的新對(duì)象琼讽,同時(shí)this會(huì)被綁定到那個(gè)新對(duì)象上必峰。
代碼舉例:
//注意,按照約定钻蹬,構(gòu)造函數(shù)命名應(yīng)該以大寫(xiě)字母開(kāi)頭
var Quo = function(string){
this.status = string;
}
//給 Quo 所有實(shí)例提供一個(gè)名為get_status的公共方法
Quo.prototype.get_status = function() {
return this.status;
}
//構(gòu)造一個(gè) Quo 實(shí)例
var myQuo = new Quo('confused');
myQuo.get_status() ==> confused;
一個(gè)函數(shù)吼蚁,如果創(chuàng)建的目的就是希望結(jié)合 new 前綴來(lái)調(diào)用,那它就被稱為構(gòu)造函數(shù)
我不推薦使用這種形式的構(gòu)造函數(shù)问欠。在下一章中我們會(huì)看到更好的替代方式(《JavaScript語(yǔ)言精粹》這本書(shū)一大特點(diǎn)就是肝匆,一個(gè)東西講完后,它會(huì)告訴你顺献,這個(gè)東西我們不建議使用旗国,要想看到更好的替代方式,請(qǐng)看下一章滚澜,真是逼著你去學(xué)習(xí)下一章4植帧!设捐!)
7. Apply 調(diào)用模式
apply方法讓我們構(gòu)建一個(gè)參數(shù)數(shù)組傳遞給調(diào)用函數(shù)借浊,它也允許我們選擇this的值。
apply方法接收兩個(gè)參數(shù)萝招,第1個(gè)是要綁定給this的值蚂斤,第2個(gè)就是一個(gè)參數(shù)數(shù)組。
var add = function(a, b) {
return a + b;
}
//構(gòu)造一個(gè)包含兩個(gè)數(shù)字的數(shù)組槐沼,并將它們相加
var array = [3,4];
var sum = add.apply(null, array) // sum值為7
參數(shù)
當(dāng)函數(shù)被調(diào)用時(shí)曙蒸,會(huì)得到一個(gè)“免費(fèi)”配送的參數(shù)捌治,那就是arguments數(shù)組。函數(shù)可以通過(guò)此參數(shù)訪問(wèn)所有它被調(diào)用時(shí)傳遞給它的參數(shù)列表纽窟,包括那些沒(méi)有被分配給函數(shù)聲明時(shí)定義的形式參數(shù)的多余參數(shù)肖油。
var sum = function(){
var i, sum = 0;
for(i = 0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
}
sum(4,8,15,16,23,42) ==> 108
因?yàn)檎Z(yǔ)言的一個(gè)設(shè)計(jì)錯(cuò)誤,arguments并不是一個(gè)真正的數(shù)組臂港。它只是一個(gè)“類似數(shù)組”的對(duì)象森枪,arguments擁有一個(gè)length屬性,但它沒(méi)有任何數(shù)組的方法审孽;
1. 返回
return 語(yǔ)句可用來(lái)使函數(shù)提前返回县袱,當(dāng)return被執(zhí)行時(shí),函數(shù)立即返回而不再執(zhí)行余下的語(yǔ)句佑力;
如果一個(gè)函數(shù)沒(méi)有指定返回值式散,則返回 undefined;
var Vue = function(a){
return this
}
Vue.prototype.returnThis = function(){
return this;
}
var vue = new Vue();
vue.returnThis() ==》 Vue{}
2. 異常
(1) throw
var add = function(a, b){
if( typeof a !== 'number' || typeof b !== 'number' ){
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
add(3,'ww') ==> { name: 'TypeError', message: 'add needs numbers' }
(2) try語(yǔ)句的catch從句
var try_it = function(){
try {
add('seven');
} catch(e) {
document.writenIn(e.name + ':' + e.message);
}
}
try_it();
一個(gè)try語(yǔ)句只會(huì)有一個(gè)捕獲所有異常的catch代碼塊
3. 擴(kuò)充類型的功能
通過(guò)給基本類型增加方法打颤。我們可以極大地提高語(yǔ)言的表現(xiàn)力暴拄。
給Number原型增加方法來(lái)提取數(shù)字中的整數(shù)部分
Number.method('integer', function(){
return Math[this < 0 ? 'ceil' : 'floor'](this);
})
(-10/3).integer();
給String原型添加一個(gè)移除字符串首尾空白的方法
String.method('trim', function(){
return this.replace(/^\s+|\s+$/g, '');
})
通過(guò)給基本類型添加方法,我們可以極大地提高語(yǔ)言表現(xiàn)力瘸洛。
其實(shí)揍移,對(duì)于這一點(diǎn),我個(gè)人建議盡量使用Jquery反肋,underscore等庫(kù)來(lái)實(shí)現(xiàn)各種基本類型和引用類型的處理。因?yàn)樵谠玩溕咸砑舆^(guò)多方法會(huì)出現(xiàn)一些問(wèn)題踏施,比如結(jié)構(gòu)混亂石蔗,代碼不好維護(hù)等。
4. 遞歸
遞歸函數(shù)就是會(huì)直接或間接地調(diào)用自身的一種函數(shù)畅形。一般來(lái)說(shuō)养距,一個(gè)遞歸函數(shù)調(diào)用自身去解決它的子問(wèn)題;
遞歸函數(shù)可以非常高效的操作樹(shù)形結(jié)構(gòu)日熬。比如瀏覽器的文檔對(duì)象模型(DOM)棍厌,每次遞歸調(diào)用時(shí)處理指定的樹(shù)的一段。
5. 作用域
在編程語(yǔ)言中竖席,作用域控制著變量與參數(shù)的可見(jiàn)性及生命周期耘纱。它減少了命名沖突,并且提供了自動(dòng)內(nèi)存管理毕荐。
var foo = function(){
var a = 3, b = 5;
var bar = function(){
var b = 7, c = 11;
//此時(shí)束析,a = 3, b = 7, c = 11
a += b + c;
//此時(shí),a = 21, b = 7, c = 11
}
//此時(shí)憎亚,a = 3, b = 5, c未定義
bar();
//此時(shí)员寇,a = 21, b = 5
}
任何一對(duì)花括號(hào)({和})中的語(yǔ)句集都屬于一個(gè)塊弄慰,在這之中定義的所有變量在代碼塊外都是不可見(jiàn)的,我們稱之為塊級(jí)作用域蝶锋。
函數(shù)作用域就好理解了(__) 陆爽,定義在函數(shù)中的參數(shù)和變量在函數(shù)外部是不可見(jiàn)的。
JS并不支持塊級(jí)作用域扳缕,它只支持函數(shù)作用域慌闭,而且在一個(gè)函數(shù)中的任何位置定義的變量在該函數(shù)中的任何地方都是可見(jiàn)的
因?yàn)镴S缺少塊級(jí)作用域,只支持函數(shù)作用域第献, 所以贡必,最好的做法是在函數(shù)體的頂部聲明函數(shù)中可能用到的所有變量。因?yàn)橛购粒谝粋€(gè)函數(shù)體內(nèi)任何位置定義的變量仔拟,在函數(shù)內(nèi)部任何地方都可見(jiàn)。
functin test(){
for(var i=0;i<3;i++){
}
alert(i);
}
test();
//因?yàn)镴S沒(méi)有塊級(jí)作用域飒赃,所以會(huì)彈出 i 為 3利花,可見(jiàn),在塊外载佳,塊中定義的變量i仍然是可以訪問(wèn)的
是否還記得炒事,在一個(gè)函數(shù)中定義的變量,當(dāng)這個(gè)函數(shù)調(diào)用完后蔫慧,變量會(huì)被銷毀挠乳,我們是否可以用這個(gè)特性來(lái)模擬出JS的塊級(jí)作用域呢?
function test(){
(function(){
for(var i = 0; i < 3; i++ ){
}
})();
alert(i);
}
test();
會(huì)報(bào)錯(cuò):i is not defined
這里姑躲,我們把for語(yǔ)句塊放到了一個(gè)閉包之中睡扬,然后調(diào)用這個(gè)函數(shù),當(dāng)函數(shù)調(diào)用完畢黍析,變量i自動(dòng)銷毀卖怜,因此,我們?cè)趬K外便無(wú)法訪問(wèn)了
閉包
作用域的好處是內(nèi)部函數(shù)可以訪問(wèn)定義它們的外部函數(shù)的參數(shù)和變量(除了this和arguments)阐枣,這太美妙啦马靠!
一個(gè)更有趣的情形是:內(nèi)部函數(shù)擁有比它的外部函數(shù)更長(zhǎng)的生命周期。
var myObject = {
value: 0,
increment: function( inc ){
this.value += typeof inc === 'number' ? inc : 1;
}
}
如果我們要實(shí)現(xiàn)保護(hù)myObject中的value值不被非法修改蔼两,怎么做甩鳄?
var myObject = (function() {
var value = 0;
return {
increment: function( inc ){
value += typeof inc === 'number' ? inc : 1;
},
getValue: function(){
return value;
}
};
}());
該函數(shù)返回一個(gè)對(duì)象字面量。返回一個(gè)包含兩個(gè)方法的對(duì)象宪哩,并且這些方法繼續(xù)享有訪問(wèn)value變量的特權(quán)娩贷。
myObject中的value對(duì)于increment和getValue方法總是可用的,但函數(shù)的作用域使得它對(duì)其他的程序來(lái)說(shuō)是不可見(jiàn)的K稀彬祖!
我們?cè)賮?lái)看一個(gè)例子:
var quo = function(status){
return {
get_status: function(){
return status;
}
};
};
var myQuo = quo('amazed');
myQuo.get_status();
一個(gè)有用的例子:把DOM節(jié)點(diǎn)設(shè)置為黃色茁瘦,然后漸變?yōu)榘咨?/p>
var fade = function(node){
var level = 1;
var step = function(){
var hex = level.toString(16);
node.style.backgroundColor = '#FFFF' + hex + hex;
if( level < 15 ){
level++;
setTimeout(step,100)
}
}
setTimeout(step, 100)
}
fade(document.body);
7 模塊
通過(guò)使用函數(shù)產(chǎn)生模塊,我們幾乎可以完全摒棄全局變量的使用储笑,從而緩解這個(gè)js的最為糟糕的特性之一所帶來(lái)的影響
模塊形式舉例:
var obj = function(){
var seq = 0;
var preFix = '';
return {
set_prefix: function(p){
prefix = String(p)
},
set_seq: function(s){
seq = s;
}
}
}
8 級(jí)聯(lián)
其實(shí)就是通過(guò)在函數(shù)內(nèi)部 return this 來(lái)實(shí)現(xiàn)函數(shù)的鏈?zhǔn)秸{(diào)用甜熔;
9 柯里化
10 記憶
通過(guò)緩存變量,從而避免無(wú)謂的重復(fù)計(jì)算突倍;