前端教學講義:閉包、高階函數、原型鏈

上一章:前端教學講義:JS基礎前端

閉包、高階函數

閉包是一種打通兩個做用域的能力,在 Java 和 OC 中都有相似的功能,JS 中的閉包和他們沒有太大的差異。express

不過由於 JS 的函數是一等公民,因此使用起來會更加靈活。segmentfault

在數學和計算機科學中,高階函數是至少知足下列一個條件的函數:數組

接受一個或多個函數做爲輸入閉包

輸出一個函數app

下面的例子就是個典型的高階函數函數

const multiple = function(a, b) {
    return a * b;
}

const plus = function(a, b) {
    return a + b;
}

const express = function(operator, a, b) {
    return operator(a)(b);
}

[
    [plus, 1, 2],
    [multiple, 3, 4],
].map(function(formula) {
  return express.apply(null, formula)
});
 
// [3, 12]

call,apply,bind

在 JS 中常常會遇到 this 指向錯亂的問題:this

var obj = {
    value: 1234,
    log: function() {
      console.log(this.value)
    }
}
var log = obj.log;
log(); // 這個函數執行的時候沒有上下文,this 會默認爲 window 或者 global
 
// undefined

JS 提供了兩個方法,call 和 apply,可以指定一個函數執行時候的上下文prototype

log.call(obj);
 
// 1234
 
log.apply(obj);
 
// 1234

call 和 apply 同時還能夠指定傳給函數的參數是什麼,他們的區別就是後面的傳參數形式不同;code

function fn(arg1, arg2){}
 
fn.call(obj, arg1, arg2)
fn.apply(obj, [arg1, arg2]) // apply 是將參數看成數組傳入

bind 函數經過閉包的形式,來強制的將某個上下文綁定到函數上:

log = bind(log, obj); // 返回一個新函數,此函數的 this 強制被綁定爲 obj
log()
 
1234

bind 的實現大概以下(省略傳參的功能):

function bind(fn, ctx) {
  return function() {
    return fn.apply(ctx);
  }
}

和上面舉例有點區別的是 JS 提供的 bind 方法是 Function 類的一個方法,調用方法以下:

function log() {}
 
log = log.bind(obj)

原型鏈

JS 的原型鏈(prototype chain)是爲了實現面向對象而存在的,當訪問一個對象的方法的時候,首先會遍歷對象自己,是否存在這個鍵,而後在查找父類是否有這個鍵,再一直追溯到頂層。

「父類」中包含的方法和屬性,都存放在對象的 「__proto__」對象上(在舊的 JS 規範中,「__proto__」是隱藏屬性,可是 ES6 將它標準化並暴露出來了)。

var c = {
  somekey: 1,
}
var b = {
  __proto__: c,
}
var a = {
__proto__: b
}
a.somekey
 
// 1

當訪問 「a.somekey」 的時候,會沿着原型鏈一直向上查找,至關於作了以下計算:

function getValue(obj, key) {
  while (obj) {
    if (key in obj) {
      return obj[key]
    }
    if (obj.__proto__) {
      obj = obj.__proto__;
    } else {
      return undefined;
    }
  }
}
 
getValue(a, 'somekey')

在最先期的 JS 版本中是沒有類的,後來加入了原型鏈和 new 關鍵詞才實現了類。

function SubClass(value) {
    this.value = value;
}
 
SubClass.prototype = {
    log: function() {
        console.log(this.value);
    }
}

var sub = new SubClass(1234);
sub.log();
 
// 1234

在 JS 中任何函數均可以看成構造函數並搭配 new 關鍵詞使用。

new 關鍵詞的做用就是新建一個空對象 a,而後把構造函數上的 prototype 值掛載到 a.__proto__ 上。

再將 a 做爲 context 運行構造函數。

若是咱們將 new 做爲一個函數,它就是這樣:

function new(class, ...params) {
  var instance = {};
  instance.__proto__ = class.prototype;
  var result = class.apply(instance, params);
  if (typeof result !== 'undefined') {
    return result;
  }
  return instance;
}
 
var sub = new(SubClass, 1234);

從上面的代碼能夠看出,在構造函數中,能夠指定 new 關鍵詞返回的類型,也就是說 new A() 返回的結果不必定就是 A 的實例,這要看 A 的構造函數內部是如何實現的。

function A() {
    return 1234;
}
var a = new A();
console.log(a)
 
// 1234

JS 用原型鏈的方式,曲線的實現了類的行爲和寫法,因此在 JS 中,「類」就是構造函數。

instanceof

如何判斷一個對象是不是一個類的實例呢?

JS 實現的方法很粗糙,就是判斷實例的原型鏈上是否存在類的原型。

function A() {
}
var a = new A();
a instanceof A
 
true

咱們甚至可讓任意一個類成爲實例的父類

function C() {}
function A() {}
var a = new A();
C.prototype = A.prototype;
a instanceof A
// true
 
a instanceof C
// true

咱們甚至也可讓一個父類不是實例的父類

function A() {}
var a = new A();
A.prototype = {}; // 對原型從新賦值
a instanceof A
 
false

總之就是很神奇

ES6 中對類的改進

在 ES6 中新增了 class 關鍵詞,使得聲明一個類更加簡單,但只是寫法上有改變,運行機制仍是同樣。

class A {
    constructor(value) {
        this.value = value;
    }
    log() {
        console.log(this.value);
    }
}
var a = new A(1234);
a.log();
 
// 1234
相關文章
相關標籤/搜索