鹿茸就是鹿耳朵裡的毛 - bind & call & apply


Posted by TempuraEngineer on 2022-08-06

目錄


bind

回傳函式,所以通常用在不立刻呼叫的情況

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

this.antler = '頭上的角';

const angel = {
    name: '芙丸',
    antler:'耳朵裡的毛',
    answerWhatIsAntler: function() {
        return `鹿茸就是鹿${this.antler}。(${this.name})`;
    }
};

const foo = angel.answerWhatIsAntler; // 脫離angel後,指向變成window,結果為「鹿茸就是鹿頭上的角。(undefined)」
const bar = angel.answerWhatIsAntler.bind(angel); // 重新指向angel,結果為「鹿茸就是鹿耳朵裡的毛。(芙丸)」


call

立刻呼叫,回傳值

The call() allows for a function/method belonging to one object to be assigned and called for a different object.

this.antler = '頭上的角';

const falsy = {
    antler:'耳朵裡的毛'
};

function answerWhatIsAntler(year = '', author = '') {
    return `鹿茸就是鹿${this.antler}。(${year} ${author})`;
}

answerWhatIsAntler.call(falsy, '2014', '芙丸'); // this就會改指向falsy


apply

立刻呼叫,回傳值
和call差在接受的參數是陣列或者類陣列(array like)

The apply() method calls the specified function with a given this value, and arguments provided as an array (or an array-like object).

this.antler = '頭上的角';

const falsy = {
    antler:'耳朵裡的毛'
};

function answerWhatIsAntler(year = '', author = '') {
    return `鹿茸就是鹿${this.antler}。(${year} ${author})`;
}

answerWhatIsAntler.apply(falsy, ['2014', '芙丸']); // this就會改指向falsy


bind / call & currying

因為bind和call都會接受多個參數,所以都可以跟currying一起用

有些文章可能會說只有bind可以跟currying一起用,但我認為這句話只在,下方這種例1的情況成立,至於例2用call會比bind更好

例1

function add(a, b){
    return a + b + this.c;
}

const foo = add.bind({c:3}, 1);
const bar = add.call({c:3}, 1);
const fooBar = add.call({c:3}, 1, 2);

foo(2); // 6
foo(4); // 8

bar(2); // bar is not a function
bar; // NaN
fooBar; // 6,雖然這樣也能得到結果,但意義不大


例2

頁面上有2個地圖,還有1個用來在地圖上設置marker的setMarker()。map1的marker都是黃色,map2則都是紅色

不用bind或call可能會長這樣

import { Map, Marker, Popup } from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

function drawMap(style, zoom, token){
    return (containerId, lngLat) => {
        const map = new Map({
            container: containerId,
            style: style,
            center: lngLat,
            zoom: zoom, // starting zoom
            accessToken: token,
        });

        map.on('style.load', () => {
            map.addSource('point', {
              type: 'geojson',
              data: {
                type: 'FeatureCollection',
                features: [{
                    type: 'Feature',
                    geometry: {
                        type: 'Point',
                        coordinates: lngLat,
                    },
                }],
              },
            });
        })

        map.setFog({});

        return map;
    }
}

function setMarker(map, color, lngLat){
    const marker = new Marker({ color, draggable: false })

    marker.setLngLat(lngLat).addTo(map);
}

// style、zoom、token這些比較固定,id、lngLat則較可能變動,所以使用partial拆成2次傳參數會比currying一個一個傳適合
const drawStreetMap = drawMap('mapbox://styles/mapbox/streets-v11', 10, 'pk.eyJ1IjoidGVtcHVyYTMyNyIsImEiOiJja3Z6eXVqdnQ1YTdxMm9tdHUwMGx4eXBmIn0.7SOTd4xVrpfdvJiDx5R34g');

// 先設好兩個map
const map1 = drawStreetMap('map1', [121.5, 23.5]);
const map2 = drawStreetMap('map2', [0, 35]);

setMarker(map1, 'gold', [121.7, 22.857]);
setMarker(map1, 'gold', [120.92, 22.912]);

setMarker(map2, '#dc3545', [0.242, 34.5]);
setMarker(map2, '#dc3545', [0.255, 35.212]);

使用bind / call + currying改寫後,可以發現只需傳[lng, lat],簡潔多了

import { Map, Marker, Popup } from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

function drawMap(style, zoom, token){
    return (containerId, lngLat) => {
        const map = new Map({
            container: containerId,
            style: style,
            center: lngLat,
            zoom: zoom, // starting zoom
            accessToken: token,
        });

        map.on('style.load', () => {
            map.addSource('point', {
              type: 'geojson',
              data: {
                type: 'FeatureCollection',
                features: [{
                    type: 'Feature',
                    geometry: {
                        type: 'Point',
                        coordinates: lngLat,
                    },
                }],
              },
            });
        })

        map.setFog({});

        return map;
    }
}

function setMarker(color){
    return function(lngLat){
        const marker = new Marker({ color, draggable: false })

        // 待會要使用bind將指向改變,所以寫this.map
        marker.setLngLat(lngLat).addTo(this.map);
    }
}

// style、zoom、token這些比較固定,id、lngLat則較可能變動,所以使用partial拆成2次傳參數會比currying一個一個傳適合
const drawStreetMap = drawMap('mapbox://styles/mapbox/streets-v11', 10, 'pk.eyJ1IjoidGVtcHVyYTMyNyIsImEiOiJja3Z6eXVqdnQ1YTdxMm9tdHUwMGx4eXBmIn0.7SOTd4xVrpfdvJiDx5R34g');

// 先設好兩個map
const map1 = drawStreetMap('map1', [121.5, 23.5]);
const map2 = drawStreetMap('map2', [0, 35]);

// 各自先改變好指向,然後先傳color進去。這樣就不用每次都要傳map和color
// 因為bind回傳的會是包裹住setMarker內部回傳的匿名函示function,所以需要呼叫讓它回傳內部的匿名函式
const setMarkerTo1 = setMarker.bind({map:map1}, 'gold')();
const setMarkerTo2 = setMarker.call({map:map2}, '#dc3545');

setMarkerTo1([121.7, 22.857]);
setMarkerTo1([120.92, 22.912]);

setMarkerTo2([0.242, 34.5]);
setMarkerTo2([0.255, 35.212]);


參考資料

MDN - bind
MDN - call
MDN - apply
When to Use Bind(), Call(), and Apply() in JavaScript
[筆記] 了解function borrowing和function currying ─ bind(), call(), apply() 的應用


#bind #call #apply #改變this指向







Related Posts

D39_W4-HW3

D39_W4-HW3

運算子/優先序/寬鬆與嚴格相等/型別 - 小測驗

運算子/優先序/寬鬆與嚴格相等/型別 - 小測驗

漫談傳輸介面-I2C

漫談傳輸介面-I2C


Comments