字符集其實很簡單
函式知識
函式是JavaScript世界裡第一等公民,在這個世界裡到處都是函式;而函式帶有一個特別重要的絕招——定義 作用域,在 ECMAScript 6 之前,只有函式才有這個絕招
函式常見的四種形態
-
函式的宣告形態
function func(){
console.log("函式的宣告形態");
}
-
函式的表示式形態
let func0 = function(){
console.log("函式的表示式形態一")
}
(function func1(){console.log("函式的表示式形態二")})
-
函式巢狀形態
let func2 = function(){
console.log("函式的巢狀形態");
let func3 = function(){
console.log("func2巢狀在func1裡")
}
func3();
}
-
函式的閉包形態
let func4 = function(){
var n = "ow";
return function(){
console.log("我是以閉包形態存在的函式:"+ n)
}
}
函式宣告提升
只有宣告形態的函式,才具有提升的特性。所謂的提升,意思就是程式碼的執行順序提升排到最前面。
console.log(fn) //function(){return:0}
console.log(fn1) // undefined
//函式的宣告形態
function fn(){
return 0;
}
//函式的表示式形態
var fn1 = function(){
return 1;
}
IIFE 與 匿名函式、有名函式
IIFE(Immediately-Invoked Function Expression, 立即執行函式)形式的函式呼叫方式,非常適合匿名函式呼叫。如一下程式碼,兩種表達方式
(function(){console.log("我是立即執行的匿名函式")})();
(function(){console.log("我也是立即執行的匿名函式")}());
既然匿名函式讓程式碼這麼簡潔,為什麼還需要給函式起名--有名函式呢? 首先,最好的理由之一當然是為了方便遞迴,遞迴需要函式呼叫自身,函式如果沒有名字,就無法有效地通過一個標誌符(名字)找到函式自身以便供呼叫。函式的名字可以通過 name 屬性讀取到。
//函式呼叫自身稱為遞迴,函式名為“func”
(function fn(i){
console.log("函式名為"+func.name+",第"+i+"次呼叫")
if(i<3){//遞迴出口
fn(++i);//遞迴
}
})(1);
其次,匿名函式不利於除錯棧追蹤,有名函式根據名字可以很快在除錯的時候定位程式碼位置。
作用域(Scope)
作用域即函式或者變數的可見區域。通俗來講,函式或者變數不在這個區域內,就無法訪問到。
函式作用域 ES6塊級作用域 為什麼要引進塊級作用域? 作用域鏈(scope chain) 變數函式的查詢機制 ......見下篇介紹
箭頭函式
用(引數)=>{表示式}這種寫法宣告一個函式,就叫箭頭函式
(function(i){
console.log(i);
})(1)
((i)=>console.log(i))
箭頭函式一個明顯作用就是可以保持this的指向,總是指向定義它時所在的上下文環境。關於this內容會單獨詳述
高階函式
如果某個函式可以接受另外一個函式作為引數,該函式就稱之為高階函式。
函式作為引數?這似乎太奇怪了,其實是因為javascript裡的函式可以賦值給某個變數,而變數可以作為引數傳遞給函式,因此函式也可以作為引數傳遞函式。
function fn(){
if(callback){
callback();
}
}
fn1(function(){
console.log("高階函式")
})
函式過載
過載是面向物件程式語言(比如Java、C#)裡的特性,JavaScript語言並不支援該特性。所謂過載(overload),就是函式名稱一樣,但是隨著傳入的引數個數不一樣,呼叫的邏輯或返回的結果會不一樣。
簡單的實現一個過載的方法
function overLoading(){
//根據arguments.length,對不同的值進行不同的操作
switch(arguments.length){
case 0 :
//操作0的程式碼寫在這裡
brank;
case 1:
//操作 1的程式碼寫在這裡
brank;
default:
//預設執行這裡
}
}
在JQuery之父John Resig寫的《secrets of the JavaScript ninja》找到了一個絕佳巧妙的方法!那種方法充分的利用了閉包的特性!
function addMethod(object, name, fn) {
var old = object[name]; //把前一次新增的方法存在一個臨時變數old裡面
object[name] = function() { // 重寫了object[name]的方法
// 如果呼叫object[name]方法時,傳入的引數個數跟預期的一致,則直接呼叫
if(fn.length === arguments.length) {
return fn.apply(this, arguments);
// 否則,判斷old是否是函式,如果是,就呼叫old
} else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}
現在有這樣一個需求,有一個people物件,裡面存著一些名人,如下:
var people = {
values: ["Dean Edwards", "Sam Stephenson", "Alex Russell", "Dean Tom"]
};
我們希望people物件擁有一個find方法,當不傳任何引數時,就會把people.values裡面的所有元素返回來;當傳一個引數時,就把first-name跟這個引數匹配的元素返回來;當傳兩個引數時,則把first-name和last-name都匹配的才返回來。因為find方法是根據引數的個數不同而執行不同的操作的,所以,我們希望有一個addMethod方法,能夠如下的為people新增find的過載
addMethod(people, "find", function() {}); /*不傳參*/
addMethod(people, "find", function(a) {}); /*傳一個*/
addMethod(people, "find", function(a, b) {}); /*傳兩個*/
OK,現在這個addMethod方法已經實現了,我們接下來就實現people.find的過載啦!
//addMethod
function addMethod(object, name, fn) {
var old = object[name];
object[name] = function() {
if(fn.length === arguments.length) {
return fn.apply(this, arguments);
} else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}
var people = {
values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
};
/* 下面開始通過addMethod來實現對people.find方法的過載 */
// 不傳引數時,返回peopld.values裡面的所有元素
addMethod(people, "find", function() {
return this.values;
});
// 傳一個引數時,按first-name的匹配進行返回
addMethod(people, "find", function(firstName) {
var ret = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i].indexOf(firstName) === 0) {
ret.push(this.values[i]);
}
}
return ret;
});
// 傳兩個引數時,返回first-name和last-name都匹配的元素
addMethod(people, "find", function(firstName, lastName) {
var ret = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i] === (firstName + " " + lastName)) {
ret.push(this.values[i]);
}
}
return ret;
});
// 測試:
console.log(people.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
console.log(people.find("Dean")); //["Dean Edwards", "Dean Tom"]
console.log(people.find("Dean Edwards")); //["Dean Edwards"]
早上好