__proto__ 和 prototype 會出如今什麼地方?它們之間是什麼關係?實現繼承依賴什麼?

遞歸、閉包、原型、繼承

本文主要講解、理清一些函數經常使用的知識點:遞歸、閉包是什麼、閉包使用場景、什麼是原型和原型鏈、如何實現繼承、繼承的原理,原文javascript

遞歸

函數的遞歸就是在函數中調用自身html

舉一個實例,著名斐波那契數列如何求得,問題是這樣的:java

  1. 第一個月初有一對剛誕生的兔子
  2. 第二個月以後(第三個月初)它們能夠生育
  3. 每個月每對可生育的兔子會誕生下一對新兔子
  4. 兔子永不死去

定義出來的數列是git

clipboard.png

咱們須要求得 n 月有多少對兔子,經過遞歸算法能夠求得github

function fn(n) {
  return n < 2 ? 1 : fn(n - 1) + fn(n - 2)
}
var count = fn(30);
console.log(count);

閉包

什麼是閉包?閉包就是函數,它能夠繼承並訪問自身所被聲明的函數做用域內的變量。算法

function fn1 () {
  var a = 'hello'
  function fn2 () {
    console.log(a)
  }
}
fn1() // 其中 fn2 就是閉包函數

閉包的使用場景

閉包有不少使用場景,如下舉例:閉包

1. 私有變量
function Person(){    
  var name = "default";       
  return {    
    getName : function(){    
      return name;    
    },    
      setName : function(newName){    
      name = newName;    
    }    
  }    
};
var person = Person()
console.log(person.getName()) // default
person.setName('xiaomuchen')
console.log(person.getName()) // xiaomuchen
var person2 = Person()
console.log(person2.getName()) // default

上述函數,使用閉包建立私有變量 name,變量不可被外部直接操做、獲取,只能經過返回的接口控制。函數

2. 匿名自執行函數

好比在開發頁面時,須要在頁面初始化時,你須要當即作一些操做,那麼能夠在頁面中使用匿名自執行函數,它會在 JS 引擎讀取到這部分代碼時就當即執行。this

// 在 title 上添加頁面打開時間
(function(){
  var openTime = new Date()
  document.title = document.title + openTime
})();

原型、原型鏈、繼承

先問一個問題:__proto__ 和 prototype 會出如今什麼地方?它們之間是什麼關係?實現繼承依賴什麼?spa

__proto__ 和 prototype 的區別

1.JavaScript 中每個對象都擁有原型鏈(__proto__)指向其構造函數的原型(prototype)

var a = {}
a.__proto__ === Object.prototype // true

function Person () {}
Person.__proto__ === Function.prototype // true

var p = new Person()
p.__proto__ === Person.prototype // true

2.JavaScript 中每個函數都擁有原型(prototype),原型也是一個對象,這個對象包括:原型鏈、原型方法(屬性)、函數構造,同理它的原型鏈指向其構造函數的原型

function Person () {}
Person.prototype.getName = function () {}
Object.getOwnPropertyNames(Person.prototype) // ["constructor", "getName"]
Person.prototype.__proto__ === Object.prototype // true

3.當訪問一個函數上的屬性時,先嚐試訪問自身上的屬性,再嘗試訪問其原型上的屬性。當訪問一個對象上的屬性時,先嚐試訪問自身上的屬性,再經過原型鏈嘗試訪問其構造函數原型上的屬性。若是沒有則經過原型上的原型鏈,繼續向上查找,直到訪問 Object.prototype 上的屬性,若是仍是沒有,由於 Object.prototype 是一個沒有 __proto__ 的對象,則查詢到此爲止,返回 undefined。

function Person () {}
Person.getName = function () {
  console.log('Person1')
}
Person.prototype.getName = function () {
  console.log('Person2')
}
var p = new Person()

Person.getName() // Person1
p.getName() // Person2
console.log(typeof p.getClass) // undefined
繼承

JavaScript 函數經過原型和原型鏈實現繼承

function superA (name) {
  this.name = name
}
superA.prototype.getName = function () {
  console.log(this.name)
}
function subA (name) {
  superA.call(this, name) // 繼承屬性
}
subA.prototype = new superA() // 繼承方法

var a1 = new subA('xiaomuchen')
a1.getName() // xiaomuchen

上述代碼,描述了一個函數的經典繼承,其工做原理是這樣的:

  1. 聲明父類 superA、子類 subA
  2. 重寫子類 subA 的原型,指向 superA 的實例
  3. 當實例化 a1 時,a1.__proto__ => subA.prototype => new superA() => superA.prototype,因此 a1 的構造函數是 superA
  4. 同時,運行 subA 也就是 superA.call(this, 'xiaomuchen'),其中 this 指向 a1 因此 a1 繼承了 name 屬性
  5. 這樣子類 subA 的實例 a1 就繼承了 superA 的原型方法和屬性

總結

本文歸納了遞歸、閉包、原型、繼承,理清這些基本的概念,有助於你接納更多的東西,咱們會在下一個章節對函數進行更深刻的討論。


做者:肖沐宸,github

相關文章
相關標籤/搜索