1. 程式人生 > >手牽手,從零學習Vue原始碼 系列二(變化偵測篇)

手牽手,從零學習Vue原始碼 系列二(變化偵測篇)

系列文章:

  • 手牽手,從零學習Vue原始碼 系列一(前言-目錄篇)

  • 手牽手,從零學習Vue原始碼 系列二(變化偵測篇)

    陸續更新中...

    預計八月中旬更新完畢。

    1 概述

    Vue最大的特點之一就是資料驅動檢視,那麼什麼是資料驅動檢視呢?

    其實,我們可以把資料理解為狀態,而檢視就是使用者可直觀看到頁面。頁面不可能是一成不變的,它應該是動態變化的,而它的變化也應該是有跡可尋的,或者是由使用者操作引起的,亦或者是由後端資料變化引起的,當狀態發生改變時,頁面也就應該隨之而變化,所以我們就可以得到如下一個公式:

    UI = render(state)

    「上述公式中:狀態state是輸入,頁面UI輸出,狀態輸入一旦變化了,頁面輸出也隨之而變化。我們把這種特性稱之為資料驅動檢視。」

    我們可以把上述公式拆成三部分:state、render()以及UI。我們知道state和UI都是使用者定的,而不變的是這個render()。所以Vue就扮演了render()這個角色,當Vue發現state變化之後,經過一系列加工,最終將變化反應在UI上。

    那麼第一個問題來了,Vue怎麼知道state變化了呢?

    變化偵測

    「那麼第一個問題來了,Vue怎麼知道state變化了呢?」 那麼,這就引出了Vue中的變化偵測。

    變化偵測就是追蹤狀態,亦或者說是資料的變化,一旦發生了變化,就要去更新檢視。

    變化偵測可不是個新名詞,它在目前的前端三大框架中均有涉及。在Angular中是通過髒值檢查流程來實現變化偵測;在React是通過對比虛擬DOM來實現變化偵測,而在Vue中也有自己的一套變化偵測實現機制。

    2.Object的變化偵測

    在上一篇文章中,我們知道:資料驅動檢視的關鍵點則在於我們如何知道資料發生了變化,只要知道資料在什麼時候變了,那麼問題就變得迎刃而解,我們只需在資料變化的時候去通知檢視更新即可。

    要想知道資料什麼時候被讀取了或資料什麼時候被改寫了,其實不難,JS為我們提供了Object.defineProperty方法,通過該方法我們就可以輕鬆的知道資料在什麼時候發生變化。

    那麼,我們從原始碼出發,學習在Vue中是如何對資料進行變化偵測的。

    2.1 使Object資料變得“可觀測”

    資料的每次讀和寫能夠被我們看的見,即我們能夠知道資料什麼時候被讀取了或資料什麼時候被改寫了,我們將其稱為資料變的‘可觀測’。

    要將資料變的‘可觀測’,我們就要藉助前言中提到的「Object.defineProperty」方法了,在本文中,我們就使用這個方法使資料變得“可觀測”。


    首先,我們定義一個數據物件「car」:

    let car = {
      'brand':'BMW',
      'price':3000
    }
    

    我們定義了這個「car」的品牌「brand」是「BMW」,價格「price」是「3000」。現在我們可以通過「car.brand」和「car.pric」e直接讀寫這個「car」對應的屬性值。但是,當這個「car」的屬性被讀取或修改時,我們並不知情。那麼應該如何做才能夠讓「car」主動告訴我們,它的屬性被修改了呢?

    接下來,我們使用「Object.defineProperty」改寫上面的例子:

    let car = {}
    let val = 3000
    Object.defineProperty(car, 'price', {
      enumerable: true,
      configurable: true,
      get(){
        console.log('price屬性被讀取了')
        return val
      },
      set(newVal){
        console.log('price屬性被修改了')
        val = newVal
      }
    })
    

    通過「Object.defineProperty」方法給「car」定義了一個「price」屬性,並把這個屬性的讀和寫分別使用「get」和「set」進行攔截,每當該屬性進行讀或寫操作的時候就會觸發get()set()。如下圖: 可以看到,「car」已經可以主動告訴我們它的屬性的讀寫情況了,這也意味著,這個「car」的資料物件已經是“可觀測”的了。

    為了把「car」的所有屬性都變得可觀測,我們可以編寫如下程式碼:

    /**
     * Observer類會通過遞迴的方式把一個物件的所有屬性都轉化成可觀測物件
     */
    export class Observer {
      constructor (value) {
        this.value = value
        // 給value新增一個__ob__屬性,值為該value的Observer例項
        // 相當於為value打上標記,表示它已經被轉化成響應式了,避免重複操作
        def(value,'__ob__',this)
        if (Array.isArray(value)) {
          // 當value為陣列時的邏輯
          // ...
        } else {
          this.walk(value)
        }
      }
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
    }
    /**
     * 使一個物件轉化成可觀測物件
     * @param { Object } obj 物件
     * @param { String } key 物件的key
     * @param { Any } val 物件的某個key的值
     */
    function defineReactive (obj,key,val) {
      // 如果只傳了obj和key,那麼val = obj[key]
      if (arguments.length === 2) {
        val = obj[key]
      }
      if(typeof val === 'object'){
          new Observer(val)
      }
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
          console.log(`${key}屬性被讀取了`);
          return val;
        },
        set(newVal){
          if(val === newVal){
              return
          }
          console.log(`${key}屬性被修改了`);
          val = newVal;
        }
      })
    }
    

    在上面的程式碼中,我們定義了observer類,它用來將一個正常的object轉換成可觀測的object

    並且給value新增一個ob屬性,值為該valueObserver例項。這個操作相當於為value打上標記,表示它已經被轉化成響應式了,避免重複操作

    然後判斷資料的型別,只有object型別的資料才會呼叫walk將每一個屬性轉換成getter/setter的形式來偵測變化。最後,在defineReactive中當傳入的屬性值還是一個object時使用new observer(val)來遞迴子屬性,這樣我們就可以把obj中的所有屬性(包括子屬性)都轉換成getter/seter的形式來偵測變化。也就是說,只要我們將一個object傳到observer中,那麼這個object就會變成可觀測的、響應式的object

    那麼現在,我們就可以這樣定義「car」:

    let car = new Observer({
      'brand':'BMW',
      'price':3000
    })
    

    這樣,「car」的兩個屬性都變得可觀測了。

    2.2 依賴收集

    2.2.1 什麼是依賴收集

    在上一章中,我們邁出了第一步:讓object資料變的可觀測。變的可觀測以後,我們就能知道資料什麼時候發生了變化,那麼當資料發生變化時,我們去通知檢視更新就好了。那麼問題又來了,檢視那麼大,我們到底該通知誰去變化?總不能一個數據變化了,把整個檢視全部更新一遍吧,這樣顯然是不合理的。此時,你肯定會想到,視圖裡誰用到了這個資料就更新誰唄。對!你想的沒錯,就是這樣。

    視圖裡誰用到了這個資料就更新誰,我們換個優雅說法:我們把"誰用到了這個資料"稱為"誰依賴了這個資料",我們給每個資料都建一個依賴陣列(因為一個數據可能被多處使用),誰依賴了這個資料(即誰用到了這個資料)我們就把誰放入這個依賴陣列中,那麼當這個資料發生變化的時候,我們就去它對應的依賴陣列中,把每個依賴都通知一遍,告訴他們:"你們依賴的資料變啦,你們該更新啦!"。這個過程就是依賴收集。

    2.2.2 何時收集依賴?何時通知依賴更新?

    明白了什麼是依賴收集後,那麼我們到底該在何時收集依賴?又該在何時通知依賴更新?

    其實這個問題在上一小節中已經回答了,我們說過:誰用到了這個資料,那麼當這個資料變化時就通知誰。所謂誰用到了這個資料,其實就是誰獲取了這個資料,而可觀測的資料被獲取時會觸發「getter」屬性,那麼我們就可以在「getter」中收集這個依賴。同樣,當這個資料變化時會觸發「setter」屬性,那麼我們就可以在「setter」中通知依賴更新。

    總結一句話就是:在「getter」中收集依賴,在「setter」中通知依賴更新。

    2.2.3 把依賴收集到哪裡

    明白了什麼是依賴收集以及何時收集何時通知後,那麼我們該把依賴收集到哪裡?

    在2.2.1小節中也說了,我們給每個資料都建一個依賴陣列,誰依賴了這個資料我們就把誰放入這個依賴陣列中。單單用一個數組來存放依賴的話,功能好像有點欠缺並且程式碼過於耦合。我們應該將依賴陣列的功能擴充套件一下,更好的做法是我們應該為每一個數據都建立一個依賴管理器,把這個資料所有的依賴都管理起來。OK,到這裡,我們的依賴管理器「Dep類」應運而生,程式碼如下:

    export default class Dep {
      constructor () {
        this.subs = []
      }
      addSub (sub) {
        this.subs.push(sub)
      }
      // 刪除一個依賴
      removeSub (sub) {
        remove(this.subs, sub)
      }
      // 新增一個依賴
      depend () {
        if (window.target) {
          this.addSub(window.target)
        }
      }
      // 通知所有依賴更新
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    /**
     * Remove an item from an array
     */
    export function remove (arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
          return arr.splice(index, 1)
        }
      }
    }
    

    在上面的依賴管理器「Dep類」中,我們先初始化了一個「subs陣列」,用來存放依賴,並且定義了幾個例項方法用來對依賴進行新增,刪除,通知等操作。

    有了依賴管理器後,我們就可以在「getter」中收集依賴,在「setter」中通知依賴更新了,程式碼如下:

    function defineReactive (obj,key,val) {
      if (arguments.length === 2) {
        val = obj[key]
      }
      if(typeof val === 'object'){
        new Observer(val)
      }
      const dep = new Dep()  //例項化一個依賴管理器,生成一個依賴管理陣列dep
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
          dep.depend()    // 在getter中收集依賴
          return val;
        },
        set(newVal){
          if(val === newVal){
              return
          }
          val = newVal;
          dep.notify()   // 在setter中通知依賴更新
        }
      })
    }
    

    在上述程式碼中,我們在「getter」中呼叫了dep.depend()方法收集依賴,在「setter」中呼叫dep.notify()方法通知所有依賴更新。

    2.2.4 依賴到底是誰

    通過上一章節,我們明白了什麼是依賴?何時收集依賴?以及收集的依賴存放到何處?那麼我們收集的依賴到底是誰?

    雖然我們一直在說”誰用到了這個資料誰就是依賴“,但是這僅僅是在口語層面上,那麼反應在程式碼上該如何來描述這個”誰“呢?

    其實在Vue中還實現了一個叫做「Watcher」的類,而「Watcher類」的例項就是我們上面所說的那個"誰"。換句話說就是:誰用到了資料,誰就是依賴,我們就為誰建立一個「Watcher例項****。在之後資料變化時,我們不直接去通知依賴更新,而是通知依賴對應的」Watch例項**,由「Watcher例項」去通知真正的檢視。

    「Watcher類」的具體實現如下:

    export default class Watcher {
      constructor (vm,expOrFn,cb) {
        this.vm = vm;
        this.cb = cb;
        this.getter = parsePath(expOrFn)
        this.value = this.get()
      }
      get () {
        window.target = this;
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        window.target = undefined;
        return value
      }
      update () {
        const oldValue = this.value
        this.value = this.get()
        this.cb.call(this.vm, this.value, oldValue)
      }
    }
    /**
     * Parse simple path.
     * 把一個形如'data.a.b.c'的字串路徑所表示的值,從真實的data物件中取出來
     * 例如:
     * data = {a:{b:{c:2}}}
     * parsePath('a.b.c')(data)  // 2
     */
    const bailRE = /[^\w.$]/
    export function parsePath (path) {
      if (bailRE.test(path)) {
        return
      }
      const segments = path.split('.')
      return function (obj) {
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return
          obj = obj[segments[i]]
        }
        return obj
      }
    }
    

    誰用到了資料,誰就是依賴,我們就為誰建立一個「Watcher例項」,在建立「Watcher例項」的過程中會自動的把自己新增到這個資料對應的依賴管理器中,以後這個「Watcher例項」就代表這個依賴,當資料變化時,我們就通知「Watcher例項」,由「Watcher例項」再去通知真正的依賴。

    那麼,在建立「Watcher例項」的過程中它是如何的把自己新增到這個資料對應的依賴管理器中呢?

    下面我們分析「Watcher類」的程式碼實現邏輯:

    當例項化「Watcher類」時,會先執行其建構函式; 在建構函式中呼叫了this.get()例項方法; 在get()方法中,首先通過window.target = this把例項自身賦給了全域性的一個唯一物件window.target上,然後通過let value = this.getter.call(vm, vm)獲取一下被依賴的資料,獲取被依賴資料的目的是觸發該資料上面的getter,上文我們說過,在getter裡會呼叫dep.depend()收集依賴,而在dep.depend()中取到掛載window.target上的值並將其存入依賴陣列中,在get()方法最後將window.target釋放掉。 而當資料變化時,會觸發資料的setter,在setter中呼叫了dep.notify()方法,在dep.notify()方法中,遍歷所有依賴(即watcher例項),執行依賴的update()方法,也就是Watcher類中的update()例項方法,在update()方法中呼叫資料變化的更新回撥函式,從而更新檢視。 簡單總結一下就是:Watcher先把自己設定到全域性唯一的指定位置(window.target),然後讀取資料。因為讀取了資料,所以會觸發這個資料的getter。接著,在getter中就會從全域性唯一的那個位置讀取當前正在讀取資料的Watcher,並把這個watcher收集到Dep中去。收集好之後,當資料發生變化時,會向Dep中的每個Watcher傳送通知。通過這樣的方式,Watcher可以主動去訂閱任意一個數據的變化。為了便於理解,我們畫出了其關係流程圖,如下圖:

    以上,就徹底完成了對「Object資料的偵測」,依賴收集,依賴的更新等所有操作。

    2.2.5 不足之處

    雖然我們通過Object.defineProperty方法實現了對object資料的可觀測,但是這個方法僅僅只能觀測到object資料的取值及設定值,當我們向object資料裡新增一對新的「key/value」或刪除一對已有的key/value時,它是無法觀測到的,導致當我們對object資料新增或刪除值時,無法通知依賴,無法驅動檢視進行響應式更新。

    當然,Vue也注意到了這一點,為了解決這一問題,Vue增加了兩個全域性「API:Vue.set」和「Vue.delete」,這兩個API的實現原理將會在後面學習全域性API的時候說到。

    2.2.6 小結

    首先,我們通過「Object.defineProperty」方法實現了對object資料的可觀測,並且封裝了Observer類,讓我們能夠方便的把object資料中的所有屬性(包括子屬性)都轉換成「getter/seter」的形式來偵測變化。

    接著,我們學習了什麼是依賴收集?並且知道了在getter中收集依賴,在setter中通知依賴更新,以及封裝了依賴管理器Dep,用於儲存收集到的依賴。

    最後,我們為每一個依賴都建立了一個Watcher例項,當資料發生變化時,通知Watcher例項,由Watcher例項去做真實的更新操作。

    其整個流程大致如下:

    Data通過observer轉換成了getter/setter的形式來追蹤變化。 當外界通過Watcher讀取資料時,會觸發getter從而將Watcher新增到依賴中。 當資料發生了變化時,會觸發setter,從而向Dep中的依賴(即Watcher)傳送通知。 Watcher接收到通知後,會向外界傳送通知,變化通知到外界後可能會觸發檢視更新,也有可能觸發使用者的某個回撥函式等。

    3.Array的變化偵測

    3.1 前言

    上一節文章中我們介紹了Object資料的變化偵測方式,本篇文章我們來看一下對Array型資料的變化Vue是如何進行偵測的。

    為什麼「Object」資料和「Array型」資料會有兩種不同的變化偵測方式?

    這是因為對於Object資料我們使用的是JS提供的物件原型上的方法「Object.defineProperty」,而這個方法是物件原型上的,所以Array無法使用這個方法,所以我們需要對Array型資料設計一套另外的變化偵測機制。

    萬變不離其宗,雖然對Array型資料設計了新的變化偵測機制,但是其根本思路還是不變的。那就是:還是在獲取資料時收集依賴,資料變化時通知依賴更新。

    下面我們就通過原始碼來看看Vue對Array型資料到底是如何進行變化偵測的。

    3.2 在哪裡收集依賴

    首先還是老規矩,我們得先把用到Array型資料的地方作為依賴收集起來,那麼第一問題就是該在哪裡收集呢?

    其實Array型資料的依賴收集方式和Object資料的依賴收集方式相同,都是在「getter」中收集。那麼問題就來了,不是說Array無法使用「Object.defineProperty」方法嗎?無法使用怎麼還在「getter」中收集依賴呢?

    其實不然,我們回想一下平常在開發的時候,在元件的**data8*中是不是都這麼寫的:

    data(){
      return {
        arr:[1,2,3]
      }
    }
    

    想想看,arr這個資料始終都存在於一個object資料物件中,而且我們也說了,誰用到了資料誰就是依賴,那麼要用到arr這個資料,是不是得先從object資料物件中獲取一下arr資料,而從object資料物件中獲取arr資料自然就會觸發arr的「getter」,所以我們就可以在「getter」中收集依賴。

    總結一句話就是:Array型資料還是在「getter」中收集依賴。

    3.3 使Array型資料可觀測

    上一章節中我們知道了Array型資料還是在「getter」中收集依賴,換句話說就是我們已經知道了Array型資料何時被讀取了。

    回想上一篇文章中介紹Object資料變化偵測的時候,我們先讓Object資料變的可觀測,即我們能夠知道資料什麼時候被讀取了、什麼時候發生變化了。同理,對於Array型資料我們也得讓它變的可觀測,目前我們已經完成了一半可觀測,即我們只知道了Array型資料何時被讀取了,而何時發生變化我們無法知道,那麼接下來我們就來解決這一問題:當Array型資料發生變化時我們如何得知?

    相關推薦

    學習Vue原始碼 系列變化

    系列文章: 手牽手,從零學習Vue原始碼 系列一(前言-目錄篇) 手牽手,從零學習Vue原始碼 系列二(變化偵測篇) 陸續更新中... 預計八月中旬更新完畢。 1 概述 Vue最大的特點之一就是資料驅動檢視,那麼什麼是資料驅動檢視呢? 其實,我們可以把資料理解為狀態,而檢視就是使用者可直觀看到頁面。頁面不可

    實現Vue的元件庫-Slider元件實現

    實現一個Slider元件,方便使用者通過拖動滑塊在一個固定區間內進行選擇,增強互動細節。 概述: 在使用者手動一些限定數字時,如果採用輸入框的形式,會需要提示資訊和錯誤資訊來引導使用者,這就存在一些冗餘操作。所以衍生出Slider元件,方便使用者拖動來選定一個值。 該元件的痛點在於:

    實現Vue的元件庫- File-Reader實現

    實現一個File-Reader元件用來讀取本地資源。 概述: 在使用者手動上傳一些資源的時候,需要分為兩步,第一步是將其從本地讀取出來,得到一個file物件,然後再上傳至伺服器。該元件用於第一步,然後可通過後續進一步封裝程Upload元件。 該元件的痛點在於: 新增拖拽上傳的功能;

    實現Vue的元件庫- Breadcrumb 實現

    顯示當前頁面的路徑,快速返回之前的任意頁面。 該元件的痛點在於: 採用vnode設定擴充套件性較好的分隔符; 利用vue-router高亮已選中的路徑。 1. 例項 程式碼 <!-- 基礎用法 --> <fat-breadcrumb

    實現Vue的元件庫- Hover-Tip 實現

    常用於展示滑鼠 hover 時的提示資訊。 該元件的痛點在於: 純CSS實現; 如何利用slot使元件易擴充套件,能夠適應多種場景。 1. 例項 程式碼 <!-- 基礎用法 --> <fat-hovertip> <te

    造輪子:搭建一個簡單的nodejs伺服器開始搭建一個自用網站0

    伺服器用的是阿里雲最早期的伺服器低配版本1Gcpu,512M記憶體,20G硬碟,1M頻寬,平常只是用來做測試,目前只處理業務邏輯,網站的設計上儘量避免佔用太多的頻寬, 靜態檔案的儲存用的是阿里雲oss,100G空間,夠放視訊,圖片什麼的, html/js/c

    實現Vue的元件庫- InputNumber 實現

    基於 Input 元件進行拓展的 InputNumber 元件 InputNumer 元件的難點在於: 實現滑鼠長按,計數器數值變動; 導致 InputNumber 元件的值變化,有以下操作v-model繫結值的變化,加、減按鈕,input元件的輸入,需要對上述結果進行處理,所

    3開始搭建SSHM開發框架整合Spring MVC

    目錄 本專題部落格已共享在(這個可能會更新的稍微一些) 1.修改pom.xml,增加spring-mvc 的依賴 <project xmlns="http://maven.apache.org/POM/4.

    spring cloud 入門【Eureka註冊中心微服務之間服務呼叫方式FeignClient進行服務呼叫

    FeignClient 支隊服務消費方進行修改,服務提供方不需要修改   還是對  User 進行修改  UserApplication 中新增  @EnableFeignClients UserApplication 程式碼如下: pac

    深度學習Tensorflow生產環境部署下·模型部署

    前一篇講過環境的部署篇,這一次就講講從程式碼角度如何匯出pb模型,如何進行服務呼叫。 1 hello world篇 部署完docker後,如果是cpu環境,可以直接拉取tensorflow/serving,如果是GPU環境則麻煩點,具體參考前一篇,這裡就不再贅述了。 cpu版本的可以直接拉取t

    Hibernate的學習之路十session的快照機制

    前言 本片文章主要是說明了,hibernate的快照機制,能夠自動更新,不用update。 分析 在建立session這個一級快取物件的時候,session分為2塊區域,一個是快取區域。一個是快照

    Android菜鳥之學習android原始碼SystemUI導航欄初步認識及修改

    涉及到系統的定製開發,不可缺少的一個就是系統導航欄和狀態列的修改,而這部分的修改通常都涉及到SyetemUI這個系統應用的修改,它的路徑通常是位於platform\frameworks\base\packages\SystemUI。 先來說說導航欄的修改吧,導航

    #Java學習之路——基礎階段第十四

    out 出現 萬能 -c ack 分隔 status osi 版本 我的學習階段是跟著CZBK黑馬的雙源課程,學習目標以及博客是為了審查自己的學習情況,畢竟看一遍,敲一遍,和自己歸納總結一遍有著很大的區別,在此期間我會參雜Java瘋狂講義(第四版)裏面的內容。 前言:此隨

    使用uni-app開發一款視訊小程式 (系列上 準備工作)

    系列文章 手牽手,使用uni-app從零開發一款視訊小程式 (系列上 準備工作篇) 手牽手,使用uni-app從零開發一款視訊小程式 (系列下 開發實戰篇) 前言 好久不見,很久沒更新部落格了,前段時間在深圳出差,胡吃海喝頹廢了很久,不想每天下班刷抖音、打遊戲虛度光陰,準備把之前做的一個小程式案例詳細的介

    使用uni-app開發一款視訊小程式 (系列下 開發實戰)

    系列文章 手牽手,使用uni-app從零開發一款視訊小程式 (系列上 準備工作篇) 手牽手,使用uni-app從零開發一款視訊小程式 (系列下 開發實戰篇) 掃碼體驗,先睹為快 可以掃描下微信小程式的二維碼,體驗一下開發完畢的效果: 程式碼地址: GitHub : https://github.co

    【轉】帶你用vue擼後臺 系列(登錄權限)

    userinfo ogr abort 變化 再次 狀態碼 quest -o 監聽 前言 拖更有點嚴重,過了半個月才寫了第二篇教程。無奈自己是一個業務猿,每天被我司的產品虐的死去活來,之前又病了一下休息了幾天,大家見諒。 進入正題,做後臺項目區別於做其它的項目,權限驗證與

    學習筆記GAN001:生成式對抗網絡只需10步開始到調試

    sar quest 從零開始 http demo pip lib download mark 生成式對抗網絡(gennerative adversarial network,GAN),目前最火的非監督深度學習。一個生成網絡無中生有,一個判別網絡推動進化。學技術,不先著急看書

    教程 | 僅需六步實現機器學習演算法!

    從頭開始寫機器學習演算法能夠獲得很多經驗。當你最終完成時,你會驚喜萬分,而且你明白這背後究竟發生了什麼。 有些演算法比較複雜,我們不從簡單的演算法開始,而是要從非常簡單的演算法開始,比如單層感知器。 本文以感知器為例,通過以下 6 個步驟引導你從頭開始寫演算法:  ●  

    梳理Python基本認識基本型別開始學習Python

    先羅列一下Python提供的基本資料型別:數值(整型、浮點型、複數、布林型等)、字串、列表、元組、字典、集合等,將它們簡單分類如下: 推薦下小編的Python學習群;629440234,不管你是小白還是大牛,小編我都歡迎,不定期分享乾貨,包括小編自己整理的一份2018最新的Python和0基礎入

    python 認真學習的第天開始

    #剪刀石頭布的小遊戲 #和第一天的程式碼相比,通過編寫函式的方式來顯示電腦出的什麼,程式碼沒有那麼臃腫 import random i=random.randint(1,3) user=int(input('請出拳(1剪