lodash源碼分析(create.js)

本系列使用 lodash 4.17.4

前言

沒有引用任何文件

正文

/**
 * Creates an object that inherits from the `prototype` object. If a
 * `properties` object is given, its own enumerable string keyed properties
 * are assigned to the created object.
 *
 * @since 2.3.0
 * @category Object
 * @param {Object} prototype The object to inherit from.
 * @param {Object} [properties] The properties to assign to the object.
 * @returns {Object} Returns the new object.
 * @example
 *
 * function Shape() {
 *   this.x = 0
 *   this.y = 0
 * }
 *
 * function Circle() {
 *   Shape.call(this)
 * }
 *
 * Circle.prototype = create(Shape.prototype, {
 *   'constructor': Circle
 * })
 *
 * const circle = new Circle
 * circle instanceof Circle
 * // => true
 *
 * circle instanceof Shape
 * // => true
 */
function create(prototype, properties) {
  prototype = prototype === null ? null : Object(prototype)
  const result = Object.create(prototype)
  return properties == null ? result : Object.assign(result, properties)
}

export default create
複製代碼

這個函數的做用是建立某個原型對象的新對象,能夠傳入可枚舉的配置屬性對新生成的對象進行屬性的配置修改,主要使用到的仍是Object.create()函數。雖然這個函數代碼簡短,可是設計到原型、及其基於原型的繼承和對象屬性拷貝,涉及的知識點仍是比較多的。下面我就簡單的分析一下包含的知識點而後再回來看這段代碼及其實例吧。

1.原型


咱們都知道javascript是基於原型的一門語言。原型是javascript中比較重要也是比較難的一點。初學的時候很容易被捲入雞生蛋,蛋生雞的死衚衕裏。不信,咱們來試試幾個題:

Function.prototype === Function.__proto__   //true 
Function.prototype === Object.__proto__     //true
Function.prototype.__proto__ === Object.prototype //true
Object.prototype === Object.__proto__       //false
Function instanceof Function                //true
Object instanceof Object                    //true
Object instanceof Function                  //true
Function instanceof Object                  //true
Object.__proto__ instanceof Object          //true
Object.prototype instanceof Object          //false
Function.prototype instanceof Object        //true
Function.__proto__ instanceof Object        //true
先有Function仍是先有Object構造函數?          //沒有前後順序,若是有那也是做者編寫代碼時的前後

/*全部對象由構造函數生成,而構造函數也是對象,那這個構造函數對象是怎麼生成的呢?若是把對象比做雞,構造函數比做蛋,那就是雞生蛋蛋生雞的悖論*/
/*歸根到底就是
Function構造函數自己也算是Function類型的實例嗎?
Function構造函數的prototype屬性和__proto__屬性都指向同一個原型,是否能夠說Function對象是由Function構造函數建立的一個實例?*/
/*這一悖論的根源來自Object和Function互爲對方的實例*/
複製代碼

若是對上述問題你都知道,而且對其有較深的理解,那麼你可能已經理解了什麼是原型。固然若是你對這幾道題還有疑惑,那麼你就須要更加深刻的理解這一知識點。

好了,話說回來,我來簡單的複習一下原型的知識點和應當注意的地方,而且解決掉雞生蛋蛋生雞的問題。

根據js紅寶書講的,每個構造函數A建立的時候都會建立另一個對象,這個對象爲A.prototype,(咱們知道函數也是一個對象,因此之後就叫函數爲函數對象,還要注意不是全部函數都有prototype屬性,好比 Object.prototype.toString.bind(Array)),此時該函數對象存在一個屬性叫prototype,其指向爲這個自動建立的對象(原型對象)。而且使用該構造函數建立實例時該實例a會存在 __proto__屬性(實際上是 [[Prototype]]屬性,只不過Firefox和Chrome提供了"__proto__"這個非標準的訪問器)來指向該原型對象A.prototype。因此咱們能夠知道當建立一個構造函數和實例化其構造函數生成的實例以及自動建立的對象(原型對象)關係以下:


這裏咱們能夠提出幾個疑問了?javascript

  1. 原型對象是怎麼建立的或者說是經過什麼方式建立的,new 仍是 對象字面量?有沒有構造函數?若是有,構造函數是什麼?
  2. 構造函數對象又是怎麼建立的?有沒有構造函數?若是有,構造函數是什麼?
  3. 全部對象都是構造函數的實例嗎?或者全部對象都是Object的實例嗎?
  4. 雞生蛋蛋生雞問題

要解決這幾個問題咱們須要瞭解幾個重要概念(哲學三問~~~~)
html

1.什麼是對象,什麼是實例對象java

An object is a collection of properties and has a single prototype object. The prototype may be the null value.

全部的具備屬性的數據集合就是對象。而實例對象是由構造函數建立的一個具象化物品,全部的實例對象都是對象,而不是全部對象都是實例對象。好比最多見的Object.prototype就是內置對象,並非任何構造函數的實例。固然咱們遇到的大多數都是實例對象。c++

2.什麼是函數bash

ECMAScript規範定義的函數:函數

對象類型的成員,標準內置構造器 Function的一個實例,而且可作爲子程序被調用。
注: 函數除了擁有命名的屬性,還包含可執行代碼、狀態,用來肯定被調用時的行爲。函數的代碼不限於 ECMAScript。

函數就是構造函數Function的實例。那什麼是構造函數呢?ECMAScript規範如此定義:源碼分析

建立和初始化對象的函數對象
注:構造器的「prototype」屬性值是一個原型對象,它用來實現繼承和共享屬性。

構造函數對象做爲一個函數對象均實例化自構造函數Function(構造函數對象是實例化對象的一部分),固然構造函數Function也是函數對象,也實例化自Function(這裏你須要明白實例化是將原型對象的屬性拷貝到實例化對象中,而Function的原型Function.prototype是個內置對象)。因此若是不考慮繼承的話,構造函數對象都是基於Function.prototype這個內置對象的。ui

注:對於Function是不是Function構造函數實例存在必定的爭議

3.什麼是原型this

ECMAScript標準以下:
spa

爲其餘對象提供共享屬性的對象。
當構造器建立一個對象,爲了解決對象的屬性引用,該對象會隱式引用構造器的「prototype」屬性。經過程序表達式 constructor.prototype 能夠引用到構造器的「prototype」屬性,而且添加到對象原型裏的屬性,會經過繼承與全部共享此原型的對象共享。另外,可以使用 Object.create 內置函數,經過明確指定原型來建立一個新對象。

原型就是爲對象提供共享屬性的對象,否則每一個對象的屬性都是私有的,致使空間浪費之類的問題。其建立是在建立構造函數的時候隱式建立的。其屬性constructor指向構造函數,而構造函數的prototype屬性指向原型對象。

4.咱們說一個對象是某個構造函數實例的根據是啥

咱們通常經過instanceof 判斷對象是不是構造函數的實例,而instanceof 實際是調用hasInstance,而hasInstance的循環調用prototype直到最頂上那個對象,若是這個與第二個參數的prototype一致,那就是真,不然假。也就是遍歷實例對象的原型鏈並判斷構造函數的原型是否在該原型鏈上。是Object.getPrototypeOf(Function) === Function.prototype 或者簡單來講Object.__proto__ === Function.prototype的結果,只是一種運算關係,知足這種關係就斷定是該構造函數的實例。

好了回到正題,咱們一一解決上述問題:

1.原型對象經過調用Object.create()來建立,是Object的實例。能夠經過A.prototype.__proto__  === Object.prototype來講明

2.構造函數對象經過構造函數的構造函數Function來建立,構造函數對象是Function的實例。因爲構造函數Function也是對象,因此Function既是雞也是蛋。具體的請看4.

3.什麼是對象那裏已經回答了,不是全部對象都是Object的實例

4.要解決雞生蛋蛋生雞問題,不能從javascript的角度上思考,必須跳出來從編譯角度上看這一問題。javascript的實現不是javascript,而是更爲底層的語言,好比c,c++之類的。咱們能夠很輕易的知道Function這個函數對象是內置對象(不曉得是經過啥語言建立了這個對象),只不過其屬性prototype和__proto__都指向另一個內置對象Function.prototype,因此從咱們javascript語法的角度就會獲得悖論,但其實是該對象先存在,只不過表現爲這種關係罷了。(感受只要說不過去扯上內置對象均可以說過去啊23333)

扯不下去了。下面貼一個對原型解釋很好的圖:

給個別人的解釋:

JavaScript引擎是個工廠。
最初,工廠作了一個最原始的產品原型。
這個原型叫Object.prototype,本質上就是一組無序key-value存儲({})
以後,工廠在Object.prototype的基礎上,研發出了能夠保存一段「指令」並「生產產品」的原型產品,叫函數。
起名爲Function.prototype,本質上就是[Function: Empty](空函數)
爲了規模化生產,工廠在函數的基礎上,生產出了兩個構造器:
生產函數的構造器叫Function,生產kv存儲的構造器叫Object。
你在工廠定製了一個產品,工廠根據Object.prototype給你作了一個Foo.prototype。
而後工廠發現你定製的產品很不錯。就在Function.prototype的基礎上作了一個Foo的構造器,叫Foo。
工廠在每一個產品上打了個標籤__proto__,以標明這個產品是從哪一個原型生產的。
爲原型打了個標籤constructor,標明哪一個構造器能夠依照這個原型生產產品。
爲構造器打了標籤prototype,標明這個構造器能夠從哪一個原型生產產品。
因此,我以爲先有Function仍是Object,就看工廠先造誰了。其實先作哪一個都無所謂。由於在你定製以前,他們都作好了。

再給個比較好的連接:

javascript 世界萬物誕生記

2.原型鏈和繼承

原型鏈就是若是一個實例對象a的原型對象A.prototype爲另一個實例對象b的話,a就能夠共享a.__proto__,a.__proto__.__proto__(b.__proto__)上的屬性,這個原型之間鏈狀的東西咱們就叫他原型鏈。其主要做用是用於繼承。

繼承是爲了解決new的實例對象不能共享屬性和方法的缺點。不說啥了,上六種繼承方式:

只給連接,不想寫了。。。。。。有空的時候再修改和放圖吧。。。

繼承的六種方式

源碼分析

1.Object()方法描述:

  • obj if obj is an object
  • {} if obj is undefined or null

這玩意兒和這個語句差很少:obj = obj || {},可是區別在於若是傳入的obj是原始值,好比4,這個方法會返回包裝過的Number對象而不是4這個原始值。

2.Object.create(prop):根據傳入的對象建立一個將該對象做爲原型的對象,執行空的構造函數。

Object.create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};
複製代碼

Object.create是內部定義一個對象,而且讓F.prototype對象 賦值爲引進的對象/函數 o,並return出一個新的對象。

new作法是新建一個實例對象o,而且讓o的__proto__指向了原型對象。而且使用call 進行強轉做用環境。從而實現了實例的建立。

偷偷截張圖:


3.Object.assign():主要用於對象屬性合併和添加。沒什麼好說的。注意的是其方式是淺拷貝,而不是深拷貝。也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。若是要實現深拷貝須要遞歸的進行屬性拷貝。並且這個方法能夠被...代替使用


使用方式

function Shape() {
    this.x = 0
    this.y = 0
  }
 
  function Circle() {
    Shape.call(this)
  }
 
  Circle.prototype = create(Shape.prototype, {
    'constructor': Circle
  })
 
  const circle = new Circle
  circle instanceof Circle
  // => true

  circle instanceof Shape
  // => true複製代碼

Object.create(proto[, propertiesObject])沒啥區別。用於實現類式繼承

使用場景

用於寄生組合式繼承避免屢次調用構造函數。其餘的。。。。沒時間了,,

結語

兩天趕完了,因爲時間跨度緣由,估計有些地方牛頭不對馬嘴。mayiga。。。

相關文章
相關標籤/搜索