Function

What is this

在其他以類別為基礎的程式語言中,this指的是目前使用類別進行實體化的物件。

而JavaScript語言中因為在設計上並不是以類別為基礎的物件導向,設計上不一樣,所以this的指向的是目前呼叫函式或方法的擁有者(owner)物件,也就是說它與函式如何被呼叫或調用有關,雖然是同一函式的呼叫,因為不同的物件呼叫,也有可能是不同的this值。

全域環境下

this 值在所有函式以外的全域執行環境下,會被當作全域物件。

console.log(this);

//function declaration
function a(){
  console.log(this);
}

a();

//function expression
let b = function(){
  console.log(this);
}

b();

函式環境下

當這個函式是物件裡面的method時,這時候的this就會指稱到包含這個method的物件

var c = {
  log: function(){
    console.log(this);
  }
}

c.log(); // this為c




this會亂認人, 也因為這個特性, 如何讓thisfunction好好相認變得很重要, 請往下看




Function bind/call/apply

先說結論

Use .call() or .apply() when you want to invoke the function immediately, and modify the context.

Use .bind() when you want that function to later be called with a certain context, useful in events.

call & apply

使用給定的this參數以及分別給定的參數來呼叫某個函數

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

fun.apply(thisArg, [argsArray])

兩者差異, call接受一連串變數, apply接受一個Array或陣列

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

var o = {a: 1, b: 3};

// 第一個參數(parameter)是調用了 this 的物件, 
// 後續參數(parameters)會作為函式呼叫內的參數(arguments)而通過
add.call(o, 5, 7); // 16

// 第一個參數(parameter)是調用了 this 的物件, 
// 第二個參數的陣列作為函式呼叫內的參數(arguments)之構件
add.apply(o, [10, 20]); // 34

特殊用法: apply可以用來debug既有的function

var originalfoo = someobject.foo;
someobject.foo = function() {
  // Do stuff before calling function
  console.log(arguments);
  // Call the function as it would have been called normally:
  originalfoo.apply(this, arguments);
  // Run stuff after, here.
}

bind

bind() 方法,會建立一個新函式。該函式被呼叫時,會將 this 關鍵字設為給定的參數,並在呼叫時,帶有提供之前,給定順序的參數。

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

我的理解是, function會被各種人call, 如果function內部使用this, 會無法保證值一定會跟開發者想的一樣, 所以使用bind將function的context(也就是this回傳的物件)綁定, 未來無論如何使用都會是一樣的context. 也因為這個性質, 當任一function第一次bind完後, 後面的bind皆無效.

function f() {
  return this.a;
}

var g = f.bind({a: 'azerty'});
console.log(g()); // azerty

var h = g.bind({a: 'yoo'}); // bind 只能使用一次!
console.log(h()); // azerty

var o = {a: 37, f: f, g: g, h: h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty

bind 可以套用Partial Function唷!

function plus(a, b) {
  return a + b;
}
var addSeven = plus.bind(undefined, 7);
console.log(addSeven(3)); // 10
console.log(addSeven(23)); //30

用call回頭複習一下this值的產生規則

  1. 當使用strict code(嚴格模式程式碼)時,直接設定為call方法裡面的thisArg(this參數值)。
//注意嚴格模式strict mode 

function f2(){
  "use strict"; // 嚴格模式
  return this;
}

f2() === undefined; //true

因為 f2 是直接被呼叫,而不是在其為某個物件的方法或屬性的情況下(例如 window.f2())被直接呼叫。某些瀏覽器首次支援嚴格模式時沒導入這個特徵,它們會因此錯誤的回傳 window 物件。

  1. 當thisArg(this參數值)是null或undefined時,會綁定為全域(global)物件。
  2. 當thisArg(this參數值)的類型不是物件類型時,會綁定為轉為物件類型的值。
  3. 都不是以上的情況時,綁定為thisArg(this參數值)。

最重要的一點, 當無法確定this是誰的時候, 記得把它console.log來看看比較重要.

確保this的方法

舉例來說

function Person() {
  // 這個 Person() 建構式為它自己定義了 this
  this.age = 0;

  setInterval(function growUp() {
    // 在非嚴謹模式下, growUp() 函式把 this 定義為全域物件,
    // 而不是 Person() 建構式所定義的 this
    this.age++;
  }, 1000);
}
方法1 為作用域鏈(Scope Chain)
function Person() {
  var self = this; // 有些人喜歡 `that` 而不是 `self`. 
                   // 選好一種取法後始終如一
  self.age = 0;

  setInterval(function growUp() {
    // 這個 callback 參考 `self` 變數,為預期中的物件。
    self.age++;
  }, 1000);
}
方法2是使用bind, 確保this的context不會錯誤
function Person() {
  this.age = 0;

  setInterval(function growUp() {
    this.age++;
  }.bind(this), 1000);
}

Named/Anonymous/Arrow function


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

//Anonymous 1
let add = function(a,b){
    return a+b;
}

//Arrow 1
let add = (a,b) => {
    return a+b;
}

//Arrow 2
let add = (a,b) => (a+b);



Why do we need Arrow?

  1. 短, 可閱讀性高
  2. 可以綁定this(箭頭函數當中的 this 是定義時的對象,而不是使用時的對象)

舉例來說:

var adder = {
  base : 1,
  add : function(a) {
    var f = v => v + this.base;
    return f(a);
  },
  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base : 2
    };
    return f.call(b, a);
  }
};
console.log(adder.add(1));         // 顯示 2
console.log(adder.addThruCall(1)); // 依舊顯示 2

Default Parameter Value

參數默認值

以前的日子

function show(message) {
  if(typeof message ==="undefined"){
    message="預設值";
  }
  console.log(message);
}
show('Hello'); //Hello
show(); //預設值

ES6

function show(message="預設值") {
  console.log(message);
}
show('Hello'); //Hello
show(); //預設值

Reference