國外用戶反應 iPhone、iPad 連接至 M1 Mac 同步之後,Apps 打開都會立刻閃退(附暫時解法)_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

對於大多數 M1 Mac 用戶來說,使用的手機、平版應該都是 iPhone 與 iPad,而沒用 iCloud 備份的人,你一定是把資料備份在 M1 Mac 上,但目前推薦先不要,因為最近國外就有多位用戶反應,當他們把 iPhone 或 iPad 連接上 M1 Mac 同步之後,應用程式不僅開始不斷閃退無法使用,還沒辦法繼續從 App Store 安裝新 App 與更新。

iPhone、iPad 同步至 M1 Mac 之後,Apps 打開都會立刻閃退

根據外媒 9to5Mac 的報導,在 Apple 官方論壇與 Reddit 上,陸續有用戶回報把 iPhone/iPad 連接到 M1 Mac 同步之後,已安裝的應用程式就無法再繼續使用,一打開就會閃退,可點我觀看影片:

一位用戶說道,他把 iPhone X(iOS 14.3 系統以上) 與 M1 MacBook Pro 連接並同步之後,就沒辦法打開任何第三方的應用程式了,也無法從 App Store 中安裝或更新 Apps,下載進度條瞬間變滿,然後變成雲的下載圖示:

而 Reddit 這位 Mysterious-Pie-8 網友也有詢問 Apple 支援,他表示 Apple 支援部門發現這是 M1 Mac 與 iPhone 同步之後產生的數據損壞問題,目前已經把這情況轉移給工程團隊:

有碰到相同情況的人,現在暫時只能透過還原成出廠設置解決,不過這步驟會清除 iPhone 上的所有內容和設定,因此記得先備份好資料。

事實上這問題早在去年 12 月就有人回報,目前看下來所有 M1 Mac 型號都有案例(Mac mini、MacBook Pro、MacBook Air):

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

所以說,如果你還不曾透過 M1 Mac 備份 iPhone 或 iPad 資料,記得先不要,不確定是不是每一台都會碰到這問題,至少先等到 Apple 釋出修復的軟體更新後再嘗試。

資料來源:9to5Mac

iOS 14.5 測試版為 iPhone 12 系列加入 5G 雙卡雙待支援,還帶來不少功能呢!

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

Skype for Business Online 將於 7 月底結束,微軟開始提示商務用戶盡早轉移_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

Skype for Business Online直到現在還是很多企業、行號愛用的通訊工具,在 2019 年時,微軟已經提早預告將會在 2021 年 7 月底時終止這項服務,距離結束還有 6 個月,現在微軟也開始提醒商務用戶儘早升級、轉移到 Microsoft Teams,以免到時候手忙腳亂影響工作。

Skype for Business Online將於 7 月底結束,微軟開始提示商務用戶盡早轉移

雖然疫情的關係讓全球許多公司、企業原本的規劃大亂、行程也因而有所延宕,但這回 Skype for Business Online 的結束之日並不會因此而延後。微軟在一篇官方部落格中寫到,無論目前轉移進度如何,現在就是一個重要的檢視時間,以確保公司在 Skype for Business Online 停用前能夠升級到 Microsoft Teams,以利整體業務的無縫接軌。

在官方部落格裡面,微軟還提供了各搬遷階段的資源連結來幫助用戶的轉移:
Microsoft Teams 管理:這份文件將指導用戶如何管理、準備來使用 Microsoft Teams
Teams 升級規劃 workshops:引導用戶完成一個經驗正的框架,主要用於規劃和實際進行轉移工作
微軟粉筆講座 workshops:圍繞 Teams 中一些最受歡迎的功能提供最佳實作指導

Skype for Business Online 的用戶將擁有自動升級到 Microsoft Teams 的資格,用以幫助用戶直接升級,而計劃進行自動升級的用戶將在預計的升級日期前至少 3 個月前就會在 Teams 管理中心河 Microsoft 365 訊息中心裡面收到通知,以便有更充裕的時間進行技術層面與使用的事前準備。想瞭解更多有關自動升級的資訊,可閱讀官方說明文件。

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

OPPO Find X3 Pro 實機上手玩照片曝光:火山口設計主相機,並加入顯微鏡拍攝_網頁設計公司

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

傳聞將於 3 月登場的 OPPO Find X3 系列中備受矚目的 Find X3 Pro ,在前段時間來自 Evan Blass(evleaks)首曝過官方渲染圖,讓大家對於這款新機有了初步的概念。而今日稍早, evleaks 更是直接洩漏多張 OPPO Find X3 Pro 的實機外觀照片,從這批照片可以發現其實和之前的渲染圖幾乎吻合,不過實機的樣貌看起來還是相當特殊。

▲圖片來源:Evan Blass(Voice/@evleaks)

OPPO Find X3 Pro 實機上手玩照片曝光:火山口設計主相機,並加入顯微鏡拍攝

除了日前電腦王阿達為大家開箱的 OPPO Reno5/Reno5 Pro 開箱,相信也有不少人等待 2021 年 OPPO 在全新 Find X 系列會帶來哪些驚喜。稍早,爆料大神 Evan Blass(evleaks)釋出多張 Find X3 Pro 的實機外觀照片,與他之前分享的渲染圖的外觀造型可說是幾乎相同。這款將搭載 Qualcomm Snapdragon 888 的 Find X3 Pro 在機身背面和相機模組間有著流線的曲面弧度,而相機模組周圍微微隆起的「火山口」設計,據傳 Find X3 Pro 將推出玻璃和陶瓷兩種材質版本,相信對於生產也將帶來相當大的挑戰。

▲圖片來源:Evan Blass(Voice/@evleaks)

▲圖片來源:Evan Blass(Voice/@evleaks)

▲圖片來源:Evan Blass(Voice/@evleaks)

相機規格方面, Find X3 Pro 傳聞將搭載 5000 萬像素四鏡頭主相機的配置,其中包括 5000 萬像素的主鏡頭和超廣角鏡頭(SONY IMX766 感光元件),另外最特殊的是 Find X3 Pro 將加入一枚配有環形補光燈的 300 萬像素微距鏡頭,其 25 倍變焦可實現「顯微鏡」模式拍攝。至於第四顆鏡頭,則是去年就在 Find X2 搭載的 1300 萬像素 2x 長焦鏡頭( F2.4光圈、5P 鏡頭、5 倍混合變焦、最高 20倍數位變焦)。

▲圖片來源:Evan Blass(Voice/@evleaks)

在一月底,數碼閒聊站也在微博刊出一張手機的相機操作介面截圖,當時就曾出現過「顯微鏡」模式:

▲圖片來源:數碼閒聊站(微博)

最後關於 Find X3 系列發表的時間,傳聞 OPPO 有望於三月正式發表全新 Find X3 系列,包括 Find X3 Pro 、 Find X3 Neo 以及 Find X3 Lite ,其中 Find X3 Lite 日前的開箱照也證實就是台灣已推出的 Reno5 的更名版本。

消息來源:Evan Blass(Voice/@evleaks)|數碼閒聊站(微博)

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

延伸閱讀:
OPPO Find X3 Pro 更多關鍵規格曝光:將搭載高通 S888 處理器、12GB RAM、256GB ROM

ROG Phone 5 更多外觀、規格細節曝光:另有 Anime Matrix 顯示版本?

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

小米11 國際版將於2/8 晚間發表,有望 3 月在台亮相_潭子電動車

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

在 2020 年 12 月底,小米於中國發表首款搭載高通 Snapdragon 888 處理器的旗艦手機「小米11」,截至目前小米11 也只在中國當地銷售。針對中國以外的市場方面,小米11 國際版除了日前通過 NCC 認證,近期也正式宣佈將於 2 月 8 日晚上 8 點舉行小米11 國際版發表會,未來小米11 也將引進台灣市場販售。

小米11 國際版將於2/8 晚間發表,有望 3 月在台亮相

小米11 國際版多數規格預計和去年底發表的中國版的關鍵關格大致相似,像是搭載 Qualcomm Snapdragon 888 處理器、 6.81 吋 2K(3200×1440 WQHD)E4 材質 AMOLED 四曲面柔性螢幕。
螢幕支持最高 120Hz 螢幕更新率、480Hz 觸控採樣率, 1500nit 峰值亮度、480Hz  觸控採樣率、5,000,000:1 對比度,螢幕也擁有 100% P3 色域和 HDR10+ 認證。
另外,小米11 螢幕玻璃採用康寧 Gorilla Glass Victus 玻璃保護,抗摔性相較前代提升 1.5倍、耐刮性能提升 2 倍。

相機方面,小米 11 採用 1 億 800 萬像素三鏡頭主相機設計,分別為 1 億 800 萬像素像素(1 / 1.33″超大感光元件)標準鏡頭、1300 萬像素 123° 超廣角鏡頭、 500 萬像素 50mm 微距長焦鏡頭,前置鏡頭則配備 2000 萬像素自拍相機。
此外,小米11 內建等效 4600mAh 大電池,支持 55W 有線閃充、50W 無線閃充以及10W 反向無線充電, 55W 有線閃充可在 45 分鐘充滿 100% 電量、50W 無線閃充可在 53 分鐘充滿 100% 電量。

▲圖片來源:小米(中國)

與小米11 中國版最大的差異在於小米11 國際版將加入 GMS 支援,不過目前還無法得知將推出哪幾款配色與容量選擇。雖然,小米11 國際版在下週 2 月 8 日晚上 8 點就會發表,不過台灣市場預計最快要等到 3 月才會引進銷售。

小米11 國際版線上發表會(2021/2/8 20:00開始)

 

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

延伸閱讀:
多款旗艦手機電力續航能力實測,三星 Galaxy S21 Ultra 仍敗給 iPhone 12 Pro Max

小米隔空充電技術正式發表:手持也能隔空充電,「真」無線充電時代來臨!

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

POCO X3 Pro 通過 FCC 認證,近期有望正式亮相_包裝設計

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

上個月 POCO 才剛以獨立品牌重返台灣市場推出性價比超高的 POCO M3 ,近期也傳聞 POCO 將再有新機發表。日前有款 POCO 的新機通過 FCC 等多國機構認證通過,而它就是 POCO X 系列中的 POCO X3 Pro ,預期將帶來比起 POCO X3/X3 NFC 更高階的旗艦規格,未來也不排除再正式發表後有機會引進台灣販售。

▲示意圖,圖為 POCO X3 NFC(圖片來源:POCO)

POCO X3 Pro 通過 FCC 認證,近期有望正式亮相

近期有款小米 POCO 品牌的新機陸續通過包括 FCC 、IMDA 、 EEC 以及 TUV 等認證,其型號為 M2102J20SG ,經比對是目前尚未發表的 POCO X3 Pro 。
雖然目前未有關於 POCO X3 Pro 的詳細規格資訊,不過回顧之前海外發表的 POCO X3 標準版的規格, POCO X3 標準版搭載高通 Snapdragon 732G 處理器、8GB RAM、 128GB ROM , POCO X3 配備 6.67 吋 FHD+ 解析度挖孔全螢幕,螢幕支持最高 120Hz 螢幕更新率和 240Hz 觸控採樣率,內建 6000mAh 大容量電池並支持 33W 快速充電,相機則搭載 6400 萬像素四鏡頭主相機,搭配側邊結合電源鍵的指紋辨識鍵,而 POCO X3 Pro 預期將帶來比上述更高一階的規格配置。

▲圖片來源:FCC

參考目前在印度 POCO 的產品線規劃, POCO X 系列定調為旗艦體驗的中高階機型,目前已在海外發表的POXO X3/X3 NFC 搭載高通 Snapdragon 732G 處理器,而近期通過認證的 POCO X3 Pro 預計將搭載旗艦級處理器。
▲圖片來源:POCO

雖然目前未確認 POCO X3 Pro 的詳細規格,不過有消息指出 POCO X3 Pro 其實就是印度市場 POCO F2 的國際版本,不過目前就連同 POCO F2 都尚未正式推出,因此無論是 POCO X3 Pro 或 POCO F2 可能都還得等上一段時間才會有進一步消息。

圖片/消息來源:FCC

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

延伸閱讀:
小米11 國際版將於2/8 晚間發表,有望 3 月在台亮相

OPPO Find X3 Pro 實機上手玩照片曝光:火山口設計主相機,並加入顯微鏡拍攝

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

國外大神爆料第二代 AirPods Pro 最新渲染圖,採無柄設計、更好的音質_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

隨著供應鏈傳出 AirPods Pro 2 第二代很可能就快要推出的消息,最近國外爆料大神就透露了一張疑似介紹 AirPods Pro 2 的渲染圖,雖然沒有規格資訊,但卻顯示一個非常重要的全新「無柄」設計,變成只有上面那個耳機頭而已。

國外大神爆料第二代 AirPods Pro 最新渲染圖

稍早國外爆料大神 theapplehub 分享下面這張圖片,可以看到名稱寫著 AirPods Pro (2nd gen),下面標註三個特色重點「全心設計 – 無柄、更加圓潤」、「H1 晶片」與「提升音質」,左側則有 AirPods Pro 2 的渲染圖:

The next generation AirPods Pro are expected to feature a new design and could launch as early as March/April pic.twitter.com/GaDJtpduai

— Apple Hub (@theapplehub) February 2, 2021

theapplehub 也表示這款 AirPods Pro 2 預計會在今年三月或四月推出,先前 DigiTimes 報告僅指出將於上半年發表,並沒有註明哪一個月。另外晶片部分除了 H1,也有傳可能會搭載 W2 晶片。

現行的 AirPods 2、AirPods Pro 與 AirPods Max 都是搭載 H1 晶片,W2 則是 AirPods 第一代 W1 晶片的下一代版本。

Mr-White 分享的新款 AirPods Pro 零件圖片:

New AirPods Pro Mabey Two Sizes Still W2 Chips 🤨 pic.twitter.com/R5MpzUrUlg

— Mr·White (@laobaiTD) December 29, 2020

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

如果光看渲染圖,改成無柄設計後外型總覺得不是很討喜,而且戴起來感覺也沒有很舒適,大多數國外網友的評價也普普,很多人都覺得為什麼要取消耳機柄?

不過這也不是第一次發生,想當初 AirPods 剛推出時,一堆人不斷嘲笑那水管造型,結果開賣之後依舊被賣翻,所以還是要等到最終實品才知道,搞不好 Apple 把這造型設計的非常美型。

無論如何,離預告的時間只剩下兩個月左右時間,沒意外應該陸續有更多消息出現,最近有打算入手 AirPods 或 AirPods Pro 也不妨等一下。

至於價格,AirPods Pro 2 有可能會保持一樣的 249 美元,並不會調高或降價。

資料來源:theapplehub

AirPods Pro 2 零件現蹤,可能將像 Apple Watch 一樣有雙尺寸?

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

Apple 官方文件顯示 M1 版的 Mac mini 功耗,僅是 Intel 版的 1/3_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

自從 M1 Mac 系列推出以來,網路上已經出現不少 M1 MacBook 比 Intel 還省電的測試影片與文章,Mac mini 雖然是插電,沒有什麼續航力的問題,但對於注重功耗的人來說,一定還是會想知道跟 Intel 相比,M1 版 Mac mini 究竟有沒有更省電多少?最近外媒發現到 Apple 公布一份文件中,就明確註明 Mac mini 各代的功耗數據,從 M1 一直到幾十年前的 PowerPC G4 版本都有,而跟上一代 Intel 版相比,M1 功耗確定僅 Intel 的 1/3 而已,可說省非常多。

 M1 版 Mac mini 功耗僅是 Intel 版的 1/3

在 Apple 這份文件中就寫到,16GB RAM、2TB SSD 的 Mac mini(M1, 2020)Idle 功耗僅 6.8W,最大功耗則是 39W:

而上一代 Intel 版 Mac mini(2018),Idle 功耗不僅變成 19.9W,最大功耗還來到 122W,意味著是 M1 版的 3 倍:

有些人可能會想,會不會是 Intel 這顆 i7 效能比較強才會這樣?根據網路上 M1 vs Intel 的實測顯示,光是去年的 Intel 處理器版 Mac,很多效能就比 M1 還差,這台 2018 年的 Mac mini 還是 i7-8700B CPU(Coffee Lake),三年前的 Intel 處理器,因此無論是 CPU 還是顯示效能,都沒辦法跟 M1 匹敵。

這也意味著,M1 Mac mini 不僅效能大幅提升,在功耗部分也省電非常多,打算購買 Mac mini 的人應該知道該選哪一版了吧!

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

另外更有趣的是,M1 Mac mini 效能滿載時的最大功耗,僅比 PowerPC G4 版的 2005 Mac Mini 閒置時多一點點。當然,年份跟製程工藝是主因:

資料來源:TECHSPOT

國外用戶反應 iPhone、iPad 連接至 M1 Mac 同步之後,Apps 打開都會立刻閃退(附暫時解法)

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

Java 異常(二) 自定義異常_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

 Java 異常(二) 自定義異常

在開發中,為了適應業務的開發需求, 在 Java 中可以根據業務的異常情況自定義異常。

一、自定義異常

所有的自定義異常都必須是 Throwable 的子類,在自定義繼承時可以繼承於 Exception 或者它的子類。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

二、自定義異常的分類

1、檢查性異常類:自定義異常類繼承於Exception。

2、運行時異常類:自定義異常類繼承於RuntimeException

三、Objects的非空判斷

Objects由一些靜態的實用方法組成,這些方法是null-save(空指針安全的)或 null-tolerant(容忍空指針的),那麼在它的源碼中,對對象為null的值進行了拋出異常操作。Objects通過調用requireNonNull(T obj)方法查看調用對象是否為null。

public static <T> T requireNonNull(T obj) {
  if (obj == null)     throw new NullPointerException();   return obj; }

從上面源碼可以看出,如果傳遞對象為 null,requireNonNull 方法會拋出 NullPointerException 異常,否則返回該對象。

四、實例

1、自定義檢查性異常類(MyException)

public class MyException extends Exception {
    
    public MyException() { } // 無參構造
    
    public MyException(String msg) { 
        super(msg); // msg : 異常提示信息
    }
    
    public MyException(Throwable throwable) { 
        super(throwable);// throwable 類型
    }
}

2、自定義運行時異常類(MyRuntimeException)

public class MyRuntimeException extends RuntimeException {
    public MyRuntimeException() { } // 無參構造
    
    public MyRuntimeException(String msg) { 
        super(msg); // msg : 異常提示信息
    }
    
    public MyRuntimeException(Throwable throwable) { 
        super(throwable);// throwable 類型
    }
}

3、自定義異常的使用實例

public class ExceptionDemo {
    public static void main(String[] args) throws Exception {
        int i = demo(3);
        System.out.println("i = " + i);
    }
    
    public static int demo(int index) throws  MyException{
        int[] arr = {1,2,3};
        if(index >= arr.length || index < 0)
            throw new MyRuntimeException("您傳遞的索引錯誤,數組索引在0-2之間");
        return arr[index];
    }
}

 4、Objects的非空判斷實例

public static void main(String[] args) throws Exception {
    Integer i = 10;
    Integer i2 = Objects.requireNonNull(i);
    System.out.println(i2);
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

3 種生成高強度密碼的方法_網頁設計公司

綠能、環保無空污,成為 電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

現在信息泄露越來越嚴重,而強大的密碼是防止個人敏感信息泄露的第一步。良許曾經分享過一篇文章,如何判斷你的密碼是否足夠安全,點擊以下鏈接查看:

信息泄漏時代,如何讓自己的密碼更安全?

在生活中,我們需要用到大量的密碼,這些密碼最好不要統一,否則萬一泄漏的話,所有賬號都暴露在風險之下。而在工作中,我們同樣也需要用到大量密碼,比如批量添加用戶,批量設置服務器密碼等。

如果靠自己去想的話,想到的密碼可以不夠強大,而且比較費力。下面良許就介紹 3 種方法來批量生成高強度的密碼。

所謂的高強度密碼,就是包含了大小寫、数字、符號的密碼。

1. pwgen

pwgen 的特點是可以生成一些能夠被人類記住,並且也足夠安全的密碼。但是,如果你想生成不容易記住的隨機密碼,只需加上 -s 選項即可。

1.1 pwgen 的安裝

對於 Debian/Ubuntu 系統,直接使用 apt-get 命令即可安裝。

$ sudo apt install pwgen

對於 RHEL/CentOS 系統,可以使用 yum 命令安裝。

$ sudo yum install pwgen

其它系統可以使用對應的安裝命令,在此不贅述。

1.2 pwgen 的用法

pwgen 最簡單的用法是直接敲入這個命令,不帶任何參數就可以生成 160 個密碼。默認情況下,它生成的密碼是易於人類記住的密碼,8 個字符,包含大小寫及数字。

自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

一共 160 個,分成 20 行 8列。限於篇幅,以下結果做了縮減。

$ pwgen
ameiK2oo aibi3Cha EPium0Ie aisoh1Ee Nidee9ae uNga0Bee uPh9ieM1 ahn1ooNg
oc5ooTea tai7eKid tae2yieS hiecaiR8 wohY2Ohk Uab2maed heC4aXoh Ob6Nieso
…………
ahV4yore ue2laePh fu1eThui qui7aePh Fahth1nu ohk9puLo aiBeez0b Neengai5

如果你想生成 5 個 14 個字符長度的密碼,那麼可以使用以下命令:

$ pwgen -s 14 5
7YxUwDyfxGVTYD em2NT6FceXjPfT u8jlrljbrclcTi IruIX3Xu0TFXRr X8M9cB6wKNot1e

如果你想生成超級難記,超級安全的密碼,可以加上 -cnys 選項,使用以下格式:

$ pwgen -cnys 14 20
mQ3E=vfGfZ,5[B #zmj{i5|ZS){jg Ht_8i7OqJ%N`~2 443fa5iJ\W-L?] ?Qs$o=vz2vgQBR
^'Ry0Az|J9p2+0 t2oA/n7U_'|QRx EsX*%_(4./QCRJ ACr-,8yF9&eM[* !Xz1C'bw?tv50o
8hfv-fK(VxwQGS q!qj?sD7Xmkb7^ N#Zp\_Y2kr%!)~ 4*pwYs{bq]Hh&Y |4u=-Q1!jS~8=;
]{$N#FPX1L2B{h I|01fcK.z?QTz" l~]JD_,W%5bp.E +i2=D3;BQ}p+$I n.a3,.D3VQ3~&i

2. openssl

openssl 命令是調用 OpenSSL 的一些庫中的各種密碼學函數來生成密碼,強度也相對比較高。

我們可以使用以下命令格式來生成一個 14 位的隨機密碼:

$ openssl rand -base64 14
WjzyDqdkWf3e53tJw/c=

但是,這樣一條命令只能生成一個密碼,如果想要批量生成密碼,就要寫一個簡單的 Shell 語句。

$ for pw in {1..4}; do openssl rand -base64 14; done
6i0hgHDBi3ohZ9Mil8I=
gtn+y1bVFJFanpJqWaA=
rYu+wy+0nwLf5lk7TBA=
xrdNGykIzxaKDiLF2Bw=

3. gpg

1991年,程序員 Phil Zimmermann 為了避開政府監視,開發了加密軟件 PGP。這個軟件非常好用,迅速流傳開來,成了許多程序員的必備工具。但是,它是商業軟件,不能自由使用。所以,自由軟件基金會決定,開發一個PGP的替代品,取名為 GnuPG

我們可以使用以下格式來生成一個隨機的 14 位高強度密碼。

$ gpg --gen-random --armor 1 14
or
$ gpg2 --gen-random --armor 1 14
jq1mtY4gBa6gIuJrggM=

同樣地,如果這個命令只能生成一個密碼,如果要生成多個,那就需要寫一個簡單的 Shell 語句。

$ for pw in {1..4}; do gpg --gen-random --armor 1 14; done
or
$ for pw in {1..4}; do gpg2 --gen-random --armor 1 14; done
F5ZzLSUMet2kefG6Ssc=
8hh7BFNs8Qu0cnrvHrY=
B+PEt28CosR5xO05/sQ=
m21bfx6UG1cBDzVGKcE=

4. 小結

一個強大的密碼是保證我們賬號安全的第一步,重要性不容小覷。本文介紹了 3 種方法隨機生成高強度密碼,但還有很多工具還可以生成這樣的密碼,比如 makepasswdmkpasswd 等。大家平常都是怎麼生成密碼的?歡迎留言討論!

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

vue的第一個commit分析_如何寫文案

別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

為什麼寫這篇vue的分析文章?

對於天資愚鈍的前端(我)來說,閱讀源碼是件不容易的事情,畢竟有時候看源碼分析的文章都看不懂。每次看到大佬們用了1~2年的vue就能掌握原理,甚至精通源碼,再看看自己用了好幾年都還在基本的使用階段,心中總是羞愧不已。如果一直滿足於基本的業務開發,怕是得在初級水平一直待下去了吧。所以希望在學習源碼的同時記錄知識點,可以讓自己的理解和記憶更加深刻,也方便將來查閱。

目錄結構

本文以vue的第一次 commit a879ec06 作為分析版本

├── build
│   └── build.js               // `rollup` 打包配置
├── dist                        
│   └── vue.js    
├── package.json
├── src                        // vue源碼目錄
│   ├── compiler               // 將vue-template轉化為render函數
│   │   ├── codegen.js         // 遞歸ast提取指令,分類attr,style,class,並生成render函數
│   │   ├── html-parser.js     // 通過正則匹配將html字符串轉化為ast
│   │   ├── index.js           // compile主入口
│   │   └── text-parser.js     // 編譯{{}}
│   ├── config.js              // 對於vue的全局配置文件
│   ├── index.js               // 主入口
│   ├── index.umd.js           // 未知(應該是umd格式的主入口)
│   ├── instance               // vue實例函數
│   │   └── index.js           // 包含了vue實例的初始化,compile,data代理,methods代理,watch數據,執行渲染
│   ├── observer               // 數據訂閱發布的實現
│   │   ├── array.js           // 實現array變異方法,$set $remove 實現
│   │   ├── batcher.js         // watch執行隊列的收集,執行
│   │   ├── dep.js             // 訂閱中心實現
│   │   ├── index.js           // 數據劫持的實現,收集訂閱者
│   │   └── watcher.js         // watch實現,訂閱者
│   ├── util                   // 工具函數
│   │   ├── component.js
│   │   ├── debug.js
│   │   ├── dom.js
│   │   ├── env.js             // nexttick實現
│   │   ├── index.js
│   │   ├── lang.js
│   │   └── options.js
│   └── vdom
│       ├── dom.js             // dom操作的封裝
│       ├── h.js               // 節點數據分析(元素節點,文本節點)
│       ├── index.js           // vdom主入口
│       ├── modules            // 不同屬性處理函數
│       │   ├── attrs.js       // 普通attr屬性處理
│       │   ├── class.js       // class處理
│       │   ├── events.js      // event處理
│       │   ├── props.js       // props處理
│       │   └── style.js       // style處理
│       ├── patch.js           // node樹的渲染,包括節點的加減更新處理,及對應attr的處理
│       └── vnode.js           // 返回最終的節點數據
└── webpack.config.js          // webpack配置

從template到html的過程分析

我們的代碼是從new Vue()開始的,Vue的構造函數如下:

constructor (options) {
  // options就是我們對於vue的配置
  this.$options = options
  this._data = options.data
  // 獲取元素html,即template
  const el = this._el = document.querySelector(options.el)
  // 編譯模板 -> render函數
  const render = compile(getOuterHTML(el))
  this._el.innerHTML = ''
  // 實例代理data數據
  Object.keys(options.data).forEach(key => this._proxy(key))
  // 將method的this指向實例
  if (options.methods) {
    Object.keys(options.methods).forEach(key => {
      this[key] = options.methods[key].bind(this)
    })
  }
  // 數據觀察
  this._ob = observe(options.data)
  this._watchers = []
  // watch數據及更新
  this._watcher = new Watcher(this, render, this._update)
  // 渲染函數
  this._update(this._watcher.value)
}

當我們初始化項目的時候,即會執行構造函數,該函數向我們展示了vue初始化的主線:編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染

1. 編譯template字符串

const render = compile(getOuterHTML(el))

其中compile的實現如下:

export function compile (html) {
  html = html.trim()
  // 對編譯結果緩存
  const hit = cache[html]
  // parse函數在parse-html中定義,其作用是把我們獲取的html字符串通過正則匹配轉化為ast,輸出如下 {tag: 'div', attrs: {}, children: []}
  return hit || (cache[html] = generate(parse(html)))
}

接下來看看generate函數,ast通過genElement的轉化生成了構建節點html的函數,在genElement將對if for 等進行判斷並轉化( 指令的具體處理將在後面做分析,先關注主流程代碼),最後都會執行genData函數

// 生成節點主函數
export function generate (ast) {
  const code = genElement(ast)
  // 執行code代碼,並將this作為code的global對象。所以我們在template中的變量將指向為實例的屬性 {{name}} -> this.name 
  return new Function (`with (this) { return ${code}}`)
}

// 解析單個節點 -> genData
function genElement (el, key) {
  let exp
  // 指令的實現,實際就是在模板編譯時實現的
  if (exp = getAttr(el, 'v-for')) {
    return genFor(el, exp)
  } else if (exp = getAttr(el, 'v-if')) {
    return genIf(el, exp)
  } else if (el.tag === 'template') {
    return genChildren(el)
  } else {
    // 分別為 tag 自身屬性 子節點數據
    return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`
  }
}

我們可以看看在genData中都做了什麼。上面的parse函數將html字符串轉化為ast,而在genData中則將節點的attrs數據進一步處理,例如class -> renderClass style class props attr 分類。在這裏可以看到 bind 指令的實現,即通過正則匹配 : 和 bind,如果匹配則把相應的 value值轉化為 (value) 的形式,而不匹配的則通過JSON.stringify()轉化為字符串('value')。最後輸出attrs(key-value),在這裏得到的對象是字符串形式的,例如(value)等也僅僅是將變量名,而在generate中通過new Function進一步通過(this.value)得到變量值。

function genData (el, key) {
  // 沒有屬性返回空對象
  if (!el.attrs.length) {
    return '{}'
  }
  // key
  let data = key ? `{key:${ key },` : `{`
  // class處理
  if (el.attrsMap[':class'] || el.attrsMap['class']) {
    data += `class: _renderClass(${ el.attrsMap[':class'] }, "${ el.attrsMap['class'] || '' }"),`
  }
  // attrs
  let attrs = `attrs:{`
  let props = `props:{`
  let hasAttrs = false
  let hasProps = false
  for (let i = 0, l = el.attrs.length; i < l; i++) {
    let attr = el.attrs[i]
    let name = attr.name
    // bind屬性
    if (bindRE.test(name)) {
      name = name.replace(bindRE, '')
      if (name === 'class') {
        continue
      // style處理
      } else if (name === 'style') {
        data += `style: ${ attr.value },`
      // props屬性處理
      } else if (mustUsePropsRE.test(name)) {
        hasProps = true
        props += `"${ name }": (${ attr.value }),` 
      // 其他屬性
      } else {
        hasAttrs = true
        attrs += `"${ name }": (${ attr.value }),`
      }
    // on指令,未實現
    } else if (onRE.test(name)) {
      name = name.replace(onRE, '')
    // 普通屬性
    } else if (name !== 'class') {
      hasAttrs = true
      attrs += `"${ name }": (${ JSON.stringify(attr.value) }),`
    }
  }
  if (hasAttrs) {
    data += attrs.slice(0, -1) + '},'
  }
  if (hasProps) {
    data += props.slice(0, -1) + '},'
  }
  return data.replace(/,$/, '') + '}'
}

而對於genChildren,我們可以猜到就是對ast中的children進行遍歷調用genElement,實際上在這裏還包括了對文本節點的處理。

// 遍歷子節點 -> genNode
function genChildren (el) {
  if (!el.children.length) {
    return 'undefined'
  }
  // 對children扁平化處理
  return '__flatten__([' + el.children.map(genNode).join(',') + '])'
}

function genNode (node) {
  if (node.tag) {
    return genElement(node)
  } else {
    return genText(node)
  }
}

// 解析{{}}
function genText (text) {
  if (text === ' ') {
    return '" "'
  } else {
    const exp = parseText(text)
    if (exp) {
      return 'String(' + escapeNewlines(exp) + ')'
    } else {
      return escapeNewlines(JSON.stringify(text))
    }
  }
}

genText處理了text及換行,在parseText函數中利用正則解析{{}},輸出字符串(value)形式的字符串。

現在我們再看看__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })__h__函數

// h 函數利用上面得到的節點數據得到 vNode對象 => 虛擬dom
export default function h (tag, b, c) {
  var data = {}, children, text, i
  if (arguments.length === 3) {
    data = b
    if (isArray(c)) { children = c }
    else if (isPrimitive(c)) { text = c }
  } else if (arguments.length === 2) {
    if (isArray(b)) { children = b }
    else if (isPrimitive(b)) { text = b }
    else { data = b }
  }
  if (isArray(children)) {
    // 子節點遞歸處理
    for (i = 0; i < children.length; ++i) {
      if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i])
    }
  }
  // svg處理
  if (tag === 'svg') {
    addNS(data, children)
  }
  // 子節點為文本節點
  return VNode(tag, data, children, text, undefined)
}

到此為止,我們分析了const render = compile(getOuterHTML(el)),從elhtml字符串到render函數都是怎麼處理的。

教你寫出一流的 銷售文案?

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

2. 代理data數據/methods的this綁定

// 實例代理data數據
Object.keys(options.data).forEach(key => this._proxy(key))
// 將method的this指向實例
if (options.methods) {
  Object.keys(options.methods).forEach(key => {
    this[key] = options.methods[key].bind(this)
  })
}

實例代理data數據的實現比較簡單,就是利用了對象的setter和getter,讀取this數據時返回data數據,在設置this數據時同步設置data數據

_proxy (key) {
  if (!isReserved(key)) {
    // need to store ref to self here
    // because these getter/setters might
    // be called by child scopes via
    // prototype inheritance.
    var self = this
    Object.defineProperty(self, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
        return self._data[key]
      },
      set: function proxySetter (val) {
        self._data[key] = val
      }
    })
  }
}

3. Obaerve的實現

Observe的實現原理在很多地方都有分析,主要是利用了Object.defineProperty()來建立對數據更改的訂閱,在很多地方也稱之為數據劫持。下面我們來學習從零開始建立這樣一個數據的訂閱發布體系。

從簡單處開始,我們希望有個函數可以幫我們監聽數據的改變,每當數據改變時執行特定回調函數

function observe(data, callback) {
  if (!data || typeof data !== 'object') {
    return
  }

  // 遍歷key
  Object.keys(data).forEach((key) => {
    let value = data[key];

    // 遞歸遍歷監聽深度變化
    observe(value, callback);

    // 監聽單個可以的變化
    Object.defineProperty(data, key, {
      configurable: true,
      enumerable: true,
      get() {
        return value;
      },
      set(val) {
        if (val === value) {
          return
        }

        value = val;

        // 監聽新的數據
        observe(value, callback);
        
        // 數據改變的回調
        callback();
      }
    });
  });
}

// 使用observe函數監聽data
const data = {};
observe(data, () => {
  console.log('data修改');
})

上面我們實現了一個簡單的observe函數,只要我們將編譯函數作為callback傳入,那麼每次數據更改時都會觸發回調函數。但是我們現在不能為單獨的key設置監聽及回調函數,只能監聽整個對象的變化執行回調。下面我們對函數進行改進,達到為某個key設置監聽及回調。同時建立調度中心,讓整個訂閱發布模式更加清晰。

// 首先是訂閱中心
class Dep {
  constructor() {
    this.subs = []; // 訂閱者數組
  }

  addSub(sub) {
    // 添加訂閱者
    this.subs.push(sub);
  }

  notify() {
    // 發布通知
    this.subs.forEach((sub) => {
      sub.update();
    });
  }
}

// 當前訂閱者,在getter中標記
Dep.target = null;

// 訂閱者
class Watch {
  constructor(express, cb) {
    this.cb = cb;
    if (typeof express === 'function') {
      this.expressFn = express;
    } else {
      this.expressFn = () => {
        return new Function(express)();
      }
    }
    
    this.get();
  }

  get() {
    // 利用Dep.target存當前訂閱者
    Dep.target = this;
    // 執行表達式 -> 觸發getter -> 在getter中添加訂閱者
    this.expressFn();
    // 及時置空
    Dep.taget = null;
  }

  update() {
    // 更新
    this.cb();
  }

  addDep(dep) {
    // 添加訂閱
    dep.addSub(this);
  }
}

// 觀察者 建立觀察
class Observe {
  constructor(data) {
    if (!data || typeof data !== 'object') {
      return
    }
  
    // 遍歷key
    Object.keys(data).forEach((key) => {
      // key => dep 對應
      const dep = new Dep();
      let value = data[key];
  
      // 遞歸遍歷監聽深度變化
      const observe = new Observe(value);
  
      // 監聽單個可以的變化
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        get() {
          if (Dep.target) {
            const watch = Dep.target;
            watch.addDep(dep);
          }
          return value;
        },
        set(val) {
          if (val === value) {
            return
          }
  
          value = val;
  
          // 監聽新的數據
          new Observe(value);
          
          // 數據改變的回調
          dep.notify();
        }
      });
    });
  }
}

// 監聽數據中某個key的更改
const data = {
  name: 'xiaoming',
  age: 26
};

const observe = new Observe(data);

const watch = new Watch('data.age', () => {
  console.log('age update');
});

data.age = 22

現在我們實現了訂閱中心訂閱者觀察者。觀察者監測數據的更新,訂閱者通過訂閱中心訂閱數據的更新,當數據更新時,觀察者會告訴訂閱中心,訂閱中心再逐個通知所有的訂閱者執行更新函數。到現在為止,我們可以大概猜出vue的實現原理:

  1. 建立觀察者觀察data數據的更改 (new Observe)

  2. 在編譯的時候,當某個代碼片段或節點依賴data數據,為該節點建議訂閱者,訂閱data中某些數據的更新(new Watch)

  3. 當dada數據更新時,通過訂閱中心通知數據更新,執行節點更新函數,新建或更新節點(dep.notify())

上面是我們對vue實現原理訂閱發布模式的基本實現,及編譯到更新過程的猜想,現在我們接着分析vue源碼的實現:

在實例的初始化中

// ...
// 為數據建立數據觀察
this._ob = observe(options.data)
this._watchers = []
// 添加訂閱者 執行render 會觸發 getter 訂閱者訂閱更新,數據改變觸發 setter 訂閱中心通知訂閱者執行 update
this._watcher = new Watcher(this, render, this._update)
// ...

vue中數據觀察的實現

// observe函數
export function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    return
  }
  if (
    hasOwn(value, '__ob__') &&
    value.__ob__ instanceof Observer
  ) {
    ob = value.__ob__
  } else if (
    shouldConvert &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 為數據建立觀察者
    ob = new Observer(value)
  }
  // 存儲關聯的vm
  if (ob && vm) {
    ob.addVm(vm)
  }
  return ob
}

// => Observe 函數
export function Observer (value) {
  this.value = value
  // 在數組變異方法中有用
  this.dep = new Dep()
  // observer實例存在__ob__中
  def(value, '__ob__', this)
  if (isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment
    // 數組遍歷,添加變異的數組方法
    augment(value, arrayMethods, arrayKeys)
    // 對數組的每個選項調用observe函數
    this.observeArray(value)
  } else {
    // walk -> convert -> defineReactive -> setter/getter
    this.walk(value)
  }
}

// => walk
Observer.prototype.walk = function (obj) {
  var keys = Object.keys(obj)
  for (var i = 0, l = keys.length; i < l; i++) {
    this.convert(keys[i], obj[keys[i]])
  }
}

// => convert
Observer.prototype.convert = function (key, val) {
  defineReactive(this.value, key, val)
}

// 重點看看defineReactive
export function defineReactive (obj, key, val) {
  // key對應的的訂閱中心
  var dep = new Dep()

  var property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 兼容原有setter/getter
  // cater for pre-defined getter/setters
  var getter = property && property.get
  var setter = property && property.set

  // 實現遞歸監聽屬性 val = obj[key]
  // 深度優先遍歷 先為子屬性設置 reactive
  var childOb = observe(val)
  // 設置 getter/setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      // Dep.target 為當前 watch 實例
      if (Dep.target) {
        // dep 為 obj[key] 對應的調度中心 dep.depend 將當前 wtcher 實例添加到調度中心
        dep.depend()
        if (childOb) {
          // childOb.dep 為 obj[key] 值 val 對應的 observer 實例的 dep
          // 實現array的變異方法和$set方法訂閱
          childOb.dep.depend()
        }

        // TODO: 此處作用未知?
        if (isArray(value)) {
          for (var e, i = 0, l = value.length; i < l; i++) {
            e = value[i]
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val
      // 通過 getter 獲取 val 判斷是否改變
      if (newVal === value) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 為新值設置 reactive
      childOb = observe(newVal)
      // 通知key對應的訂閱中心更新
      dep.notify()
    }
  })
}

訂閱中心的實現

let uid = 0

export default function Dep () {
  this.id = uid++
  // 訂閱調度中心的watch數組
  this.subs = []
}

// 當前watch實例
Dep.target = null

// 添加訂閱者
Dep.prototype.addSub = function (sub) {
  this.subs.push(sub)
}

// 移除訂閱者
Dep.prototype.removeSub = function (sub) {
  this.subs.$remove(sub)
}

// 訂閱
Dep.prototype.depend = function () {
  // Dep.target.addDep(this) => this.addSub(Dep.target) => this.subs.push(Dep.target)
  Dep.target.addDep(this)
}

// 通知更新
Dep.prototype.notify = function () {
  // stablize the subscriber list first
  var subs = this.subs.slice()
  for (var i = 0, l = subs.length; i < l; i++) {
    // subs[i].update() => watch.update()
    subs[i].update()
  }
}

訂閱者的實現

export default function Watcher (vm, expOrFn, cb, options) {
  // mix in options
  if (options) {
    extend(this, options)
  }
  var isFn = typeof expOrFn === 'function'
  this.vm = vm
  // vm 的 _watchers 包含了所有 watch
  vm._watchers.push(this)
  this.expression = expOrFn
  this.cb = cb
  this.id = ++uid // uid for batching
  this.active = true
  this.dirty = this.lazy // for lazy watchers
  // deps 一個 watch 實例可以對應多個 dep
  this.deps = []
  this.newDeps = []
  this.depIds = Object.create(null)
  this.newDepIds = null
  this.prevError = null // for async error stacks
  // parse expression for getter/setter
  if (isFn) {
    this.getter = expOrFn
    this.setter = undefined
  } else {
    warn('vue-lite only supports watching functions.')
  }
  this.value = this.lazy
    ? undefined
    : this.get()
  this.queued = this.shallow = false
}

Watcher.prototype.get = function () {
  this.beforeGet()
  var scope = this.scope || this.vm
  var value
  try {
    // 執行 expOrFn,此時會觸發 getter => dep.depend() 將watch實例添加到對應 obj[key] 的 dep
    value = this.getter.call(scope, scope)
  }
  if (this.deep) {
    // 深度watch
    // 觸發每個key的getter watch實例將對應多個dep
    traverse(value)
  }
  // ...
  this.afterGet()
  return value
}

// 觸發getter,實現訂閱
Watcher.prototype.beforeGet = function () {
  Dep.target = this
  this.newDepIds = Object.create(null)
  this.newDeps.length = 0
}

// 添加訂閱
Watcher.prototype.addDep = function (dep) {
  var id = dep.id
  if (!this.newDepIds[id]) {
    // 將新出現的dep添加到newDeps中
    this.newDepIds[id] = true
    this.newDeps.push(dep)
    // 如果已在調度中心,不再重複添加
    if (!this.depIds[id]) {
      // 將watch添加到調度中心的數組中
      dep.addSub(this)
    }
  }
}

Watcher.prototype.afterGet = function () {
  // 切除key的getter聯繫
  Dep.target = null
  var i = this.deps.length
  while (i--) {
    var dep = this.deps[i]
    if (!this.newDepIds[dep.id]) {
      // 移除不在expOrFn表達式中關聯的dep中watch的訂閱
      dep.removeSub(this)
    }
  }
  this.depIds = this.newDepIds
  var tmp = this.deps
  this.deps = this.newDeps
  // TODO: 既然newDeps最終會被置空,這邊賦值的意義在於?
  this.newDeps = tmp
}

// 訂閱中心通知消息更新
Watcher.prototype.update = function (shallow) {
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync || !config.async) {
    this.run()
  } else {
    // if queued, only overwrite shallow with non-shallow,
    // but not the other way around.
    this.shallow = this.queued
      ? shallow
        ? this.shallow
        : false
      : !!shallow
    this.queued = true
    // record before-push error stack in debug mode
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.debug) {
      this.prevError = new Error('[vue] async stack trace')
    }
    // 添加到待執行池
    pushWatcher(this)
  }
}

// 執行更新回調
Watcher.prototype.run = function () {
  if (this.active) {
    var value = this.get()
    if (
      ((isObject(value) || this.deep) && !this.shallow)
    ) {
      // set new value
      var oldValue = this.value
      this.value = value
      var prevError = this.prevError
      // ...
      this.cb.call(this.vm, value, oldValue)
    }
    this.queued = this.shallow = false
  }
}

Watcher.prototype.depend = function () {
  var i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

wtach回調執行隊列

在上面我們可以發現,watch在收到信息更新執行update時。如果非同步情況下會執行pushWatcher(this)將實例推入執行池中,那麼在何時會執行回調函數,如何執行呢?我們一起看看pushWatcher的實現。

// batch.js
var queueIndex
var queue = []
var userQueue = []
var has = {}
var circular = {}
var waiting = false
var internalQueueDepleted = false

// 重置執行池
function resetBatcherState () {
  queue = []
  userQueue = []
  // has 避免重複
  has = {}
  circular = {}
  waiting = internalQueueDepleted = false
}

// 執行執行隊列
function flushBatcherQueue () {
  runBatcherQueue(queue)
  internalQueueDepleted = true
  runBatcherQueue(userQueue)
  resetBatcherState()
}

// 批量執行
function runBatcherQueue (queue) {
  for (queueIndex = 0; queueIndex < queue.length; queueIndex++) {
    var watcher = queue[queueIndex]
    var id = watcher.id
    // 執行後置為null
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > config._maxUpdateCount) {
        warn(
          'You may have an infinite update loop for watcher ' +
          'with expression "' + watcher.expression + '"',
          watcher.vm
        )
        break
      }
    }
  }
}

// 添加到執行池
export function pushWatcher (watcher) {
  var id = watcher.id
  if (has[id] == null) {
    if (internalQueueDepleted && !watcher.user) {
      // an internal watcher triggered by a user watcher...
      // let's run it immediately after current user watcher is done.
      userQueue.splice(queueIndex + 1, 0, watcher)
    } else {
      // push watcher into appropriate queue
      var q = watcher.user
        ? userQueue
        : queue
      has[id] = q.length
      q.push(watcher)
      // queue the flush
      if (!waiting) {
        waiting = true
        // 在nextick中執行
        nextTick(flushBatcherQueue)
      }
    }
  }
}

4. patch實現

上面便是vue中數據驅動的實現原理,下面我們接着回到主流程中,在執行完watch后,便執行this._update(this._watcher.value)開始節點渲染

// _update => createPatchFunction => patch => patchVnode => (dom api)

// vtree是通過compile函數編譯的render函數執行的結果,返回了當前表示當前dom結構的對象(虛擬節點樹)
_update (vtree) {
  if (!this._tree) {
    // 第一次渲染
    patch(this._el, vtree)
  } else {
    patch(this._tree, vtree)
  }
  this._tree = vtree
}

// 在處理節點時,需要針對class,props,style,attrs,events做不同處理
// 在這裏注入針對不同屬性的處理函數
const patch = createPatchFunction([
  _class, // makes it easy to toggle classes
  props,
  style,
  attrs,
  events
])

// => createPatchFunction返回patch函數,patch函數通過對比虛擬節點的差異,對節點進行增刪更新
// 最後調用原生的dom api更新html
return function patch (oldVnode, vnode) {
  var i, elm, parent
  var insertedVnodeQueue = []
  // pre hook
  for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()

  if (isUndef(oldVnode.sel)) {
    oldVnode = emptyNodeAt(oldVnode)
  }

  if (sameVnode(oldVnode, vnode)) {
    // someNode can patch
    patchVnode(oldVnode, vnode, insertedVnodeQueue)
  } else {
    // 正常的不復用 remove insert
    elm = oldVnode.elm
    parent = api.parentNode(elm)

    createElm(vnode, insertedVnodeQueue)

    if (parent !== null) {
      api.insertBefore(parent, vnode.elm, api.nextSibling(elm))
      removeVnodes(parent, [oldVnode], 0, 0)
    }
  }

  for (i = 0; i < insertedVnodeQueue.length; ++i) {
    insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i])
  }

  // hook post
  for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
  return vnode
}

結尾

以上分析了vue從template 到節點渲染的大致實現,當然也有某些地方沒有全面分析的地方,其中template解析為ast主要通過正則匹配實現,及節點渲染及更新的patch過程主要通過節點操作對比來實現。但是我們對編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染的大致流程有了個比較完整的認知。

歡迎到前端學習打卡群一起學習~516913974

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。