學習并運用JavaScript的原生函數

KriClogstou 8年前發布 | 17K 次閱讀 JavaScript開發 JavaScript

來自: http://www.w3cplus.com/JavaScript/learning-javascript-native-functions-and-how-to-use-them.html

簡介

盡管 JavaScript 總是讓人產生誤解,但是它已經成為了最流行的編程語言之一。理解 JavaScript 的內在原理很困難。同樣的,迫使 JavaScript 成為常規規范,如面向對象或函數編程,同樣具有挑戰性。這里我強調闡明 JavaScript 核心部分的原生函數。

在這篇文章中,我將討論以下幾種行為:

  • Call/Apply
  • Bind
  • Map
  • Filter

首先我會定義這個函數(利用Mozilla的聲明方式),然后提供一個例子,最后實現此函數。

為了解釋這些行為,我需要先解釋一下復雜的 this 關鍵字以及類似數組的 arguments 對象。

this 和 arguments 對象

JavaScript 的作用域是基于函數而言的,術語一般稱為 作用域 ,變量和方法的作用域都是當前函數。 此外,函數執行的作用域是他們被定義的作用域而不是執行的作用域 。如果你想了解更多有關于作用域的知識,可以參考 你應該知道的4種 JavaScript 設計模式 這篇文章。 this 對象引用當前函數的上下文并且可以以多種方式被調用。例如,它可以被綁定到 window 對象(全局作用域)。

this.globalVar = {
    myGlobalVarsMethod: function (){
        // Implementation
    }
};

console.log(this.globalVar); // { myGlobalVarsMethod: [Function] }

并且變量可以綁定到已存在的函數中,如下:

this.globalVariable = 'globalVariable';
function globalFunction (){
    this.innerVariable = 'innerVariable';
    console.log(this.globalVariable === undefined); // true
    console.log(this.innerVariable === 'innerVariable'); // true
    return {
        innerFunction: function () {
            console.log(this.globalVariable === undefined); // true
            console.log(this.innerVariable === undefined); // true
        }
    }
}

globalFunction().innerFunction();

這里存在被綁定到每一個調用函數的 this 對象。 嚴格模式 下,如果變量未定義就會拋出異常/錯誤( TypeErrors )。在生產環境下嚴格模式者會被優先考慮;然而,我故意選擇不使用此模式以避免拋出異常。下面是嚴格模式下的一個簡單例子:

this.globalVar = 'globalVar';

function nonStrictFunctionTest () {
    return function () {
        console.log(this.globalVar); // undefined
    }
}

function strictFunctionTest () {
    'use strict'; // Strict Mode
    return function () {
        console.log(this.globalVar); // TypeError: Cannot read property 'globalVar' of undefined
    }
}

nonStrictFunctionTest()();
strictFunctionTest()();

可能很多 JavaScript 開發人員不知道,創建函數時會有一個 arguments 對象。這是一個類似數組的對象(僅具有屬性的長度)。 arguments 主要有三個屬性,即 callee (調用方法), length ,和 caller (調用函數的參考)。

在一個函數中聲明變量參數會替換/覆蓋原先的參數對象。

如下列出的一些參數對象:

function fn (){
    console.log(typeof arguments); // [object Object]
    console.log(arguments[0]); // DeathStar
    console.log(arguments[1]); // Tatooine
    arguments.push("Naboo"); // TypeError: undefined is not a function
    var arguments = "Star Wars";
    console.log(arguments[5]); // W
}

fn("DeathStar", "Tatooine");

按照如下所示,用 arguments 創建一個數組:

var args = Array.prototype.slice.call(arguments);

Call/Apply

無論 call 還是 apply 都是調用對象的一個方法。關于使用點操作符, call 和 apply 都接受其作為第一個參數。如上所述,每一個函數都保持在其所定義的特定作用域內。因此,當你調用對象時必須考慮到函數的作用域。

Mozilla 瀏覽器的 apply call 調用聲明如下所示:

fun.apply(thisArg, [argsArray])
fun.call(thisArg[, arg1[, arg2[, ...]]])

通過傳遞 thisArg 參數,在特定的上下文中,被調用的函數可以訪問或修改對象。下面的例子闡明了 call 的使用。

this.lightSaberColor = 'none';

var darthVader = {
    team: 'Empire',
    lightSaberColor: 'Red'
};

var printLightSaberColor = function(){
    console.log(this.lightSaberColor);

}

printLightSaberColor() // none
printLightSaberColor.call(darthVader); // Red
printLightSaberColor.apply(darthVader); // Red

注意:第一次調用默認為全局作用域( window ),然而,第二次為 darthvader 。

call 和 apply 主要的區別在于他們的聲明方式不同。 call 需要參數分開傳遞,而 apply 需要傳入由參數組成的數組。我是這樣記憶的:“ A pply uses an A rray。”當你的程序無關乎參數數目時, apply 方法可能會更加適用。

Currying(柯里化) (部分函數應用)是應用 call 和 apply 的一個函數式編程。Currying 允許我們創建返回已知條件的函數。這里是一個 currying 函數:

var curry = function(fun) {

  // nothing to curry. return function
  if (arguments.length < 1) {
    return this;
  }

  // Create an array with the functions arguments
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {

    // *Apply* fn with fn's arguments
    return fun.apply(this, args.concat(Array.prototype.slice.call(arguments, 0)));
  };
};

// Creating function that already predefines adding 1 to a
function addOneToNumber(a) {
    console.log(1 + a);
}

// addOneCurried is of function
var addOneCurried = curry(addOneToNumber);
console.log(addOneCurried(10)); // 11

雖然 arguments 不是數組,但是 Array.prototype.slice 可以將類數組的對象轉換成新數組。

Bind

bind 方法用于明確指定調用 this 方法。在作用域方面,類似于 call 和 apply 。當你將一個對象綁定到一個函數的 this 對象時,你就會用到 bind 。

如下是 bind 的 聲明

fun.bind(thisArg[, arg1[, arg2[, ...]]])

通俗地說,我們是通過 bind 向函數 fun 傳遞 thisArg 參數。實質上就是每次 fun 函數都必須通過傳遞 thisArg 參數調用 bind 方法。讓我們在一個簡單的例子中仔細看看。

var lukeSkywalker = {
    mother: 'Padme Amidala',
    father: 'Anakin Skywalker'.
}

var getFather = function(){
    console.log(this.father);
}

getFather(); // undefined
getFather.bind(lukeSkywalker)(); // Anakin Skywalker
getFather(lukeSkywalker); // undefined

第一個 getfather() 返回值為 undefined 是因為在這里 father 屬性沒有被定義。那這時 this 代表什么呢?只要我們不明確的指定它,它就代表 window 的全局對象。第二個 getfather() 返回 “Anakin Skywalker”是因為 getfather() 中的 this 指代的是 lukeskywalker 。許多Java/C++ 開發人員會設想最后一個 getfather() 的調用將返回預想的結果–雖然再次返回全局對象。

如下這里是 bind 的實現原理:

Function.prototype.bind = function(scope) {
  var _that = this;

  return function() {
    return _that.apply(scope, arguments);
  }
}

這里 JavaScript 的作用域是合乎邏輯的,返回函數的 this 對象是不同于 bind 的 this 對象的。因此,將 this 暫時緩存給變量 _that 保證了其正確的作用域范圍。否則, this.apply(scope,arguments) 將會未定義。

Map

JavaScript 的 map 函數是遍歷數組,同時轉換每個元素的函數編程技術。它用 modified 元素創建了一個新數組并以回調的方式返回。關于我提到的修改或轉換元素,實踐表明,如果元素是對象(而不是原語),這只是克隆對象并不是從物理上改變了原生的。

以下是該方法的聲明:

arr.map(callback[, thisArg])

回調方法有三個參數,即 currentValue , index ,和 array 。

這里是一個有關于 map 的簡單例子:

function Jedi(name) {
    this.name = name;
}

var kit = new Jedi('Kit');
var count = new Jedi('Count');
var mace = new Jedi('Mace');

var jedis = [kit, count, mace];

var lastNames = ['Fisto', 'Dooku', 'Windu'];

var jedisWithFullNames = jedis.map(function(currentValue, index, array) {
    var clonedJedi = (JSON.parse(JSON.stringify(currentValue))) // Clone currentValue
    clonedJedi.name = currentValue.name + " " + lastNames[index];
    return clonedJedi;
});

jedisWithFullNames.map(function(currentValue) {
    console.log(currentValue.name);
});


/**
Output:
Kit Fisto
Count Dooku
Mace Windu
*/

了解了 map 是用來做什么的,讓我們看一下它具體是如何實現的:

Array.prototype.map = function (fun, thisArg) {
    if(typeof fun !== 'function') {
        throw new Error("The first argument must be of type function");
    }
    var arr = [];
    thisArg = (thisArg) ? thisArg : this;

    thisArg.forEach(function(element) {
        arr[arr.length] = fun.call(thisArgs, element);
    });

    return arr;
}

注:這是一個簡單的實現。到 ECMAScript 5看全部的實現, 并查閱其規范

Filter

filter 方法是數組的另外一種表現行為。類似于 map , filter 返回一個新的數組并接受一個函數和一個可選的 thisArg 參數。然而,返回的數組僅包含適合在回調函數測試的特定條件的元素。回調函數必須返回一個 Boolean –返回 true 的元素才會被接受并插入到返回的數組。

關于 filter 有許多應用,包括選擇偶數,用一個特定的屬性選擇對象,或選擇有效的電話號碼。

這里是其中一種聲明方法:

arr.filter(callback[, thisArg])

同樣的, thisArg 是可選的參數并且回調函數接受三個參數, currentValue , index 和 array 。

這里是一個有關于 filter 的例子:

function Person(name, side) {
    this.name = name;
    this.side = side;
}

var hanSolo = new Person('Han Solo','Rebels');
var bobaFett = new Person('Boba Fett','Empire');
var princessLeia = new Person('Princess Leia', 'Rebels');

var people = [hanSolo, bobaFett, princessLeia];

var enemies = people.filter(function (currentValue, index, array) {
    return currentValue.side === 'Empire';
})
.map(function(currentValue) {
    console.log(currentValue.name + " fights for the " + currentValue.side + ".");
});

/**
Output:
Boba Fett fights for the Empire.
*/

有趣的是, array 方法可以創造有趣的,復雜的操作。

最后,讓我們看看 filter 的實現:

Array.prototype.filter = function(fun, thisArg) {
    if(typeof fun !== 'function') {
        throw new Error("The first argument must be of type function");
    }
    var arr = [];
    thisArg = (thisArg) ? thisArg : this;

    thisArg.forEach(function(element) {
      if (fun.call(thisArg, element)) {
        arr[arr.length] = element;
      }
    });
    return arr;
};

這里是 ECMAScript 的 實現規范

總結

還有更多令人困惑但是很有用的原生函數。它們是值得用數組和函數來回顧其中的每一種方法。

希望這篇文章可以有助于你理解 JavaScript 的內部原理和詞法作用域。盡管與實踐緊密相連, call 、 apply ,和 bind 還是很難把握的。為了避免傳統的循環技術你可以嘗試使用 map 和 filter 方法 。

本文根據 @DEVAN PATEL 的《 Learning JavaScript Native Functions and How to Use Them 》所譯,整個譯文帶有我們自己的理解與思想,如果譯得不好或有不對之處還請同行朋友指點。如需轉載此譯文,需注明英文出處: https://scotch.io/tutorials/learning-javascript-native-functions-and-how-to-use-them

靜子

在校學生,本科計算機專業。一個積極進取、愛笑的女生,熱愛前端,喜歡與人交流分享。想要通過自己的努力做到心中的那個自己。微博:@靜-如秋葉

 本文由用戶 KriClogstou 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!