沐鳴平台網址_JavaScript 代碼加不加分號有什麼區別

這個問題在很多文章中都討論過,在 ESlint 規範中也因為加不加分號而分為兩大陣營,到於加不加分號,關鍵是需要了解分號對於 JavaScript 的影響,開始之前可以先看看下面這道面試題:

請問這段代碼是否能夠正常運行?

var a = 1
(function() {
  console.log(2)
})()

如果運行這段代碼,會出現下面的錯誤:

Uncaught TypeError: 1 is not a function

什麼鬼!1 is not a function ?我們沒有打算運行数字 1,為什麼說数字 1 不是函數,這種錯誤是很難找到原因的,經常會在出問題的代碼行上打轉。這個錯誤必然是上嗎的代碼在運行時被看作是同一行,其概念如下:

var a = 1(function() { /* */ })()

因此立即函數的 () 被附加在 1 上,這是一個調用函數的語法,所以會產生 1 is not a function 的錯誤,想要避免這個錯誤就需要使用分號:

var a = 1 // 隨便把分號放在哪裡,只要能隔開就行
;(function() {
  console.log(2)
})()

ASI 自動加入分號

ASI是 “Automatic Semicolon Insertion” 的縮寫,在運行時會往有些折行的代碼中自動插入分號,這個機制可以使部分代碼在沒有加入分號時也能正常運行,比如下面的例子:

var b = 1
++b
console.log('b', b)

由於代碼中的 ++ 屬於一元表達式,它只能在表達式的左邊右邊放置變量,如果沒有 ASI 的機制,代碼會被轉換為 var b = 1 ++ b 這樣的錯誤語句。不過好在有 ASI,在實際運行時會自動被加入分號,也就不會出現上面的錯誤。

var b = 1;
++b;
console.log('b', b); // 2

return 與分號的關係

再來看一個例子,下面的代碼在 return 的後面空一行后再寫要返回的值,那麼問運行結果是什麼呢?

function fn() {
  return 
  '小明'
}
console.log(fn())

這段程序代碼因為 ASI 的修正,return 的後面會被加上一個分號,所以 return 與預期返回的值被分開了,結果 return 的內容為空值,最終的結果也只能是 undefined 。

function fn() {
  return;
  '小明';
}
console.log(fn()); // undefined

到底應該怎樣處理分號

本來 ASI 是出於一片好心,用來修正沒有加入分號的代碼片段,但偏偏在有的地方並沒有發揮它的作用(例如本文一開始所介紹的立即函數),導致代碼出現了錯誤;甚至有些代碼不會出錯,但會使你的代碼執行結果和預期相差萬里。

解決 ASI 問題的方式如下:

無論如何都要加上分號,完全由自己決定代碼的分割

牢記不會自動加入分號的規則,當不會自動插入分號時,則手動加入

不會被自動加入分號的規則

下面時各種不會自動加入分號的規則:

新行的代碼是從 (、[、/ 字符開始的,這類情況一般會直接出現 Uncaught TypeError 從而導致代碼無法運行。

var a = 1
var b = a
(a + b).toString()

var a = 1
[1,2,3].forEach(bar)
 
(function() { })()
(function() { })()
 
var a = 1
var b = a
/test/.test(b)

新行以 +,-,*,% 開始,這類情況大多會影響運算結果,所以應該合併為一行。

var a = 2
var b = a
+a

新行以 , 或 . 開始,這種用法經常會出現,主要是為了避免代碼過長而加入的分隔,這種情況並不會影響運行,如果善用的話會使代碼更容易閱讀。

var a = 2
var b = a
  .toString()
console.log(typeof b)
 
var a = 1
,b = 2 // b 同樣會被 var 聲明

如果遇到需要加入分號的情況,除了可以在語句的末尾加入分號外,也可以把分號加在“不會自動加入分號”的最前方,例如 () 本身不會自動加入分號,在有這種需求時可以將 ; 加到前面(ESLint Standard JS 規範就用這個方法避免錯誤)。

// 運行錯誤
(function() { })()
(function() { })()
 
// 正確
;(function() { })()
;(function() { })()

總結

有的人認為不加分號可以讓代碼看起來更乾淨和精簡,而且在大部分情況下並不會出現錯誤,所以很多人在敲代碼時不會加分號。

不過我更傾向於更嚴格的規範,也許是因為我是從後端轉到前端的,習慣了。至於到底怎麼選,只要搞清楚運行上的限制,不管哪種風格都是挺不錯的,只要你喜歡就好。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9876

沐鳴怎麼當代理?_調用函數時到底可以傳多少個參數

在創建一個函數並調用時可以傳入一些參數或變量,不過函數究竟可以有多少可用的參數呢?

函數中的變量

以下通過一個例子來檢查在調用函數時會有哪些變量和參數,在這裏可以在瀏覽器的“無痕窗口”中直接運行這段代碼(無痕窗口能避免瀏覽器插件影響運行)。

注意:這裏僅僅針對傳統函數,箭頭函數的運行變量與傳統函數不同,在此不做討論。

var globalvariable = '全局變量';
var obj = {
  aFunction: function (para) {
    var localVariables = '局部變量';
    console.log(para, localVariables, arguments, this, globalVariable);
    // 包含傳入的參數
  }
}
obj.aFunction('我是一段描述', 2, 3);

在運行時切換到 Source 頁,並用 Chrome 的 JavaScript 調試模式來進行驗證。

接下來進入到 aFunction 的函數中時,可以切換到 console.log(…) 一這行停下來,結果如下:

接下來會看到 Scope 表示當前函數的作用域,作用域中可調用的變量也會按順序列出,在這裏可以看到的變量和參數包含:

  • para: 調用時傳入的參數。
  • arguments: 傳統函數默認會帶入的外部參數,這個參數來自於外部所傳入的參數,就算函數本身沒有傳入也可以在這裏取值,本文後方會有更詳細的介紹。
  • this: 函數運行時傳入的變量,調用函數的方式將會影響它的指向,以後我會另外再寫一篇文章進行介紹。
  • localVariables: 局部變量,僅在此函數內的作用域才可以調用。
  • Global: 全局變量

以上五個變量中,前兩個就屬於在調用時傳入的變量,本文也會着重介紹它們的特點。

參數的性質

參數是由主調函數傳入的變量,相對於其它編程語言來說, js 傳遞參數的限制更少,任何值都可以作為參數,也正是因為如此,初次接觸或從其它語言轉過來的會有許多不熟悉的地方,以下是一些常見的問題:

參數名稱是在函數聲明時定義的

這是一個非常基礎的問題,剛開始學編程的新手經常會把參數名搞混,誤以為調用時傳入的參數名就是在函數內使用的名稱,比如下面的例子輸出的值是什麼?

A. ‘a’, ‘b’, ‘c’, undefined
B. ‘d’, ‘c’, ‘b’, ‘a’

function callMore(d, c, b, a) {
  console.log(d, c, b, a);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

函數參數的名稱是在定義函數時就已經確定了的,如下圖:參數取值是按順序帶入,並且不會受到調用時名稱的影響

簡單的結論:

  • 參數名稱不會受到調用名的影響
  • 如果遇到聲明了卻沒有傳入值的參數,那麼是 undefined
  • 如果未定義,但有更多的參數傳入則需要使用其它方式取值

ES6 的參數默認值

如果已經定義了參數,但卻沒有傳入,那麼函數內取到的值為 undefined ,如果遇到這樣的情況,代碼就必須加入許多的容錯機制,以避免 undefined 造的錯誤。

ES6 中新增了“參數默認值”可預先給特定參數設置默認值,除了避免 undefined 所造成的錯誤外,還可以增加使用函數的彈性。

下面的例子通過簡單的語法就可加上“參數默認值”,當外部沒有傳入值時就會使用默認值。

function getMoney(money = 1000) {
  console.log(`我有 ${ money }`);
}
getMoney(); // 不需要傳入參數

arguments

如果無法確認所傳入參數的數量該怎麼辦?

這種情況在項目開發中比較少見,但在開發框架、函數庫時卻很常見,前面所提到的 “如果未定義,但有更多的參數傳入則需要使用其它方式取值”的情況 ,就會用到下面將要介紹的 arguments 參數(ES6 中有更好的方法)。

function callMore(d, c, b, a) {
  // 注意:在此並沒有用到定義的 d, c, b, a 參數
  console.log(arguments);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

所有的傳統函數都有 arguments 參數(注意:箭頭函數沒有),不需要另外定義即可直接調用,且作用域僅限於本函數中。

arguments 的結構本質上是一個數組,其中會包含調用時傳入的所有值,在不確定傳入參數的數量時,是一個很好用的方法。

ES6 剩餘參數

因為 arguments 有一些缺點,如:

  • 結構類似數組,但是無法直接使用大部分的數組方法
  • 與已經定義的參數內容重疊,無法僅用於額外傳入的參數

所以 ES6 新增了剩餘參數語法,可在定義參數時直接傳入剩餘的未定的參數,語法如下:

const callMore = (a, ...args) => {
  console.log(args);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b, c);

結果和前面的例子類似,但會是純數組的方式展現,並且只會獲取未定義的參數內容(a 會被跳過)。另外,箭頭函數是可以使用剩餘參數的。

函數也可作為參數

函數除了可以傳入純值、數組和對象外,還可以把函數作為參數,使函數運行時更加豐富而且——呃。。。複雜。

下面的例子中,在調用 functionB 時,還可以傳入另一個函數作為參數,這種手段叫回調函數( callback function)。

function functionB(fn) {
  fn('小明');
}
functionB(function(name) {
  console.log(name + ' 您好');
});

你自己可以試着看看其中的參數是如何傳遞的

JavaScript 可將函數作為參數傳遞這樣的特性也稱為“一級函數”(First-class Function)

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9870

    沐鳴平台_iframe無刷新提交表單,iframe仿ajax提交表單

    本文摘要

    使用ajax可以實現無刷新提交表單,但有人表示ajax的效率不行,或者是其他缺點,例如客戶端臃腫,太多客戶段代碼造成開發上的成本,如果網速慢,則會出現ajax請求緩慢,頁面空白的情況,對客戶的體驗不好。ajax請求不利於搜索引擎優化,一般搜不到ajax添加到頁面的信息。

    這次就介紹一下iframe仿造ajax異步請求,實際上iframe是同步請求,只是把提交的跳轉,發生在iframe的可視區域內。

    代碼

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title>iframe提交表單</title>
        <meta charset="utf-8">
        <style type="text/css"> #result{ border: none; /*去掉默認的邊框*/ width: 300px; /*可視區域的寬度*/ height: 60px; /*可視區域的高度*/ } </style>
    </head>
    <body>
    <!-- 表單 -->
    <h1>iframe提交表單</h1>
    <form action="check.php" method="post" target='result'>
        <input type="text" class="input_css" name="user" placeholder="請輸入賬號"><br/>
        <input type="password" class="input_css" name="pwd" placeholder="請輸入密碼"><br/>
        <input type="submit" class="formbtn" value="登陸"><br/>
    </form>
    
    <!-- 用於查看提交結果 -->
    <iframe name='result' id="result" scrolling="no"></iframe>
    </body>
    </html>

    check.php

    <style type="text/css"> *{ margin:0; padding:0; } </style>
    <?php // 設置編碼 header("Content-type:text/html;charset=utf-8"); // 獲得POST過來的登陸所需參數 $user = $_POST["user"]; $pwd = $_POST["pwd"]; // 過濾參數 if ($user == '' && $pwd == '') { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>賬號和密碼不得為空</p>"; }else if ($user == '' ) { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>賬號不得為空</p>"; }else if ($pwd == '' ) { echo "<p style='color:#f00;font-size:15px;margin-top:10px;'>密碼不得為空</p>"; }else{ echo "<p style='color:#000;font-size:15px;margin-top:10px;'>你提交的賬號是:".$user."<br/>你提交的密碼是:".$pwd."</p>"; } ?>

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10086

    杏耀註冊平台官網_Angular 如何處理未可知異常錯誤

    寫在前面

    代碼寫得再好,始終都無法完整的處理所有可能產生異常,特別是生產環境中的應用,很大一部分是數據來自用戶、遠程,很難保證所有數據都按程序規定的產生。事實上,除非測試人員發現或者客戶報告,否則都無法得知。因此,將應用產生的未可知異常進而上報是非常重要的環節。

    Angular 默認情況下也提供了全局的異常管理,當發生異常時,會把它扔到 Console 控制台上。如果你在使用 NG-ZORRO 時,可能經常就會遇到 ICON 未加載的異常消息,這也是異常消息的一種:

    core.js:5980 ERROR Error: [@ant-design/icons-angular]:the icon setting-o does not exist or is not registered.
        at IconNotFoundError (ant-design-icons-angular.js:94)
        at MapSubscriber.project (ant-design-icons-angular.js:222)
        at MapSubscriber._next (map.js:29)
        at MapSubscriber.next (Subscriber.js:49)
        at RefCountSubscriber._next (Subscriber.js:72)
        at RefCountSubscriber.next (Subscriber.js:49)
        at Subject.next (Subject.js:39)
        at ConnectableSubscriber._next (Subscriber.js:72)
        at ConnectableSubscriber.next (Subscriber.js:49)
        at CatchSubscriber.notifyNext (innerSubscribe.js:42)

    而 Angular 是通過 ErrorHandler 統一管理異常消息,而且只需要覆蓋其中的 handleError 方法並重新處理異常消息即可。

    ErrorHandler

    首先創建一個 custom-error-handler.ts 文件:

    import { ErrorHandler, Injectable } from '@angular/core';
    
    @Injectable()
    export class CustomErrorHandler extends ErrorHandler {
      handleError(error: any): void {
        super.handleError(error);
      }
    }

    CustomErrorHandler 可以完整的獲取當前用戶數據、當前異常消息對象等,並允許通過 HttpClient 上報給後端。

    以下是 NG-ALAIN 的文檔站,由於是使用 Google Analytics 來分析,只需要將異常消息轉給 onerror 即可:

    import { DOCUMENT } from '@angular/common';
    import { ErrorHandler, Inject, Injectable } from '@angular/core';
    
    @Injectable()
    export class CustomErrorHandler extends ErrorHandler {
      constructor(@Inject(DOCUMENT) private doc: any) {
        super();
      }
    
      handleError(error: any): void {
        try {
          super.handleError(error);
        } catch (e) {
          this.reportError(e);
        }
        this.reportError(error);
      }
    
      private reportError(error: string | Error): void {
        const win = this.doc.defaultView as any;
        if (win && win.onerror) {
          if (typeof error === 'string') {
            win.onerror(error);
          } else {
            win.onerror(error.message, undefined, undefined, undefined, error);
          }
        }
      }
    }

    最後,在 AppModule 模塊內註冊 CustomErrorHandler:

    @NgModule({
        providers: [
            { provide: ErrorHandler, useClass: CustomErrorHandler },
        ]
    })
    export class AppModule { }

    結論

    事實上還有一項非常重要的工作,生產環境中都是打包壓縮過後的,換言之所產生的異常消息也是無法與實際代碼行數相同的数字,這就需要 SourceMap 的支持,當然正常的生產環境是不會發布這份文件的,所以如果想要得到正確的行列數,還是需要藉助一層中間層,在後端利用 source-map 模塊來解析出真正的行列數值。

    Angular 的依賴注入(DI)系統可以使我們快速替換一些 Angular 內置模塊,從而實現在不修改業務層面時快速解決一些特殊需求。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10085

    沐鳴開戶_發布Npm包到GitHub Packages

    Github集成了GitHub Packages功能,目前提供了npm、Docker、Maven、NuGet、RubyGems的包管理工具,可以通過Github管理開源包,本文主要介紹使用GitHub Packages發布Npm包。

    發布

    首先需要製作一個package.json文件,可以通過npm init命令根據提示生成一個package.json文件。


    這是已經發布好的package.json文件,作為示例,需要注意的是name字段、publishConfig字段與repository字段的配置,在GitHub Packages發布的包屬於作用域包,需要使用@username/package-name的形式作為name字段,publishConfig是發布到GitHub Packages的必填且值固定的字段,repository字段是必須要指定的倉庫url,可以發布多個包到一個倉庫,可以參考https://github.com/WindrunnerMax/Asse/packages/292805。

    `{ "name": "@WindrunnerMax/mini-program-cli", "version": "1.1.0", "description": "Uniapp小程序開發腳手架", "author": "Czy", "license": "MIT", "bin": { "mini-program": "bin/cli.js" }, "scripts": { "test": "echo "Please use HbuildX import this project"" }, "engines": { "node": ">= 8" }, "publishConfig": { "registry": "https://npm.pkg.github.com/WindrunnerMax" }, "repository": "https://github.com/WindrunnerMax/Asse" }` 

    下面需要授權,首先在Github申請一個Token,user – setting – Developer settings – Personal access tokens – Generate new token,生成一個用以發布Npm包的Token,需要選擇權限,以下權限必選
    hub申請一個Token,user – setting – Developer settings – Personal access tokens – Generate new token,生成一個用以發布Npm包的Token,需要選擇權限,以下權限必選:

    接下來將Token添加至~/.npmrc,win用戶是路徑C://users/current-user。

    //npm.pkg.github.com/:_authToken=TOKEN

    或者使用npm login命令進行授權,注意用戶名要全部小寫,Token的輸入是以密碼的方式輸入,不會显示。

    npm login --registry=https://npm.pkg.github.com
    > Username: USERNAME
    > Password: TOKEN
    > Email: PUBLIC-EMAIL-ADDRESS`

    接下來在項目的根目錄添加一個~/.npmrc文件,並添加如下配置。

    registry=https://npm.pkg.github.com/WindrunnerMax

    接下來就可以使用npm publish命令發布包。

    npm publish --access=public 

    安裝

    需要注意的是,無論發布包還是安裝包都需要授權,也就是上述生成Token以及配置的過程,否則無法安裝指定的包,以我發布的包為例,執行安裝。

    npm install -g @windrunnermax/mini-program-cli@1.1.0 --registry=https://npm.pkg.github.com/

    如果使用Github安裝Npm包很慢的話,可以考慮配置代理,同樣是在~/.npmrc文件中加入配置。

    proxy=socks5://127.0.0.1:1080
    https-proxy=socks5://127.0.0.1:1080`

    Github

    https://github.com/WindrunnerMax

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10076

    沐鳴登錄_開發React組件 發布npm包 (使用TSDX)

    運行該命令,會新建組件開發的文件夾。(mylib就是項目名)

    因為我這邊的網速很爛 所以可以先安裝

    cnpm i tsdx@latest -g

    然後在執行

    npx tsdx create mylib

    中途我們會被要求選擇一個模版:

    模版 描述
    basic 用於一個TypeScript包,可以開發任何東西,靈活度高
    react 用於開發React組件的包,內置了@types,而且有一個基於Parcel的調試模塊,幫助快速開發
    react-with-storybook 與react模版相同,但是多內置了storybook

    我們選擇第二個,react模版。

    在mylib文件夾下,src文件夾是讓你寫源碼的,example是讓你開發調試用的文件夾,裏面也是源碼(使用你npm包的源碼),dist是你編譯后的輸出目錄,在npm pub時就會把dist上傳到npm上

    到這一步 從NPM下載依賴 因為我的網還是很爛,一直裝不上,所以ctrl+c 退出了,使用cnpm來安裝

    cnpm i @size-limit/preset-small-lib @types/react @types/react-dom husky react react-dom size-limit tsdx tslib typescript --save-dev

    安裝完成后 目錄結構是

    這是想要啟動它 需要打開2個shell(一個用於實時編譯到dist,另一個用於example的調試)

    用於實時編譯的shell:

    npm start # or yarn start

    用於實時調試的shell:

    cd example
    cnpm i # yarn install
    npm start # yarn start

    前者會實時監測代碼變更,編譯最新的版本到dist中,後者會監測dist變更,將example中的內容啟動,默認在 http://localhost:1234/ 運行example項目。

    現在你可以去試着寫一些內容,看看有沒有生效

    改動一些內容

    在src/index.tsx中,默認有如下內容:

    import * as React from 'react';
    // Delete me
    export const Thing = () => {
     return <div>啊哈哈哈哈</div>;
    };

    注意,src/index.tsx中export的內容,就是我們的npm包要導出的內容。例如上面代碼,導出了Thing,如果npm包名字是my-demo,將來發布后,需要這樣引入:

    import { Thing } from 'my-demo';

    接下來,看看example/index.tsx的內容:

    import 'react-app-polyfill/ie11';
    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    import { Thing } from '../.';
    
    const App = () => {
     return (
         <div>
            <Thing />
         </div>
     );
    };
    
    ReactDOM.render(<App />, document.getElementById('root'));

    本地測試時,我們肯定不能先發布再去測試,TSDX的做法比較好,它是這麼做的:

    import { Thing } from '../.'; // 就是example/index.tsx的第4行

    意思是去example文件夾的上一層來導入,它會發現上層文件夾的package.json,根據裏面的module或main來import到相應的內容(這些都不需要我們關心,因為它已經定義好了”module”: “dist/mylib.esm.js”,和”main”: “dist/index.js”)。

    所以,在example/index.tsx中,我們寫一些使用我們npm包的案例,不僅方便開發時的測試,也可以作為我們npm包的“最佳實踐”,一舉兩得。

    此外,可以關注一下example/index.html,使用example測試時,TSDX實際上是基於parcel的,會基於index.html生成網頁,展示example/index.tsx中的案例。如果你需要修改html中的內容,你可以直接修改,也是非常方便的!下面是example/index.html默認的代碼:

        <!DOCTYPE html>
        <html lang="en">
         <head>
         <meta charset="UTF-8" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <meta http-equiv="X-UA-Compatible" content="ie=edge" />
         <title>Playground</title>
         </head>
        
         <body>
         <div id="root"></div>
         <script src="./index.tsx"></script>
         </body>
        </html>

    接下來就可以發布啦

    # 發布前要先在根目錄下編譯 
    npm build # yarn build
    # 正式發布
    npm publish

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10073

    沐鳴代理:_JavaScript this 關鍵字詳解

    一、前言

    this關鍵字是JavaScript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。對於那些沒有投入時間學習this機制的JavaScript開發者來說,this的綁定一直是一件非常令人困惑的事。

    二、了解this

    學習this的第一步是明白this既不指向函數自身也不指向函數的詞法作用域,你也許被這樣的解釋誤導過,但其實它們都是錯誤的。隨着函數使用場合的不同,this的值會發生變化。但總有一條原則就是js中的this代表的是當前行為執行的主體,在js中主要研究的都是函數中的this,但並不是說只有在函數里才有this,this實際上是在函數被調用時發生的綁定,它指向什麼完全取決於函數在哪裡被調用。如何的區分this呢?

    三、this到底是誰

    這要分情況討論,常見有五種情況:

    1、函數執行時首先看函數名前面是否有”.”,有的話,”.”前面是誰,this就是誰;沒有的話this就是window

    function fn(){
      console.log(this);
    }
    var obj={fn:fn};
    fn();//this->window
    obj.fn();//this->obj
    function sum(){
         fn();//this->window
    }
    sum();
    var oo={
     sum:function(){
     console.log(this);//this->oo
           fn();//this->window
      }
    };
    oo.sum();
    

    2、自執行函數中的this永遠是window

      (function(){ //this->window })();
      ~function(){ //this->window }();
    

    3、給元素的某一個事件綁定方法,當事件觸發的時候,執行對應的方法,方法中的this是當前的元素,除了IE6~8下使用attachEvent(IE一個著名的bug)

    DOM零級事件綁定

      odiv.onclick=function(){
         //this->odiv
      };
    

    DOM二級事件綁定

      oDiv.addEventListener("click",function(){
         //this->oDiv
      },false);
    

    在IE6~8下使用attachEvent,默認的this就是指的window對象

      oDiv.attachEvent("click",function(){
           //this->window
      });
    

    我們大多數時候,遇到事件綁定,如下面例子這種,對於IE6~8下使用attachEvent不必太較真

    function fn(){
      console.log(this);
    }
    document.getElementById("div1").onclick=fn;//fn中的this就是#divl
    document.getElementById("div1").onclick=function(){
    console.log(this);//this->#div1
    fn();//this->window
    };
    

    4、在構造函數模式中,類中(函數體中)出現的this.xxx=xxx中的this是當前類的一個實例

    function CreateJsPerson(name,age){
    //瀏覽器默認創建的對象就是我們的實例p1->this
    this.name=name;//->p1.name=name
    this.age=age;
    this.writeJs=function(){
    console.log("my name is"+this.name +",i can write Js");
       };
    //瀏覽器再把創建的實例默認的進行返回
    }
    var p1=new CreateJsPerson("尹華芝",48);
    

    必須要注意一點:類中某一個屬性值(方法),方法中的this需要看方法執行的時候,前面是否有”.”,才能知道this是誰。大家不妨看下接下來的這個例子,就可明白是啥意思。

    function Fn(){
    this.x=100;//this->f1
    this.getX=function(){
    console.log(this.x);//this->需要看getX執行的時候才知道
       }
    }
    var f1=new Fn;
    f1.getX();//->方法中的this是f1,所以f1.x=100
    var ss=f1.getX;
    ss();//->方法中的this是window ->undefined 

    5.call、apply和bind

    我們先來看一個問題,想在下面的例子中this綁定obj,怎麼實現?

    var obj={name:"浪里行舟"};
    function fn(){
    console.log(this);//this=>window
    }
    fn();
    obj.fn();//->Uncaught TypeError:obj.fn is not a function
    

    如果直接綁定obj.fn(),程序就會報錯。這裏我們應該用fn.call(obj)就可以實現this綁定obj,接下來我們詳細介紹下call方法:

    call方法的作用:

    ①首先我們讓原型上的call方法執行,在執行call方法的時候,我們讓fn方法中的this變為第一個參數值obj;然後再把fn這個函數執行。

    ②call還可以傳值,在嚴格模式下和非嚴格模式下,得到值不一樣。

    //在非嚴格模式下
    var obj={name:"浪里行舟 "};
    function fn(num1,num2){
    console.log(num1+num2);
    console.log(this);
    }
    fn.call(100,200);//this->100 num1=200 num2=undefined
    fn.call(obj,100,200);//this->obj num1=100 num2=200
    fn.call();//this->window
    fn.call(null);//this->window
    fn.call(undefined);//this->window
    
    //嚴格模式下 
    fn.call();//在嚴格模式下this->undefined
    fn.call(null);// 在嚴格模式 下this->null
    fn.call(undefined);//在嚴格模式下this->undefined
    

    apply和call方法的作用是一模一樣的,都是用來改變方法的this關鍵字並且把方法執行,而且在嚴格模式下和非嚴格模式下對於第一個參數是null/undefined這種情況的規律也是一樣的。

    兩者唯一的區別:call在給fn傳遞參數的時候,是一個個的傳遞值的,而apply不是一個個傳,而是把要給fn傳遞的參數值統一的放在一個數組中進行操作。但是也相當子一個個的給fn的形參賦值。總結一句話:call第二個參數開始接受一個參數列表,apply第二個參數開始接受一個參數數組

    fn.call(obj,100,200);
    fn.apply(obj,[100,200]);
    

    bind:這個方法在IE6~8下不兼容,和call/apply類似都是用來改變this關鍵字的,但是和這兩者有明顯區別:

    fn.call(obj,1,2);//->改變this和執行fn函數是一起都完成了
    fn.bind(obj,1,2);//->只是改變了fn中的this為obj,並且給fn傳遞了兩個參數值1、2,
                         但是此時並沒有把fn這個函數執行
    var tempFn=fn.bind(obj,1,2);
    tempFn(); //這樣才把fn這個函數執行
    

    bind體現了預處理思想:事先把fn的this改變為我們想要的結果,並且把對應的參數值也準備好,以後要用到了,直接的執行即可。

    call和apply直接執行函數,而bind需要再一次調用。

      var a ={
            name : "Cherry",
            fn : function (a,b) {
                console.log( a + b)
            }
        }
      var b = a.fn;
      b.bind(a,1,2)

    上述代碼沒有執行,bind返回改變了上下文的一個函數,我們必須要手動去調用:

     b.bind(a,1,2)() //3
    

    必須要聲明一點:遇到第五種情況(call apply和bind),前面四種全部讓步。


    四、箭頭函數this指向

    箭頭函數正如名稱所示那樣使用一個“箭頭”(=>)來定義函數的新語法,但它優於傳統的函數,主要體現兩點:更簡短的函數並且不綁定this

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = function () {
                return new Date().getFullYear() - this.birth; // this指向window或undefined
            };
            return fn();
        }
    };
    

    現在,箭頭函數完全修復了this的指向,箭頭函數沒有自己的this,箭頭函數的this不是調用的時候決定的,而是在定義的時候處在的對象就是它的this

    換句話說,箭頭函數的this看外層的是否有函數,如果有,外層函數的this就是內部箭頭函數的this,如果沒有,則this是window

        <button id="btn1">測試箭頭函數this_1</button>
        <button id="btn2">測試箭頭函數this_2</button>
        <script type="text/javascript">   
            let btn1 = document.getElementById('btn1');
            let obj = {
                name: 'kobe',
                age: 39,
                getName: function () {
                    btn1.onclick = () => {
                        console.log(this);//obj
                    };
                }
            };
            obj.getName();
        </script>

    上例中,由於箭頭函數不會創建自己的this,它只會從自己的作用域鏈的上一層繼承this。其實可以簡化為如下代碼:

       let btn1 = document.getElementById('btn1');
            let obj = {
                name: 'kobe',
                age: 39,
                getName: function () {
                    console.log(this)
                }
            };
       obj.getName();
    

    那假如上一層並不存在函數,this指向又是誰?

        <button id="btn1">測試箭頭函數this_1</button>
        <button id="btn2">測試箭頭函數this_2</button>
        <script type="text/javascript">   
            let btn2 = document.getElementById('btn2');
            let obj = {
                name: 'kobe',
                age: 39,
                getName: () => {
                    btn2.onclick = () => {
                        console.log(this);//window
                    };
                }
            };
            obj.getName();
        </script>

    上例中,雖然存在兩個箭頭函數,其實this取決於最外層的箭頭函數,由於obj是個對象而非函數,所以this指向為Window對象

    由於this在箭頭函數中已經按照詞法作用域綁定了,所以,用call()或者apply()調用箭頭函數時,無法對this進行綁定,即傳入的第一個參數被忽略

    var obj = {
        birth: 1990,
        getAge: function (year) {
            var b = this.birth; // 1990
            var fn = (y) => y - this.birth; // this.birth仍是1990
            return fn.call({birth:2000}, year);
        }
    };
    obj.getAge(2018); // 28

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10078

    沐鳴主管:_React中的優先級

    UI產生交互的根本原因是各種事件,這也就意味着事件與更新有着直接關係。不同事件產生的更新,它們的優先級是有差異的,所以更新優先級的根源在於事件的優先級。一個更新的產生可直接導致react生成一個更新任務,最終這個任務被Scheduler調度。

    所以在React中,人為地將事件劃分了等級,最終目的是決定調度任務的輕重緩急,因此,React有一套從事件到調度的優先級機制。

    本文將圍繞事件優先級、更新優先級、任務優先級、調度優先級,重點梳理它們之間的轉化關係。

    • 事件優先級:按照用戶事件的交互緊急程度,劃分的優先級
    • 更新優先級:事件導致React產生的更新對象(update)的優先級(update.lane)
    • 任務優先級:產生更新對象之後,React去執行一個更新任務,這個任務所持有的優先級
    • 調度優先級:Scheduler依據React更新任務生成一個調度任務,這個調度任務所持有的優先級

    前三者屬於React的優先級機制,第四個屬於Scheduler的優先級機制,Scheduler內部有自己的優先級機制,雖然與React有所區別,但等級的劃分基本一致。下面我們從事件優先級開始說起。

    優先級的起點:事件優先級

    React按照事件的緊急程度,把它們劃分成三個等級:

    • 離散事件(DiscreteEvent):click、keydown、focusin等,這些事件的觸發不是連續的,優先級為0。
    • 用戶阻塞事件(UserBlockingEvent):drag、scroll、mouseover等,特點是連續觸發,阻塞渲染,優先級為1。
    • 連續事件(ContinuousEvent):canplay、error、audio標籤的timeupdate和canplay,優先級最高,為2。

    派發事件優先級

    事件優先級是在註冊階段被確定的,在向root上註冊事件時,會根據事件的類別,創建不同優先級的事件監聽(listener),最終將它綁定到root上去。

    let listener = createEventListenerWrapperWithPriority(
        targetContainer,
        domEventName,
        eventSystemFlags,
        listenerPriority,
      );

    createEventListenerWrapperWithPriority函數的名字已經把它做的事情交代得八九不離十了。它會首先根據事件的名稱去找對應的事件優先級,然後依據優先級返回不同的事件監聽函數。

    export function createEventListenerWrapperWithPriority( targetContainer: EventTarget, domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, priority?: EventPriority, ): Function {
      const eventPriority =
        priority === undefined
          ? getEventPriorityForPluginSystem(domEventName)
          : priority;
      let listenerWrapper;
      switch (eventPriority) {
        case DiscreteEvent:
          listenerWrapper = dispatchDiscreteEvent;
          break;
        case UserBlockingEvent:
          listenerWrapper = dispatchUserBlockingUpdate;
          break;
        case ContinuousEvent:
        default:
          listenerWrapper = dispatchEvent;
          break;
      }
      return listenerWrapper.bind(
        null,
        domEventName,
        eventSystemFlags,
        targetContainer,
      );
    }
    

    最終綁定到root上的事件監聽其實是dispatchDiscreteEventdispatchUserBlockingUpdatedispatchEvent這三个中的一個。它們做的事情都是一樣的,以各自的事件優先級去執行真正的事件處理函數。

    比如:dispatchDiscreteEvent和dispatchUserBlockingUpdate最終都會以UserBlockingEvent的事件級別去執行事件處理函數。

    以某種優先級去執行事件處理函數其實要藉助Scheduler中提供的runWithPriority函數來實現:

    function dispatchUserBlockingUpdate( domEventName, eventSystemFlags, container, nativeEvent, ) {
    
      ...
    
      runWithPriority(
        UserBlockingPriority,
        dispatchEvent.bind(
          null,
          domEventName,
          eventSystemFlags,
          container,
          nativeEvent,
        ),
      );
    
      ...
    
    }

    這麼做可以將事件優先級記錄到Scheduler中,相當於告訴Scheduler:你幫我記錄一下當前事件派發的優先級,等React那邊創建更新對象(即update)計算更新優先級時直接從你這拿就好了。

    function unstable_runWithPriority(priorityLevel, eventHandler) {
      switch (priorityLevel) {
        case ImmediatePriority:
        case UserBlockingPriority:
        case NormalPriority:
        case LowPriority:
        case IdlePriority:
          break;
        default:
          priorityLevel = NormalPriority;
      }
    
      var previousPriorityLevel = currentPriorityLevel;
      // 記錄優先級到Scheduler內部的變量里
      currentPriorityLevel = priorityLevel;
    
      try {
        return eventHandler();
      } finally {
        currentPriorityLevel = previousPriorityLevel;
      }
    }

    更新優先級

    以setState為例,事件的執行會導致setState執行,而setState本質上是調用enqueueSetState,生成一個update對象,這時候會計算它的更新優先級,即update.lane:

    const classComponentUpdater = {
      enqueueSetState(inst, payload, callback) {
        ...
    
        // 依據事件優先級創建update的優先級
        const lane = requestUpdateLane(fiber, suspenseConfig);
    
        const update = createUpdate(eventTime, lane, suspenseConfig);
        update.payload = payload;
        enqueueUpdate(fiber, update);
    
        // 開始調度
        scheduleUpdateOnFiber(fiber, lane, eventTime);
        ...
      },
    };

    重點關注requestUpdateLane,它首先找出Scheduler中記錄的優先級:schedulerPriority,然後計算更新優先級:lane,具體的計算過程在findUpdateLane函數中,計算過程是一個從高到低依次佔用空閑位的操作,具體的代碼在這裏 ,這裏就先不詳細展開。

    export function requestUpdateLane( fiber: Fiber, suspenseConfig: SuspenseConfig | null, ): Lane {
    
      ...
      // 根據記錄下的事件優先級,獲取任務調度優先級
      const schedulerPriority = getCurrentPriorityLevel();
    
      let lane;
      if (
        (executionContext & DiscreteEventContext) !== NoContext &&
        schedulerPriority === UserBlockingSchedulerPriority
      ) {
        // 如果事件優先級是用戶阻塞級別,則直接用InputDiscreteLanePriority去計算更新優先級
        lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
      } else {
        // 依據事件的優先級去計算schedulerLanePriority
        const schedulerLanePriority = schedulerPriorityToLanePriority(
          schedulerPriority,
        );
        ...
        // 根據事件優先級計算得來的schedulerLanePriority,去計算更新優先級
        lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
      }
      return lane;
    }

    getCurrentPriorityLevel負責讀取記錄在Scheduler中的優先級:

    function unstable_getCurrentPriorityLevel() {
      return currentPriorityLevel;
    }

    update對象創建完成后意味着需要對頁面進行更新,會調用scheduleUpdateOnFiber進入調度,而真正開始調度之前會計算本次產生的更新任務的任務優先級,目的是與已有任務的任務優先級去做比較,便於做出多任務的調度決策。

    調度決策的邏輯在ensureRootIsScheduled 函數中,這是一個非常重要的函數,控制着React任務進入Scheduler的大門。


    任務優先級

    一個update會被一個React的更新任務執行掉,任務優先級被用來區分多個更新任務的緊急程度,它由更新優先級計算而來,舉例來說:

    假設產生一前一后兩個update,它們持有各自的更新優先級,也會被各自的更新任務執行。經過優先級計算,如果後者的任務優先級高於前者的任務優先級,那麼會讓Scheduler取消前者的任務調度;如果後者的任務優先級等於前者的任務優先級,後者不會導致前者被取消,而是會復用前者的更新任務,將兩個同等優先級的更新收斂到一次任務中;如果後者的任務優先級低於前者的任務優先級,同樣不會導致前者的任務被取消,而是在前者更新完成后,再次用Scheduler對後者發起一次任務調度。

    這是任務優先級存在的意義,保證高優先級任務及時響應,收斂同等優先級的任務調度。

    任務優先級在即將調度的時候去計算,代碼在ensureRootIsScheduled函數中:

    function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
    
      ...
    
      // 獲取nextLanes,順便計算任務優先級
      const nextLanes = getNextLanes(
        root,
        root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
      );
    
      // 獲取上面計算得出的任務優先級
      const newCallbackPriority = returnNextLanesPriority();
    
      ...
    
    }

    通過調用getNextLanes去計算在本次更新中應該處理的這批lanes(nextLanes),getNextLanes會調用getHighestPriorityLanes去計算任務優先級。任務優先級計算的原理是這樣:更新優先級(update的lane),
    它會被併入root.pendingLanes,root.pendingLanes經過getNextLanes處理后,挑出那些應該處理的lanes,傳入getHighestPriorityLanes,根據nextLanes找出這些lanes的優先級作為任務優先級。

    function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
      ...
      // 都是這種比較賦值的過程,這裏只保留兩個以做簡要說明
      const inputDiscreteLanes = InputDiscreteLanes & lanes;
      if (inputDiscreteLanes !== NoLanes) {
        return_highestLanePriority = InputDiscreteLanePriority;
        return inputDiscreteLanes;
      }
      if ((lanes & InputContinuousHydrationLane) !== NoLanes) {
        return_highestLanePriority = InputContinuousHydrationLanePriority;
        return InputContinuousHydrationLane;
      }
      ...
      return lanes;
    }

    getHighestPriorityLanes的源碼在這裏,getNextLanes的源碼在這裏

    return_highestLanePriority就是任務優先級,它有如下這些值,值越大,優先級越高,暫時只理解任務優先級的作用即可。

    export const SyncLanePriority: LanePriority = 17;
    export const SyncBatchedLanePriority: LanePriority = 16;
    
    const InputDiscreteHydrationLanePriority: LanePriority = 15;
    export const InputDiscreteLanePriority: LanePriority = 14;
    
    const InputContinuousHydrationLanePriority: LanePriority = 13;
    export const InputContinuousLanePriority: LanePriority = 12;
    
    const DefaultHydrationLanePriority: LanePriority = 11;
    export const DefaultLanePriority: LanePriority = 10;
    
    const TransitionShortHydrationLanePriority: LanePriority = 9;
    export const TransitionShortLanePriority: LanePriority = 8;
    
    const TransitionLongHydrationLanePriority: LanePriority = 7;
    export const TransitionLongLanePriority: LanePriority = 6;
    
    const RetryLanePriority: LanePriority = 5;
    
    const SelectiveHydrationLanePriority: LanePriority = 4;
    
    const IdleHydrationLanePriority: LanePriority = 3;
    const IdleLanePriority: LanePriority = 2;
    
    const OffscreenLanePriority: LanePriority = 1;
    
    export const NoLanePriority: LanePriority = 0;

    如果已經存在一個更新任務,ensureRootIsScheduled會在獲取到新任務的任務優先級之後,去和舊任務的任務優先級去比較,從而做出是否需要重新發起調度的決定,若需要發起調度,那麼會去計算調度優先級。

    調度優先級

    一旦任務被調度,那麼它就會進入Scheduler,在Scheduler中,這個任務會被包裝一下,生成一個屬於Scheduler自己的task,這個task持有的優先級就是調度優先級。

    它有什麼作用呢?在Scheduler中,分別用過期任務隊列和未過期任務的隊列去管理它內部的task,過期任務的隊列中的task根據過期時間去排序,最早過期的排在前面,便於被最先處理。而過期時間是由調度優先級計算的出的,不同的調度優先級對應的過期時間不同。

    調度優先級由任務優先級計算得出,在ensureRootIsScheduled更新真正讓Scheduler發起調度的時候,會去計算調度優先級。

    function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
    
        ...
    
        // 根據任務優先級獲取Scheduler的調度優先級
        const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
          newCallbackPriority,
        );
    
        // 計算出調度優先級之後,開始讓Scheduler調度React的更新任務
        newCallbackNode = scheduleCallback(
          schedulerPriorityLevel,
          performConcurrentWorkOnRoot.bind(null, root),
        );
    
        ...
    }

    lanePriorityToSchedulerPriority計算調度優先級的過程是根據任務優先級找出對應的調度優先級。

    export function lanePriorityToSchedulerPriority( lanePriority: LanePriority, ): ReactPriorityLevel {
      switch (lanePriority) {
        case SyncLanePriority:
        case SyncBatchedLanePriority:
          return ImmediateSchedulerPriority;
        case InputDiscreteHydrationLanePriority:
        case InputDiscreteLanePriority:
        case InputContinuousHydrationLanePriority:
        case InputContinuousLanePriority:
          return UserBlockingSchedulerPriority;
        case DefaultHydrationLanePriority:
        case DefaultLanePriority:
        case TransitionShortHydrationLanePriority:
        case TransitionShortLanePriority:
        case TransitionLongHydrationLanePriority:
        case TransitionLongLanePriority:
        case SelectiveHydrationLanePriority:
        case RetryLanePriority:
          return NormalSchedulerPriority;
        case IdleHydrationLanePriority:
        case IdleLanePriority:
        case OffscreenLanePriority:
          return IdleSchedulerPriority;
        case NoLanePriority:
          return NoSchedulerPriority;
        default:
          invariant(
            false,
            'Invalid update priority: %s. This is a bug in React.',
            lanePriority,
          );
      }
    }

    總結

    本文一共提到了4種優先級:事件優先級、更新優先級、任務優先級、調度優先級,它們之間是遞進的關係。事件優先級由事件本身決定,更新優先級由事件計算得出,然後放到root.pendingLanes,任務優先級來自root.pendingLanes中最緊急的那些lanes對應的優先級,調度優先級根據任務優先級獲取。幾種優先級環環相扣,保證了高優任務的優先執行。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10072

    沐鳴平台註冊登錄_響應式圖片解決方案

    如今開發一個網站不是響應式都不好意思拿出來,那麼作為響應式中的重要一環「響應式圖片」你又是如何解決的呢?

    網站的平均加載已經到了近 2MB 並在不斷地增加中,其中圖片佔據了絕大多數流量(63%)。可以肯定的是網頁已經有了嚴重的大小問題,而圖片就是罪魁禍首。雖然已經有很多種 措施 可以減少網頁加載量,但或許更重要的步驟之一是確保響應式圖片的加載方案。通過利用響應式圖片解決方案,我們可以確保最佳的圖片被加載,帶寬不會被過大的圖片所浪費。因此 W3C 定義了 picture 標籤:基於檢測客戶端設備類型的可替換圖片方案。具體是由 picture 這個標籤去實現,也就是說我們現在有了一個基於標準的響應式圖片解決方案可以用在實踐中。

    長話短說(人話)

    • 斷點(breakpoints)應該 取決於內容 而不是設備寬度 (css3 media query)
    • 根據尺寸加載不同圖片
    • 計算圖片像素密度並加到圖片加載列表中
    • 通過消除圖片加載列表中太相近的值來讓加載列表變得可維護
    • 利用程序自動輸出圖片的不同大小
    • 利用 img 標籤的 srcset sizes 等屬性輸出同一張圖片的不同路徑/尺寸,以解決響應式圖片的大小和像素密度的問題
    • Picturefill 庫能讓我們現在就使用這些強大的特性
    • 雖然 Picturefill 還有一些缺點 但這麼做仍然利大於弊

    定義

    第一步就是定義所有響應式圖片的尺寸和斷點,這些信息在網頁原型中就應該被精確的標示出來。重要的一點是 你的斷點應該取決於你的內容 而不是不同設備的寬度。這麼做是因為設備的參數是在不斷變化的,流行的設備尺寸總是在變。通過讓內容來決定斷點位置,這將確保我們的界面在所有屏幕上響應而不是特殊的幾個設備。

    注意 當決定哪些圖片應該被做成響應式時,要記住一點,大多數都是放在內容中的圖片。例如在 html 中插入的圖片而不是在 css 中的背景圖片。

    圖片尺寸

    首先將你的瀏覽器窗口放到最大(或者你規定的內容展示最大寬度),然後記錄下此時你的內容寬度和你想要展示的圖片寬度,通過瀏覽器的開發者工具或者類似的插件。

    接下來縮放你的瀏覽器窗口直到你想要給圖片設置寬度的下一個斷點,再繼續縮放直到你記錄下所有的作用於圖片寬度斷點。當你完成的時候你應該記錄下每張圖片在不同大小下應該載入的寬度。

    舉例,僅僅是例子:

    [max-width] : [1440]
    [breakpoint large] : [1120]
    [breakpoint medium] : [800]
    [reakpoint small] : [400]

    注意 關於決定斷點需要注意的是:斷點越多,代碼越難維護。除此之外大量的斷點會使 CSS 變得臃腫。所以盡量在保證效果的情況下保持最少的斷點。

    高分辨率

    下一步是根據你想要支持的分辨率 對圖片寬度進行計算。決定要支持那些分辨率是很困難的,因為有 太多的不同的分辨率,並且每支持一種分辨率你需要計算寬度並放在你的圖片加載列表裡。所以需要做的是根據實際情況和你的用戶群體去選擇支持不同的分辨率。

    如果你已經決定了需要支持那些高分辨率,那圖片加載列表會像下面這樣:

    [max-width] : [image width], [image width x1.5], [image width x2]
    [breakpoint large] : [image width], [image width x1.5], [image width x2]
    [breakpoint medium] : [image width], [image width x1.5], [image width x2]
    [breakpoint small] : [image width], [image width x1.5], [image width x2]

    整合

    如你所見,我們的圖片列表數量會隨着支持不同的分辨率和斷點而變長。通過改變整合列表項讓列表更清晰可控是很有必要的。例如任何相近或者不超過 200 像素差距的值。將幾個相近的值整合為一個值將有助於構建更清晰的列表:

    (min-width:1280px) : 1040px, 1560px, 2080px
    (min-width:1120px) : 924px, 1386px, 1848px
    (min-width:800px) : 800px, 1200px, 1600px
    (min-width:400px) : 400px, 600px, 800px

    注意 這裏用像素來做斷點值只是為了好對應圖片寬度,實際上你的 斷點應該使用相對單位(em/rem not px)

    輸出

    現在我們有了一個慎重考慮的圖片寬度列表,下一步則要將每個圖片導出為以「斷點名」+「像素密度倍數」為名的文件。例如我將最大的斷點稱為 “large” 並且圖片像素密度倍數為兩倍,那麼我的文件名則為 ‘image_large@2x.jpg’。我傾向於在 Photoshop 中保存圖片為 70% 壓縮,因為 70% 壓縮能確保達到最優的圖片大小並且不會損失過多的清晰度(這取決於不同圖片,目的是在保證清晰度的情況下盡量減小文件大小)。如果你傾向於保存為 JPEG 格式,那確保通過勾選 progressive(漸進) 來讓圖片從模糊到清晰的加載效果。

    譯者注 PS/AI 中保存圖片請使用「存儲為 web 格式/save for web」,快捷鍵 ctrl/command + shift + alt + s。PNG 格式請勾選 交錯/interlaced,JPEG 格式請勾選 漸進/progressive。

    腳本

    通過提前寫好動作腳本(宏)可以讓你批量自動化導出你想要的圖片大小。根據你選擇的設計軟件,自動化的腳本可以很容易的設置使用。不幸的是你仍然需要手動重命名每張圖片為 ‘xxx_large@2x.png’ 這樣的命名格式。如果你使用 Photoshop 這裡有一個 指南 可以幫助你編寫你自己的腳本批處理。或者你也可以用這個 寫好的 PS 腳本。

    構建工具

    另外一個自動化導出圖片的方法是使用構建工具,我選擇使用 Gulp。Gulp 是一個基於 JavaScript 流式思想的構建工具,這使得構建複雜任務的編寫更加簡單。這裡有各種任務插件,包括調整圖片大小、文件重命名。只需要編寫小段配置,你就可以完全自動化工作流中的某些部分,並且在使用的時候你無須再考慮。使用構建工具的另一個好處是你可以將你的任務鏈式的串聯起來並且可調整順序。

    優化

    最後,你需要注意的是在將你的圖片放進網頁前進行優化。這將確保圖片數據多餘的數據被刪除,將有效的減小文件的大小。和處理圖片或縮放圖片一樣,有很多種方法可以完成這個任務:你可以使用軟件或者終端命令手動優化圖片,或者你也可以使用構建工具自動完成這項任務。我喜歡的 JPGs 圖片壓縮軟件是 imageOptim PNG 是 imageAlpha,還有一大堆軟件你可以選擇。

    另外你也可以使用強大的構建工具,好處你不需要記得壓縮圖片,你只需要在你的每個項目里區配置工具即可。我選擇用 Gulp 的插件 (imagemin)[https://www.npmjs.com/package/gulp-imagemin] 來做這件事情,它也能壓縮 SVG 和 GIF 文件。

    實現

    最終的步驟是在網頁中實現響應式圖片。這種方法即所謂的 分辨率切換,因為我們只是切換了同一張圖片在不同尺寸和分辨率下的源文件地址,以達到在不同的尺寸和像素密度下達到響應的目的。所以我們會使用 picture 標準的一部分 srcset 和 sizes 屬性。這些屬性繼承了 <img> <source> 標籤,提供了一個可供瀏覽器選擇最佳圖片的圖片地址列表。我們實際上提供給瀏覽器的是我們所知道的信息,而具體會加載那個源則是未知的。這取決於用戶設備的帶寬、像素密度等。

    srcset 屬性

    讓我們從 srcset 屬性開始,首先我們會提供一個 src 屬性給那些不支持 srcset 屬性的瀏覽器。中等大小和分辨率就足夠了(不支持 srcset 屬性的瀏覽器用戶電腦的尺寸和分辨率也不會大)。接下來通過 srcset 指定給 <img> 標籤所有圖片源的信息。然後使用逗號分隔列出一個從小到大的圖片源列表。每個圖片源后可以跟w描述符或者x描述符。最後的結果看起來像是這樣:

    <img src="image_medium.jpg"
    srcset="image_small.jpg 400w, image_small@1.5x.jpg 600w, image_medium.jpg 800w, image_xlarge.jpg 1040w, image_large@1.5x.jpg 1386w, image_medium@2x.jpg 1600w, image_large@2x.jpg 1848w, image_xlarge@2x.jpg 2080w"
    alt="Image description" />

    現在我們有了一個可供選擇源的圖片標籤,瀏覽器可以根據選擇採用最佳的源,同時我們還有一個回退方案填寫在 src 屬性中。如果瀏覽器支持 srcset 則會下載最佳的圖像,否則直接下載 src 屬性內的圖像。因此帶寬浪費和頁面大小冗餘會被降到最低。

    需要注意的是 srcset 只是描述了一個 <img> 有哪些可用的源,後面的 w 描述符並不是說某個寬度下就一定會加載這個源。具體的情況還是取決於用戶的瀏覽器環境、帶寬等 同時對於同一源,只能標示一個符號w/x,不能同時標記兩者,也建議不要在列表中混用 w/x,混用將會導致計算過於複雜。

    sizes 屬性

    我們也可以通過設置 sizes 屬性來幫助瀏覽器選擇不同的圖片源,通過 size 屬性瀏覽器知道我們的圖片在不同的 viewport 下佔了多大,也就是圖片相對於 viewport 的比例。sizes 屬性並不是必填的,沒有sizes 屬性 srcset 仍然有效。但是如果 srcset 屬性沒有那麼 sizes 屬性將不會生效。

    如果沒有 sizes 屬性,那麼 srcset 則被認為不論圖片佔多寬,都始終採用和 viewport 相同寬度的源(排除分辨率的影響)。建議 sizes 應該配合 srcset 一起使用:

    <img src="image_medium.jpg"
    sizes ="(min-width:1120px) 924px, (min-width:1280px) 1040px, 100vw"
    srcset="image_small.jpg 400w, image_small@1.5x.jpg 600w, image_medium.jpg 800w, image_xlarge.jpg 1040w, image_large@1.5x.jpg 1386w, image_medium@2x.jpg 1600w, image_large@2x.jpg 1848w, image_xlarge@2x.jpg 2080w"
    alt="Image description" />

    上面的代碼告訴瀏覽器,在視窗寬度小於 1120px 的時候這個圖片要加載 924px 寬度的,在視窗寬度小於 1280px 大於 1120px 的時候要加載 1040px 的圖片,視窗大於 1280px 的加載和視窗寬度相同的圖片。

    瀏覽器將使用這些屬性來進一步為用戶選擇合適的圖片源,你可以選擇增加更多的斷點和不同寬度下圖片的加載列表(但這會顯著的增加布局內的信息),或者你也可以保持相對簡單。關鍵是現在瀏覽器知道了關於圖片的更多信息,它將在不同的情況下選擇最佳的圖片源。

    藝術指導(ART-DIRECTION USE CASE)

    大多數情況下 srcset 和 sizes 都已經夠用了。但是仍然有時候你需要根據不同的尺寸修改圖片的內容。例如使用類似於 srcset 和 sizes 標準的 <picture> 標籤。關於 <picture> 的更多信息可以了解 Jason Grigsby’s article (這篇文章其實說的大多數情況下不要用 picture,個人認為其實不同的圖片內容也可依賴 srcset,picture 過於複雜。並且兼容性上 picture 不如 srcset )。

    瀏覽器兼容

    srcset 和 sizes的瀏覽器兼容性 已經獲得了更多的支持,但對於大多數項目你需要更多的考慮兼容。幸運的是 Picturefill 是一個很棒的支持跨瀏覽器的兼容庫,它支持 <picture> 和 srcset 以及 sizes 特性。這個兼容庫允許你使用推薦的 <picture> 語法,因此可以在瀏覽器兼容性沒有問題的時候移除該庫。雖然 Picturefill 還有一些缺點,但這麼做仍然利大於弊。

    注意 在使用 picturefill 庫的時候,作者推薦拋棄使用 src 標籤,因為這將 導致圖片的重複下載。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10018

    沐鳴登錄測速_JS判斷單、多張圖片加載完成

    在實際的運用中有這樣一種場景,某資源加載完成后再執行某個操作,例如在做導出時,後端通過打開模板頁生成PDF,並返回下載地址。這時前後端通常需要約定一個flag,用以標識模板準備就緒,可以生成PDF了。

    試想,如果模板中有圖片,此時如何判斷圖片是否加載完成?

    在此之前來了解一下jquery的ready與window.onload的區別,ready只是dom結構加載完畢,便視為加載完成。(此時圖片沒有加載完畢),onload是指dom的生成和資源完全加載(比如flash、圖片)出來后才執行。接下來回到正題,先從單張圖片說起。

    (1)、單張圖片(圖片在文檔中)

    // html
    <img id='xiu' src="http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg">  
    
    //js
     $(document).ready(function(){
    
        //jquery
        $('#xiu').load(function(){
           // 加載完成 
        });
    
       //原生  onload
        var xiu = document.getElementById('xiu')
        xiu.onload = xiu.onreadystatechange = function(){
           if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'){
               // 加載完成 
           }
        };
    
    })

    注:
    1、IE8及以下版本不支持onload事件,但支持onreadystatechange事件;
    2、readyState是onreadystatechange事件的一個狀態,值為loaded或complete的時候,表示已經加載完畢。
    3、以下內容省略兼容

    (2)、單張圖片(圖片動態生成)

    //js
     var xiu = new Image()
     xiu.src = 'http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg'
     xiu.onload = function(){
        // 加載完成 
     }

    (3)、單張圖片(結合ES6 Promise)

    //js
     new Promise((resolve, reject)=>{
        let xiu = new Image()
        xiu.src = 'http://www.daqianduan.com/wp-content/uploads/2014/11/hs-xiu.jpg'
        xiu.onload = function(){
           // 加載完成 
           resolve(xiu)
        }
     }).then((xiu)=>{
     //code
     })

    (4)、多張圖片

    var img = [],  
        flag = 0, 
        mulitImg = [
        'http://www.daqianduan.com/wp-content/uploads/2017/03/IMG_0119.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2017/01/1.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2015/11/jquery.jpg',
        'http://www.daqianduan.com/wp-content/uploads/2015/10/maid.jpg'
     ];
     var imgTotal = mulitImg.length;
     for(var i = 0 ; i < imgTotal ; i++){
        img[i] = new Image()
        img[i].src = mulitImg[i]
        img[i].onload = function(){
           //第i張圖片加載完成
           flag++
           if( flag == imgTotal ){
              //全部加載完成
           }
        }
     }

    (5)、多張圖片(結合ES6 Promise.all())

      let mulitImg = [
         'http://www.daqianduan.com/wp-content/uploads/2017/03/IMG_0119.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2017/01/1.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2015/11/jquery.jpg',
         'http://www.daqianduan.com/wp-content/uploads/2015/10/maid.jpg'
     ];
     let promiseAll = [], img = [], imgTotal = mulitImg.length;
     for(let i = 0 ; i < imgTotal ; i++){
         promiseAll[i] = new Promise((resolve, reject)=>{
             img[i] = new Image()
             img[i].src = mulitImg[i]
             img[i].onload = function(){
                  //第i張加載完成
                  resolve(img[i])
             }
         })
     }
     Promise.all(promiseAll).then((img)=>{
         //全部加載完成
     })

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/10016