在前面的學習之中,我們能輸入的圖片都是內置的圖片或者數(shù)據(jù)集的圖片。那么我們在這一小節(jié)便來學習一下如何將自己繪制的圖片輸入到 TensorBoard 之中。這里涉及到一定的繪圖知識,因此我們不會繪制過于復雜的圖片,我們會繪制一張簡單的圖片,然后將其輸出到 TensorBoard 之中。首先我們要繪制出一張我們自定義的圖片:import matplotlib.pyplot as pltimport iofigure = plt.figure()# 繪圖x = [1, 2, 3, 4]y = [1.2, 2.5, 4.5, 7.5]plt.plot(x, y)在這張圖片之中,我們簡單地繪制了一條線段。然后我們便可以將其保存為 PNG 格式:# 定義緩存區(qū)buf = io.BytesIO()# 保存為png圖片plt.savefig(buf, format='png')# 關閉畫布plt.close(figure)buf.seek(0)然后我們將保存好的png圖片轉化為TensorFlow的圖片格式:# 轉化為TensorFlow的圖片格式image = tf.image.decode_png(buf.getvalue(), channels=4)# 增添一維數(shù)據(jù),表示Batchimage = tf.expand_dims(image, 0)最后我們便可以將圖片輸出到 TensorBoard 日志:with file_writer.as_default(): tf.summary.image("Own Image", image, step=0)然后我們便可以看到我們的圖片的形式:
除了描述帶有屬性的普通對象外,接口也可以描述函數(shù)類型。為了使接口表示函數(shù)類型,我們需要給接口定義一個調用簽名。 它就像是一個只有 參數(shù)列表 和 返回值類型 的函數(shù)定義。interface SearchFunc { (source: string, subString: string): boolean;}let mySearch: SearchFunc;mySearch = function(source: string, subString: string): boolean { return source.search(subString) > -1;}對于函數(shù)類型的類型檢查來說,函數(shù)的參數(shù)名不需要與接口里定義的名字相匹配。你可以改變函數(shù)的參數(shù)名,只要保證函數(shù)參數(shù)的位置不變。函數(shù)的參數(shù)會被逐個進行檢查:interface SearchFunc { (source: string, subString: string): boolean;}let mySearch: SearchFunc;// source => src, subString => submySearch = function(src: string, sub: string): boolean { return src.search(sub) > -1;}如果你不想指定類型,TypeScript 的類型系統(tǒng)會推斷出參數(shù)類型,因為函數(shù)直接賦值給了 SearchFunc 類型變量。interface SearchFunc { (source: string, subString: string): boolean;}let mySearch: SearchFunc;mySearch = function(src, sub) { let result = src.search(sub); return result > -1;}如果接口中的函數(shù)類型帶有函數(shù)名,下面兩種書寫方式是等價的:interface Calculate { add(x: number, y: number): number multiply: (x: number, y: number) => number}
1218運行結果:可以看到這個案例雖然文字撐開了內容導致溢出,然后出現(xiàn)滾動條可以上下滾動,但背景色卻沒有變化,這是因為我們是在主盒子上面加的背景色。這種靠子元素來撐開內容的溢出應該把樣式全部寫在子元素上面去,父元素只提供一個高度和一個overflow-y即可:1219運行結果:
datetime 類年、月、日、小時、分鐘和秒表示日期和時間,包含 date 對象和 time 對象的所有信息,示例如下:>>> from datetime import datetime,date,time>>> datetime.today()datetime.datetime(2020, 5, 22, 12, 27, 34, 896212)從 datetime 模塊中引入類 datetime、類 date、類 timedatetime.today() 返回表示今天的日期對象,包括年、月、日、小時、分鐘、秒和微秒>>> d = date(2020,12,12)>>> t = time(8,8,8,8)>>> dt = datetime.combine(d, t)>>> dtdatetime.datetime(2020, 12, 12, 8, 8, 8, 8)構造日期對象 d構造時間對象 tdatetime.combine(d, t) 將日期對象 d 和時間對象 t 組合形成一個日期時間對象 dt>>> dt.isoformat()'2020-12-12T08:08:08.000008'>>> dt.strftime("%Y-%m-%d %H:%M:%S:%f")'2020-12-12 08:08:08:000008't.isoformat() 返回 ISO 8601 格式的時間字符串t.strftime() 返回指定格式的時間格式%Y 表示年份%m 表示月份%d 表示日%H 表示小時%M 表示分鐘%S 表示秒%f 表示微秒>>> u = dt.replace(year=2000, hour=20)>>> udatetime.datetime(2000, 12, 12, 20, 8, 8, 8)>>> u.year2000>>> u.hour20dt.replace() 替換年、月、日、小時、分鐘、秒生成一個新時間將年份替換為 2000將小時替換為 20
增加一個 500px 的透視效果<div class="demo"> <div class="cell"></div></div>.demo{ perspective: 500px; background: #f2f2f2;}.cell{ width: 100px; height: 100px; background: #000; transform: translate3d(1px,-1px,-200px) rotateY(70deg);}效果圖:無透視有透視效果圖解釋:加了 500px 的透視效果。修改觀察點的位置為 50% 50% 。.demo{ perspective: 500px; background: #f2f2f2; perspective-origin:50% 50%;}.cell{ width: 100px; height: 100px; background: #000; transform: translate3d(1px,-1px,-200px) rotateY(70deg);}效果圖:設置透視的 x 軸和 y 軸。
首先點擊菜單欄中的編輯(E)菜單,在點擊后的下拉列表中選擇快捷鍵(Y),彈出如下圖界面:從圖片中我們可以看到,我們在這里可以通過加號和減號來對快捷鍵進行新添和刪除。也可以查找系統(tǒng)默認定義的一些快捷鍵的使用方法。
進入 /home 目錄,使用 nano 命令查看 new.log 文件內容,命令如下:cd /homelsnano new.log執(zhí)行結果如下圖:執(zhí)行如上圖所示命令就會進入 nano 編輯器界面,如下圖所示:Tips:如圖所示編輯完自己想要修改的內容之后按下 Ctrl + X 輸入 y 回車即可保存退出。
前面有提到訪問對象屬性的機制。function Point(x, y) { this.x = x; this.y = y;}var point = new Point(1, 2);console.log(point.toString());假如要訪問 point 對象的 toString 方法,首先會去 point 類里找,很顯然是沒有這個方法的。然后回去 point 類的原型對象上找,也就是 Point 函數(shù)的 prototype 屬性上,很顯然也是沒有的。然后會再往上一層找,也就是找到了 Point.prototype.__proto__ 上 (等同于 point.__proto__.__proto__),這個時候就找到了 toString,隨后被返回并且調用。Point.prototype.__proto__ 其實就是 Object.prototype。console.log( Point.prototype.__proto__ === Object.prototype,); // 輸出:true假如檢查到 Object.prototype 還沒有目標屬性,則在往上就找不到了,因為 Object.prototype.__proto__ 是 null。也就是說原型查找的末端是 null,碰到 null 就會終止查找。這些原型環(huán)環(huán)相扣,就形成了原型鏈。有些同學會有疑問,為什么 Point.prototype 的原型是 Object.prototype。其實 Point.prototype 也是一個對象,可以理解成這個對象是通過 new Object 創(chuàng)建的,所以原型自然是 Object.prototype。
在處理手勢的過程中,我們還會調用一些系統(tǒng)方法來輔助完成事件處理,主要有以下幾個:getEventTime():獲取事件發(fā)生的時間戳getFocusX():獲取當前手勢焦點的 X 軸坐標getFocusY():獲取當前手勢焦點的 Y 軸坐標getTimeDelta():獲取兩次縮放時間的時間差isInProgress():判斷當前是否正處理縮放過程中onTouchEvent(MotionEvent event):接收系統(tǒng)觸摸事件,并分發(fā)到相應的監(jiān)聽器中
這次,我們依然使用貓狗分類的例子來進行實現(xiàn),具體的代碼如下所示:注意:部分代碼來自 TensorFlow 官方 API 。import tensorflow_datasets as tfdsimport tensorflow as tfimport numpy as nptrain_data, validation_data = tfds.load( "cats_vs_dogs", split=["train[:80%]", "train[80%:]"], as_supervised=True,)# 重新調整大小train_data = train_data.map(lambda x, y: (tf.image.resize(x, (150, 150)), y))validation_data = validation_data.map(lambda x, y: (tf.image.resize(x, (150, 150)), y))# 分批次train_data = train_data.batch(32)validation_data = validation_data.batch(32)# 遷移模型base_model = tf.keras.applications.Xception( weights="imagenet", input_shape=(150, 150, 3), include_top=False,)base_model.trainable = False# 定義輸入inputs = tf.keras.Input(shape=(150, 150, 3))# 數(shù)據(jù)正則化norm_layer = tf.keras.layers.experimental.preprocessing.Normalization()x = norm_layer(inputs)mean = np.array([127.5] * 3)norm_layer.set_weights([mean, mean ** 2])# 數(shù)據(jù)經(jīng)過遷移模型x = base_model(x, training=False)# 數(shù)據(jù)經(jīng)過自定義網(wǎng)絡x = tf.keras.layers.GlobalAveragePooling2D()(x)outputs = tf.keras.layers.Dense(1)(x)model = tf.keras.Model(inputs, outputs)model.summary()model.compile( optimizer=tf.keras.optimizers.Adam(), loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=[tf.keras.metrics.BinaryAccuracy()],)model.fit(train_ds, epochs=20, validation_data=validation_ds)在這里的代碼之中,我們有幾處需要注意的地方:在數(shù)據(jù)獲取方面,我們采用了 tfds.load 函數(shù),該函數(shù)能夠直接獲取相應的內置數(shù)據(jù)集,同時進行相應的分割,這里我們按照 8:2 的比例來進行訓練集、測試集的劃分;我們使用 map 函數(shù),來將所有的數(shù)據(jù)的圖片重新調整至(150, 150)大小,我們將圖片調整至相同大小是為了方便后面的處理;使用 tf.keras.applications.Xception API 來獲取已經(jīng)預訓練的 Xception 模型,在該 API 之中,包含三個參數(shù):weights:表示在哪個數(shù)據(jù)集上訓練;input_shape:表示輸入圖片的形狀;include_top=False:表示不含頂層網(wǎng)絡,因為我們要定義自己的網(wǎng)絡。然后我們使用 base_model.trainable=False 語句來將基本模型的訓練參數(shù)凍結,這樣我們就不能訓練 Xception 的參數(shù)。我們使用了 tf.keras.layers.experimental.preprocessing.Normalization 這個 API 來進行數(shù)據(jù)的正則化,我們需要通過 norm_layer.set_weights () 設定它的權重:第一個參數(shù)是輸入的每個通道的平均值,這里是 255/2=127.5;第二個參數(shù)是第一個參數(shù)的平方;最后我們采用了一種新的定義模型的方式:先定義一個 Input ,然后將該 Input 逐次經(jīng)過自己需要處理的網(wǎng)絡層得到 output,最后通過 tf.keras.Model (inputs, output) 來讓 TensorFlow s 根據(jù)數(shù)據(jù)的流動過程來自動生成網(wǎng)絡模型。最終我們可以得到結果:Model: "functional_5"_________________________________________________________________Layer (type) Output Shape Param # =================================================================input_10 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________normalization_3 (Normalizati (None, 150, 150, 3) 7 _________________________________________________________________xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________global_average_pooling2d_2 ( (None, 2048) 0 _________________________________________________________________dropout_2 (Dropout) (None, 2048) 0 _________________________________________________________________dense_2 (Dense) (None, 1) 2049 =================================================================Total params: 20,863,536Trainable params: 2,049Non-trainable params: 20,861,487_________________________________________________________________Epoch 1/20291/291 [==============================] - 9s 31ms/step - loss: 0.1607 - binary_accuracy: 0.9313 - val_loss: 0.0872 - val_binary_accuracy: 0.9703Epoch 2/20291/291 [==============================] - 8s 27ms/step - loss: 0.1181 - binary_accuracy: 0.9501 - val_loss: 0.0869 - val_binary_accuracy: 0.9690......Epoch 20/20291/291 [==============================] - 8s 27ms/step - loss: 0.0914 - binary_accuracy: 0.9841 - val_loss: 0.0875 - val_binary_accuracy: 0.9765我們可以看到,我們的模型最終達到了 97% 的分類準確率,這是一個非常高的準確率,而這得益于 Xception 模型強大的特征提取能力。
在 MainActivity 中我們對 id 為 touch 的 TextView 注冊觸摸監(jiān)聽器,然后在DOWN中獲取觸摸起點,并寫在對應的 TextView 中;隨后在MOVE中實時獲取滑動偏移量,也在對應的 TextView 中進行實時更新,代碼如下:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.MotionEvent;import android.view.View;import android.widget.TextView;public class MainActivity extends Activity { float xAxis = 0f; float yAxis = 0f; float downXAxis = 0f; float downYAxis = 0f; TextView downX, downY, moveX, moveY; TextView touch; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); downX = findViewById(R.id.down_x); downY = findViewById(R.id.down_y); moveX = findViewById(R.id.move_x); moveY = findViewById(R.id.move_y); touch = findViewById(R.id.touch); // 1、注冊觸摸監(jiān)聽器 touch.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { final int actionPeformed = event.getAction(); // 2、判斷當前觸摸狀態(tài) switch (actionPeformed) { case MotionEvent.ACTION_DOWN: { // 3、在不同狀態(tài)中進行觸摸事件處理 downXAxis = event.getX(); downYAxis = event.getY(); downX.setText("按下的位置橫坐標:" + downXAxis); downY.setText("按下的位置縱坐標:" + downYAxis); break; } case MotionEvent.ACTION_MOVE: { final float x = event.getX(); final float y = event.getY(); final float dx = x - downXAxis; final float dy = y - downYAxis; xAxis += dx; yAxis += dy; moveX.setText("移動距離的橫坐標:" + xAxis); moveY.setText("移動距離的縱坐標:" + yAxis); break; } } return true; } }); }}編譯運行,效果如下:觸摸左下角的“點我開始滑動”,當前觸摸的坐標就會在 TextView 中展示了,然后滑動手指,隨著滑動的偏移量的變化,也會在 TextView 中進行同步更新。
perspective 屬性定義 3D 元素距視圖的距離,以像素計算。該屬性允許您改變 3D 元素查看 3D 元素的視圖。當為元素定義 perspective 屬性時,其子元素會獲得透視效果,而不是元素本身。perspective-origin 屬性定義 3D 元素所基于的 X 軸和 Y 軸。該屬性允許您改變 3D 元素的底部位置。當為元素定義 perspective-origin 屬性時,其子元素會獲得透視效果,而不是元素本身。
屬性定義圖像為服務器端圖片映射,就是定義圖片中可以點擊的區(qū)域,并且將對標發(fā)送到服務器,需要配合 a 標簽使用。972上述代碼,當用戶點擊圖片時,瀏覽器會將點擊的坐標點以 x,y 作為參數(shù),以 GET 的方式請求服務器,例如當用戶點擊圖片的坐標點(8,8)時,服務器會受到一條 HTTP 請求 http://www.baidu.com?8,8,此坐標點是相對定位坐標點。
有時您需要對字符串的每個字符進行操作,這個時候就需要遍歷字符串的每個字符。第一種方法,您可以使用each_char方法:實例:# 輸出每一個字符"rubyguides".each_char { |ch| puts ch # 這里ch參數(shù)的名稱可以是任意的}# ---- 輸出結果 ----rubyguides另外,您也可以使用chars方法將字符串轉換為字符數(shù)組,對數(shù)組上每個對象進行迭代(iterate)。實例:array_of_characters = "rubyguides".chars# ---- 輸出結果 ----["r", "u", "b", "y", "g", "u", "i", "d", "e", "s"]
讓我們先創(chuàng)建一個 txt 文件作為附件。echo "這是一個測試的txt文件" > test.txt然后讓我再次修改 headers,增加Content-type: multipart/mixed。注意事項:我們需要使用 pack(“m”) 將函數(shù)轉化為 base64 格式的。filename = File.expand_path(File.dirname(__FILE__) + "/test.txt")encodedcontent = [File.read(filename)].pack("m")msg = <<MESSAGEFrom: Andrew <#{sender_email}>To: Testuser <#{receiver_email}>Subject: Test Upload File/miDate: #{Time.now.strftime("%a, %d %b %Y %H:%M:%S +0800")}Message-Id: <#{rand.to_s[3...8]}.message.@163.com>Content-Transfer-Encoding:8bitContent-Type: multipart/mixed; name=\"#{filename}\"Content-Transfer-Encoding:base64Content-Disposition: attachment; filename="#{filename}"#{encodedcontent}MESSAGEputs msg 改好即可。
進入 MySQL 安裝目錄: 命令 : cd /usr/local/mysql執(zhí)行初始化: 命令 : mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data # 過程較慢,需要耐心等待Tips:在初始化過程中如果出現(xiàn)以下輸出:mysqld: error while loading shared libraries: libnuma.so.1: cannot open shared object file: No such file or directory,可以使用命令:可以使用命令 yum -y install numactl 來解決。出現(xiàn)這個錯誤是因為 Linux 缺少軟件依賴包 numactl,上述命令時通過 yum 倉庫安裝 numactl 依賴包。
要打開 Memory Profiler,請按以下步驟操作:依次點擊 View > Tool Windows > Profiler,可以點擊工具欄中的 Profile 圖標。從 Android Profiler 工具欄中選擇要分析的設備和應用進程。點擊 MEMORY 時間軸上的任意位置以打開 Memory Profiler。當我們首次打開 Memory Profiler 時,我們將看到一條表示應用內存使用量的詳細時間軸,并可使用各種工具來強制執(zhí)行垃圾回收、捕獲堆轉儲以及記錄內存分配。用于強制執(zhí)行垃圾回收事件的按鈕;用于捕獲堆轉儲的按鈕;用于指定分析器多久捕獲一次內存分配的下拉菜單。選擇適當?shù)倪x項可幫助我們在分析時提高應用性能;用于縮放時間軸的按鈕;用于跳轉到實時內存數(shù)據(jù)的按鈕;事件時間軸,顯示活動狀態(tài)、用戶輸入事件和屏幕旋轉事件;內存使用量時間軸,它會顯示以下內容:一個堆疊圖表,顯示每個內存類別當前使用多少內存,如左側的 y 軸以及頂部的彩色鍵所示;一條虛線,表示分配的對象數(shù),如右側的 y 軸所示;每個垃圾回收事件的圖標。
Splash 中涉及到元素定位和操作的方法主要有如下幾個:splash:select():從當前網(wǎng)頁的 DOM 中選擇與指定 CSS 選擇器匹配的第一個 HTML 元素;splash:select_all():從當前網(wǎng)頁的 DOM 中選擇與指定 CSS 選擇器匹配的 HTML 元素列表;splash:send_keys():將鍵盤事件發(fā)送到頁面上下文;splash:send_text():將文本作為輸入發(fā)送到頁面上下文,一個字符一個字符發(fā)送;來看看我們對這些方法的一個簡單實例:function main(splash) splash:go("https://www.baidu.com") splash:wait(2) input = splash:select("#kw") input:send_text("慕課網(wǎng) wiki") splash:wait(2) return { png = splash:png() }end來看看針對百度頁面的執(zhí)行效果:使用 splash 服務獲取百度搜索結果另外一個例子,我們還是前面的頭條熱點數(shù)據(jù),我們加上滾動效果后能提取出更多的熱點新聞,那么就在這里使用 splash:select_all() 方法將這些熱點新聞的標題提取出來。為此,我們編寫如下的 lua 代碼:function main(splash, args) local treat = require('treat') assert(splash:go(args.url)) assert(splash:wait(2)) splash.scroll_position = {y=1000} assert(splash:wait(2)) splash.scroll_position = {y=1500} assert(splash:wait(5)) news_list = splash:select_all('div.title-box a') local result = {} for idx, a in ipairs(news_list) do result[idx] = a.node.innerHTML end return treat.as_array(result)end來看看渲染后的結果,如下:使用 select_all() 方法提取熱點新聞標題
date 類以年、月和日表示日歷中的日期,示例如下:>>> from datetime import date>>> date.today()datetime.date(2020, 5, 22)>>> d = date(2020, 5, 1)從 datetime 模塊中引入 date 類date.today() 返回表示今天的日期對象構造日期 2020 年 5 月 1 日>>> d.year2020>>> d.month5>>> d.day1d.year 返回日期對象的年份d.month 返回日期對象的月份d.day 返回日期對象的日>>> d.weekday()4>>> d.isoweekday()5>>> d.isoformat()'2020-05-01'>>> d.isocalendar()(2020, 18, 5)d.weekday() 返回一星期中的第幾天, 星期一是 0d.isoweekday() 返回一星期中的第幾天, 星期一是 1d.isoformat() 以格式 YYYY-MM-DD 返回日期的字符串形式d.isocalendar() 返回一個元組第 0 項是年份第 1 項是這一年的第幾周第 2 項是周幾>>> d.ctime()'Fri May 1 00:00:00 2020'>>> d.strftime("%Y-%m-%d")'2020-05-01'd.ctime() 返回一個表示日期的字符串d.strftime() 返回指定格式的日期字符串%Y 表示年份%m 表示月份%d 表示日>>> e = d.replace(year=2000, month=12)>>> edatetime.date(2000, 12, 1)d.replace() 替換年份、月份、日期生成一個新日期將年份替換為 2000將月份替換為 12
IMDB? 數(shù)據(jù)集合一共包含 50000 條數(shù)據(jù),每條數(shù)據(jù)都是從 IMDB? 電影的評價中選取,同時每個評論都被歸類為**“正面評價”或“負面評價”**。比如:x: [1, 778, 128, 74, 12, 630, 163, 15, 4, 1766, 7982, 1051, 2, 32, 85, 156, 45, 40, 148, 139, 121, 664, 665, 10, 10, 1361, 173, 4, 749, 2, 16, 3804, 8, 4, 226, 65, 12, 43, 127, 24, 2, 10, 10]y: 0其中評論是被編碼之后所得到的數(shù)組,每個英文單詞對應一個固定的數(shù)字。而標簽用 0 和 1 來表示“負面評價”和“證明評價”。將上述例子還原一下就是:x: "begins better than it ends funny that the russian submarine crew <UNK> all other actors it's like those scenes where documentary shots br br spoiler part the message <UNK> was contrary to the whole story it just does not <UNK> br br"y: "Negative"這 50000 條數(shù)據(jù)它們具體的分布如下:訓練集包含 25000 條訓練數(shù)據(jù),其中正負數(shù)據(jù)各 12500 條;測試集包含 25000 條測試數(shù)據(jù),其中正負數(shù)據(jù)各 12500 條。換句話說,該數(shù)據(jù)集合上面的數(shù)據(jù)是**“平衡的”**,因為它包含的正樣本與負樣本的數(shù)目相同。在 TensorFlow 之中,我們可以直接通過調用內部 API 的方式來獲取該數(shù)據(jù)集:(train_data, train_labels), (test_data, test_labels) = \tf.keras.datasets.imdb.load_data(num_words=words_num)
在 TensorFlow 之中,我們目前一般采用創(chuàng)建 keras 層的方式來進行網(wǎng)絡層的構建,因此,我們的創(chuàng)建步驟大致分為以下幾步:定義網(wǎng)絡層的類并繼承 tf.keras.layers.Layer 類;在初始化方法或者 build 方法之中定義網(wǎng)絡的參數(shù);在 call 函數(shù)之中編寫具體的處理流程。在第二步之中,我們可以在初始化或者 build 方法之中定義網(wǎng)絡的參數(shù),兩者的效果在目前的教程之中時一樣的,因此為了簡單起見,我們統(tǒng)一在初始化函數(shù)之中定義我們的網(wǎng)絡參數(shù)。以下是一個簡單的例子:import tensorflow as tfclass MyLayer(tf.keras.layers.Layer): def __init__(self, hidden_units, input_units): super(MyLayer, self).__init__() self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal") self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal") def call(self, inputs): return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b在這個例子之中,我們定義了一個網(wǎng)絡層,該網(wǎng)絡層的數(shù)學形式為:y = w*(x**2) + b我們讓 x 進行平方之后乘上一個系數(shù) w ,然后加上一個常數(shù)項 b 。通過代碼我們可以發(fā)現(xiàn),我們在初始化函數(shù)之中進行了參數(shù)的初始化操作,然后再在 call 方式之中進行了具體的計算。對于添加參數(shù),我們最常用的方式是采用 add_weight 方法來進行的;該方法最常用的參數(shù)有兩個:shape: 表示該參數(shù)的形狀,比如 3x3 等;initializer: 初始化器,一般為 “zeros”(初始化為 0),或者 “random_normal”(隨機初始化)。我們可以通過具體的數(shù)據(jù)進行查看:x = tf.ones((5, 5))my_layer = MyLayer(10, 5)y = my_layer(x)print(y)輸出為:tf.Tensor([[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103 0.07284987 -0.5888218 0.13172552 0.01993698] [ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103 0.07284987 -0.5888218 0.13172552 0.01993698] [ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103 0.07284987 -0.5888218 0.13172552 0.01993698] [ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103 0.07284987 -0.5888218 0.13172552 0.01993698] [ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103 0.07284987 -0.5888218 0.13172552 0.01993698]], shape=(5, 10), dtype=float32)因此我們可以發(fā)現(xiàn)我們的網(wǎng)絡成功的得到了輸出。但是因為我們是隨機初始化的參數(shù),因此輸出一定會是雜亂無章的。
一個 npm 包 由一個 package.json 文件描述。package.json 所在的位置通常會被作為項目的根目錄??梢酝ㄟ^ npm 提供的命令創(chuàng)建一個 package.json,可以先創(chuàng)建一個項目目錄,然后在終端中進入到這個目錄,使用 npm init -y 命令,就可以創(chuàng)建一個最簡單的 package.json。當然現(xiàn)在的工程化的前端項目,也會用 package.json 來描述項目信息,來管理依賴、工作里等,一個包不一定要發(fā)送到 npm 上。
BooleanField:之前演示過,它會被渲染成前端的 checkbox 組件。從源碼上看它似乎沒有額外特殊的屬性。主要就是繼承了 Field 類,然后重寫了 to_python() 等方法class BooleanField(Field): widget = CheckboxInput def to_python(self, value): """Return a Python boolean object.""" # Explicitly check for the string 'False', which is what a hidden field # will submit for False. Also check for '0', since this is what # RadioSelect will provide. Because bool("True") == bool('1') == True, # we don't need to handle that explicitly. if isinstance(value, str) and value.lower() in ('false', '0'): value = False else: value = bool(value) return super().to_python(value) def validate(self, value): if not value and self.required: raise ValidationError(self.error_messages['required'], code='required') def has_changed(self, initial, data): if self.disabled: return False # Sometimes data or initial may be a string equivalent of a boolean # so we should run it through to_python first to get a boolean value return self.to_python(initial) != self.to_python(data)CharField:用的最多的,會被渲染成輸入框,我們可以通過 widget 屬性值控制輸入框樣式等。這在前面的登錄表單例子中也是演示過的。 name = forms.CharField( label="賬號", min_length=4, required=True, error_messages={'required': '賬號不能為空', "min_length": "賬號名最短4位"}, widget=forms.TextInput(attrs={'class': "input-text", 'placeholder': '請輸入登錄賬號'}) )class CharField(Field): def __init__(self, *, max_length=None, min_length=None, strip=True, empty_value='', **kwargs): self.max_length = max_length self.min_length = min_length self.strip = strip self.empty_value = empty_value super().__init__(**kwargs) if min_length is not None: self.validators.append(validators.MinLengthValidator(int(min_length))) if max_length is not None: self.validators.append(validators.MaxLengthValidator(int(max_length))) self.validators.append(validators.ProhibitNullCharactersValidator()) def to_python(self, value): """Return a string.""" if value not in self.empty_values: value = str(value) # 是否去掉首尾空格 if self.strip: value = value.strip() if value in self.empty_values: return self.empty_value return value def widget_attrs(self, widget): attrs = super().widget_attrs(widget) if self.max_length is not None and not widget.is_hidden: # The HTML attribute is maxlength, not max_length. attrs['maxlength'] = str(self.max_length) if self.min_length is not None and not widget.is_hidden: # The HTML attribute is minlength, not min_length. attrs['minlength'] = str(self.min_length) return attrs除了 Field 屬性外,CharField 還有 max_length 和 min_length 等屬性。這些也都會反映在渲染的 input 元素上,同時校驗器也會添加該屬性的校驗:if min_length is not None: self.validators.append(validators.MinLengthValidator(int(min_length)))if max_length is not None: self.validators.append(validators.MaxLengthValidator(int(max_length)))CharField 還是很多 Field 類的父類,比如 RegexField、EmailField 等。ChoiceField:前面我們在 widget 屬性的小實驗中看到了 ChoiceField 對應的 widget 屬性值是 Select 類,也即對應 select 元素。我們繼續(xù)使用前面的登錄表單來演示下這個 ChoiceField 類。我們除了添加 Field 之外,也需要添加下前端代碼,如下所示。{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" />{% if not success %}<form action="/hello/test_form_view2/" method="POST">{% csrf_token %}<div><span>{{ form.name.label }}:</span>{{ form.name }}<div><span>{{ form.password.label }}:</span>{{ form.password }}<!------- 新增login_type字段的HTML -------------><div><span>{{ form.login_type.label }}:</span>{{ form.login_type }}<!--------------------------------------------><div>{{ form.save_login }}{{ form.save_login.label }}</div><div><input class="input-text input-red" type="submit" value="登錄" style="width: 214px"/></div>{% if err_msg %}<div><label class="color-red">{{ err_msg }}</label</div>{% endif %}</form>{% else %}<p>登錄成功</p>{% endif %}login_type = forms.ChoiceField( label="賬號類型", required=True, initial=1, choices=((0, '普通用戶'), (1, '管理員'), (2, '其他')), error_messages={'required': '必選類型' }, widget=forms.Select(attrs={'class': "input-text"}), )效果圖如下所示??梢钥吹?,這里 initial 屬性值表示最開始選中那個選項,而 choices 屬性值是一個元組,表示多選框的顯示 name 值和實際 value 值。DateField:默認的小部件是 DateInput。它有一個比較重要的屬性:input_formats,用于將字符串轉換為有效datetime.date對象的格式列表。如果沒有提供 input_formats 參數(shù),則默認的格式為:['%Y-%m-%d', # '2006-10-25' '%m/%d/%Y', # '10/25/2006' '%m/%d/%y'] # '10/25/06'class DateField(BaseTemporalField): widget = DateInput input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') default_error_messages = { 'invalid': _('Enter a valid date.'), } def to_python(self, value): """ Validate that the input can be converted to a date. Return a Python datetime.date object. """ if value in self.empty_values: return None if isinstance(value, datetime.datetime): return value.date() if isinstance(value, datetime.date): return value return super().to_python(value) def strptime(self, value, format): return datetime.datetime.strptime(value, format).date()DateTimeField:默認的小部件是 DateTimeInput,這里會校驗對應的值是否是datetime.datetime、 datetime.date類型,或者按照 input_formats 參數(shù)格式化的字符串。同樣,如果沒有提供 input_formats 參數(shù),則默認的格式為:['%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d', # '2006-10-25' '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' '%m/%d/%Y %H:%M', # '10/25/2006 14:30' '%m/%d/%Y', # '10/25/2006' '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' '%m/%d/%y %H:%M', # '10/25/06 14:30' '%m/%d/%y'] # '10/25/06'class DateTimeField(BaseTemporalField): widget = DateTimeInput input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS') default_error_messages = { 'invalid': _('Enter a valid date/time.'), } def prepare_value(self, value): if isinstance(value, datetime.datetime): value = to_current_timezone(value) return value def to_python(self, value): """ Validate that the input can be converted to a datetime. Return a Python datetime.datetime object. """ if value in self.empty_values: return None if isinstance(value, datetime.datetime): return from_current_timezone(value) if isinstance(value, datetime.date): result = datetime.datetime(value.year, value.month, value.day) return from_current_timezone(result) result = super().to_python(value) return from_current_timezone(result) def strptime(self, value, format): return datetime.datetime.strptime(value, format)這些類的定義都是比較簡單的,都是基于 Field 類,有的基于 CharField 類等。后面我們會重點分析 Field 類。EmailField:EmailField 直接繼承 CharField,它和 CharField 的一個主要區(qū)別就是多加了一個默認的校驗器,主要校驗輸入的值是否是郵箱格式。其實現(xiàn)的代碼如下:class EmailField(CharField): widget = EmailInput default_validators = [validators.validate_email] def __init__(self, **kwargs): super().__init__(strip=True, **kwargs)IntegerField:對應的小部件是 NumberInput,輸入整數(shù)字符串。它可以輸入 min_Value、max_value 等參數(shù)用于控制輸入值的范圍。其源碼如下,和 CharFiled 類的代碼比較類似。class IntegerField(Field): widget = NumberInput default_error_messages = { 'invalid': _('Enter a whole number.'), } re_decimal = re.compile(r'\.0*\s*$') def __init__(self, *, max_value=None, min_value=None, **kwargs): self.max_value, self.min_value = max_value, min_value if kwargs.get('localize') and self.widget == NumberInput: # Localized number input is not well supported on most browsers kwargs.setdefault('widget', super().widget) super().__init__(**kwargs) if max_value is not None: self.validators.append(validators.MaxValueValidator(max_value)) if min_value is not None: self.validators.append(validators.MinValueValidator(min_value)) def to_python(self, value): """ Validate that int() can be called on the input. Return the result of int() or None for empty values. """ value = super().to_python(value) if value in self.empty_values: return None if self.localize: value = formats.sanitize_separators(value) # Strip trailing decimal and zeros. try: value = int(self.re_decimal.sub('', str(value))) except (ValueError, TypeError): raise ValidationError(self.error_messages['invalid'], code='invalid') return value def widget_attrs(self, widget): attrs = super().widget_attrs(widget) if isinstance(widget, NumberInput): if self.min_value is not None: attrs['min'] = self.min_value if self.max_value is not None: attrs['max'] = self.max_value return attrs對于 IntegerField 字段輸入的值,看 to_python() 方法,首先對于 20.0 這樣的形式會先去掉后面的 .0,然后用 int() 方法強轉,如果發(fā)生異常,那就表明該字段對應的值不是整數(shù),然后可以拋出異常。DecimalField:它繼承自 IntegerField,用于輸入浮點數(shù)。它有如下幾個重要參數(shù):max_value: 最大值min_value: 最小值max_digits: 總長度decimal_places: 小數(shù)位數(shù)來看看它的定義的代碼,如下:class DecimalField(IntegerField): default_error_messages = { 'invalid': _('Enter a number.'), } def __init__(self, *, max_value=None, min_value=None, max_digits=None, decimal_places=None, **kwargs): self.max_digits, self.decimal_places = max_digits, decimal_places super().__init__(max_value=max_value, min_value=min_value, **kwargs) self.validators.append(validators.DecimalValidator(max_digits, decimal_places)) def to_python(self, value): """ Validate that the input is a decimal number. Return a Decimal instance or None for empty values. Ensure that there are no more than max_digits in the number and no more than decimal_places digits after the decimal point. """ if value in self.empty_values: return None if self.localize: value = formats.sanitize_separators(value) value = str(value).strip() try: # 使用Decimal()方法轉換類型 value = Decimal(value) except DecimalException: raise ValidationError(self.error_messages['invalid'], code='invalid') return value def validate(self, value): super().validate(value) if value in self.empty_values: return if not value.is_finite(): raise ValidationError(self.error_messages['invalid'], code='invalid') def widget_attrs(self, widget): attrs = super().widget_attrs(widget) if isinstance(widget, NumberInput) and 'step' not in widget.attrs: if self.decimal_places is not None: # Use exponential notation for small values since they might # be parsed as 0 otherwise. ref #20765 step = str(Decimal(1).scaleb(-self.decimal_places)).lower() else: step = 'any' attrs.setdefault('step', step) return attrs可以看到在 to_python() 方法中,最后對該字段輸入的值使用 Decimal() 方法進行類型轉換 。FloatField:用于渲染成一個只允許輸入浮點數(shù)的輸入框。它同樣繼承自 IntegerField,因此它對應的小部件也是 NumberInput。class FloatField(IntegerField): default_error_messages = { 'invalid': _('Enter a number.'), } def to_python(self, value): """ Validate that float() can be called on the input. Return the result of float() or None for empty values. """ value = super(IntegerField, self).to_python(value) if value in self.empty_values: return None if self.localize: value = formats.sanitize_separators(value) try: value = float(value) except (ValueError, TypeError): raise ValidationError(self.error_messages['invalid'], code='invalid') return value def validate(self, value): super().validate(value) if value in self.empty_values: return if not math.isfinite(value): raise ValidationError(self.error_messages['invalid'], code='invalid') def widget_attrs(self, widget): attrs = super().widget_attrs(widget) if isinstance(widget, NumberInput) and 'step' not in widget.attrs: attrs.setdefault('step', 'any') return attrs其余的 Field 類如 ImageField,RegexField 就不一一描述了,具體可以參考官方文檔以及相應的源碼進行學習。
我們知道 main 函數(shù)是很多應用程序的入口,ios 也不例外,在 AppDelegate.swift 中有 @UIApplicationMain 的注解,這里就是 APP 啟動的入口。@UIApplicationMain //main函數(shù)注解入口,所以AppDelegate類相當于啟動入口類class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow?//默認加了UIWindow func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // KNFKotlinNativeFramework class is located in the framework that is generated during build. // If it is not resolved, try building for the device (not simulator) and reopening the project NSLog("%@", KNFKotlinNativeFramework().helloFromKotlin())//注意: 這里就是調用了Kotlin中的一個helloFromKotlin方法,并把返回值用Log打印出來,所以你會看到App啟動的時候是有一段Log被打印出來 return true } ...}KotlinNativeFramework 類:class KotlinNativeFramework { fun helloFromKotlin() = "Hello from Kotlin!" //返回一個Hello from Kotlin!字符串}但是呢,有追求的程序員絕對不能允許跑出來的是一個空白頁面,空白頁面那還怎么裝逼呢?哈哈。在 ViewController.swift 中的 viewDidLoad 函數(shù)中加入一個文本 (UILabel)。class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21)) label.center = CGPoint(x: 160, y: 285) label.textAlignment = .center label.font = label.font.withSize(15) label.text = "Hello IOS, I'm from Kotlin/Native" view.addSubview(label) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. }}最后重新 run 一遍,效果如下:
1204運行結果:可以看到這個案例雖然文字撐開了內容導致溢出,然后出現(xiàn)滾動條可以上下滾動,但背景色卻沒有變化,這是因為我們是在主盒子上面加的背景色。這種靠子元素來撐開內容的溢出應該把樣式全部寫在子元素上面去,父元素只提供一個高度和一個 overflow-y 即可:1205運行結果:
對于 arr 這樣的二維數(shù)組,可以用 2 個維度(x 方向和 y 方向)的序列來唯一定位每一個元素,multi-index 則可以打印出該種索引順序。multi_index 索引類型可以按照下圖來直觀理解:multi 順序的索引案例設置 flags=["multi_index"],效果如下:mul_it = np.nditer(arr, flags=['multi_index'])while not mul_it.finished: print("value:", mul_it[0], "index:<{}>".format(mul_it.multi_index)) mul_it.iternext()打印結果為:value: 0 index:<(0, 0)>value: 1 index:<(0, 1)>value: 2 index:<(0, 2)>value: 3 index:<(1, 0)>value: 4 index:<(1, 1)>value: 5 index:<(1, 2)>
2.1.1 一個基本的甘特圖基本的甘特圖由「標題」、「日期格式約定」、「分組及任務」三部分組成。實例 1:一個基本的甘特圖。?```mermaidgantt title 簡單的甘特圖 dateFormat YYYY-MM-DD section 分區(qū)1 任務1-1 :a1, 2014-01-01, 30d 任務1-2 :after a1 , 20d section 分區(qū)2 任務2-1 :2014-01-12 , 12d 任務2-2 : 24d?```渲染效果如下:2.1.2 規(guī)定日期格式通過設置 dateFormat 屬性,可以指定甘特圖定義日期時的解析方式。日期的格式支持以下情形:表達式取值示例描述 YYYY20144 位數(shù)年 YY142 位數(shù)年 Q1…4 季度數(shù) M MM1…12 月份數(shù) MMM MMMMJanuary…Dec 月份名稱 D DD1…31 月中天數(shù) Do1st…31st 月中第幾天 DDD DDDD1…365 年中天數(shù) X1410715640.579Unix 時間戳(秒)x1410715640579Unix 時間戳(毫秒)H HH0…2324 小時制小時數(shù) h hh1…1212 小時制小時數(shù) a Aam pm 上午、下午 m mm0…59 分鐘數(shù) s ss0…59 秒鐘數(shù) S0…9 十分之一秒 SS0…99 百分之一秒 SSS0…999 千分之一秒 Z ZZ+12:00 時區(qū)2.1.3 任務的定義Mermaid 甘特圖中,每個任務隸屬于一個分組,一個分組內可以定義多個任務,一張甘特圖表中可以包含多個分組。甘特圖中的任務定義格式形如:<任務名> : [crit], [active], [任務ID], [前置任務], <周期>,其中「任務名」和「周期」兩項是必要項。實例 2:在甘特圖中定義任務。 gantt 蘋果 :a, 2017-07-20, 1w 香蕉 :crit, b, 2017-07-23, 1d 櫻桃 :active, c, after b a, 1d渲染效果如下:2.1.4 定義對象的生命周期甘特圖上的對象都是以時間為基礎而存在的,對于時間,我們可能有非常多的定義需求,比如精度上的「年」、「月」、「日」、「時」等,又比如「24H」或者 「12H」這樣寫法上的不同等。Mermaid 為時間提供了豐富的語法支持。完整的定義語法如下:%a - 周維度名稱(簡寫形式)。%A - 周維度名稱(完整形式)。%b - 月維度名稱(簡寫形式)。%B - 月維度名稱(完整形式).%c - 日期時間,相當于"%a %b %e %H:%M:%S %Y"。%d - 月中日期固定寬度寫法,取值范圍 [01,31].%e - 月中日期變動寬度寫法,取值范圍 [ 1,31];等同于 %_d.%H - 小時數(shù)(24小時制)取值范圍 [00,23]。%I - 小時數(shù)(12小時制)取值范圍 [01,12]。%j - 年中日期固定寬度寫法,取值范圍 [001,366]。%m - 年中月份固定寬度寫法,取值范圍 [01,12]。%M - 分鐘數(shù)固定寬度寫法,取值范圍 [00,59]。%L - 毫秒數(shù)固定寬度寫法,取值范圍 [000, 999]。%p - 上午 \ 下午。%S - 秒數(shù)固定寬度寫法,取值范圍 [00,61]。%U - 年中周數(shù)的固定寬度寫法,以周日為每周第一天,取值范圍 [00,53]。%w - 周中日期寫法,取值范圍 [0(周日),6]。%W - 年中周數(shù)的固定寬度寫法,以周一為每周第一天,取值范圍 [00,53]。%x - 日期,等同于 "%m/%d/%Y"。%X - 時間,等同于 "%H:%M:%S"。%y - 年,僅后兩位,取值范圍 [00,99]。%Y - 年,完整四位。%Z - 時區(qū),例如:"-0700"。%% - 用于輸出百分號 "%" 。
setContentView(View contentView):設置 PopupWindow 上顯示的 ViewgetContentView():獲得 PopupWindow 上顯示的 ViewshowAsDropDown(View anchor):在控件 anchor 的正左下方展示showAsDropDown(View anchor, int xoff, int yoff):在控件 anchor 的正左下方位置,橫向偏移 xoff、縱向偏移 yoff 展示**showAtLocation(View parent, int gravity, int x, int y): **設置在父控件重心位置,橫向偏移 x,縱向偏移 y 處展示。這里的 gravity 和 Layout 里面的 gravity 類似,可以設置成Gravity.TOP或者Gravity.RIGHT等setWidth:設置彈窗的寬度,和 View 設置寬度一樣,支持直接寫具體數(shù)值,也可以用WRAP_CONTENT或者MATCH_PARENTsetHeight::設置彈窗的高度,使用方法和setWidth一樣,這兩個參數(shù)也可以通過 PopupWindow 的構造器設置setAnimationStyle(int):設置彈窗的動畫特效setOutsideTouchable(boolean touchable):設置 PopupWindow 之外的區(qū)域是否可點擊,通常點擊彈窗外的區(qū)域會讓彈窗消失setBackgroundDrawable(Drawable background):設置彈窗的背景資源,可以傳入一個 Drawable 資源注意: 這幾個方法都比較好理解,其中需要注意的是最后兩個:setOutsideTouchable、setBackgroundDrawable。從字面上理解,setOutsideTouchable就是控制是否在點擊彈窗區(qū)域以外時關閉彈窗,但實際上它的生效需要配合setBackgroundDrawable使用,簡而言之就是需要同時使用這兩個方法才能實現(xiàn)點擊外圍區(qū)域關閉彈窗,代碼如下: // 設置點擊彈窗以外區(qū)域關閉彈窗 popWindow.setBackgroundDrawable(new BitmapDrawable()); popWindow.setOutsideTouchable(true)兩句代碼缺一不可,具體原因可以參考 PopupWindow 源代碼中preparePopup()方法的實現(xiàn)。
由于進行 Mnist 圖像分類的任務比較簡單,因此我們可以定義一個較為簡單的模型,這里的模型的結構包含四層:Flattern 層:對二維數(shù)據(jù)進行展開;第一個 Dense 層:包含 128 個神經(jīng)元;第二個 Dense 層:包含 64 個神經(jīng)元;最后一個 Dense 分類層;包含 10 個神經(jīng)元,對應于我們的十個分類。class MyModel(tf.keras.Model): def __init__(self): super(MyModel, self).__init__() self.l1 = tf.keras.layers.Flatten() self.l2 = tf.keras.layers.Dense(128, activation='relu') self.l3 = tf.keras.layers.Dense(64, activation='relu') self.l4 = tf.keras.layers.Dense(10, activation='softmax') def call(self, inputs, training=True): x = self.l1(inputs) x = self.l2(x) x = self.l3(x) y = self.l4(x) return ymodel = MyModel()
開始之前,我先拿現(xiàn)實生活中畫一條線段的流程來類比我們在 canvas 中繪制線段。在現(xiàn)實中,我們如何去畫一條線段呢?我們暫且就按下面的流程來做一遍:拿到一張白紙(畫布);鉛筆移動到起點;開始描線到終點(類似于素描中的打線);選擇一種有顏色的畫筆;開始描邊(畫出輪廓線)。在 canvas 中,我們也是按這個流程來繪制一條線段的。先看整體案例:1410運行結果:我們將上面的例子類比現(xiàn)實中畫線的流程拆分講解:拿到一張白紙類比我們獲取到 canvas 的渲染上下文。const canvas = document.getElementById('imooc');const ctx = canvas.getContext('2d');我們開始鉛筆打線,先把鉛筆移動到 (100,100) 這個點,這里使用的方法是:moveTo(x,y),參數(shù)為起點坐標。ctx.moveTo(100, 100)我們用鉛筆從起點畫一個路徑到終點,也就是 (200,300) 這個點,這里使用的方法是:lineTo(x, y),參數(shù)為終點坐標。ctx.lineTo(200,300)選擇一個畫筆,這里我們設定為綠色的畫筆,這是使用了 strokeStyle 屬性,這里需要注意屬性和方法的區(qū)別,直觀的理解就是:屬性是一個變量,方法是一個函數(shù)。ctx.strokeStyle = "green"開始用畫筆描邊,這里使用的方法是:stroke(),這個方法沒有參數(shù)。ctx.stroke()到這里,我們就完成了一條線段的繪制。