沐鳴娛樂怎麼樣?_關於javascript中的promise的用法和注意事項

一、promise描述

promise是JavaScript中標準的內置對象,用於表示一個異步操作的最終狀態(是失敗還是成功完成)及其結果值。它讓你能夠把異步操作最終成功或者失敗的原因和響應的處理程序相關聯,也就是說通過promise你可以自定義異步操作結束后該做什麼。這樣的話異步方法就和同步方法很類似,也有返回值,只不過這個返回值不是立即返回最終的值,而是返回一個promise,當promise的狀態發生改變時,就會觸發相應的處理程序。

一個promise無論什麼時候都必然處於以下幾種狀態:1、待定(pending) 2、已兌現(fulfilled) 3、已拒絕(rejected)

promise創建之初是待定狀態,它既咩有被兌現也咩有被拒絕,它用於包裝還沒有被添加promise支持的函數,包裝后的函數變成異步操作函數,當操作結束時,會有兩個兩個分支,一個是已兌現,一個是已拒絕,如下圖。已兌現狀態的promise會調用後續的then方法,而已拒絕狀態的promise既可以調用then方法,也可以被catch方法捕捉到。為什麼then方法可以捕捉兩個狀態的promise呢?這裏先不講,下面再詳細介紹。

二、promise 的流程走向

上圖是promise的流程圖,從左到右可以得知一個promise在待定狀態時通過兩個分支走向分別進入then方法或者catch方法,這兩個c方法都會返回promise,如果這個promise也被敲定了,而且其後也有then方法或者catch方法,則又會進入後續的then和catch中,直至咪有。

也就是說,promise和then或者catch可以形成一個異步操作的鏈式結構,結合js的事件循環機制,這使得js的優勢更加明顯,發展至今仍能在瀏覽器上頻繁看到它的身影。

我們要注意流程的特點:

1、promise是對象,它的轉移成功不取決於返回值,而取決於狀態的敲定(是成功還是失敗)

2、then和catch是函數,只要有promise狀態發生了改變,該promise對應後面的then或者catch方法就會被調用,而這兩個方法本身會返回一個promise,這個返回的promise又會影響這兩個方法後面的then或者catch方法。兩個方法的返回值一定是promise。

三、promise的創建

Promise 對象是由關鍵字 new 及其構造函數來創建的。該構造函數會把一個叫做“處理器函數”(executor function)的函數作為它的參數。這個“處理器函數”接受兩個函數——resolve 和 reject ——作為其參數。當異步任務順利完成且返回結果值時,會調用 resolve 函數;而當異步任務失敗且返回失敗原因(通常是一個錯誤對象)時,會調用reject 函數。示例如下:

let myFirstPromise = new Promise(function(resolve, reject){
    //當異步代碼執行成功時,我們才會調用resolve(...), 當異步代碼失敗時就會調用reject(...)
    //在本例中,我們使用setTimeout(...)來模擬異步代碼,實際編碼時可能是XHR請求或是html5的一些API方法.
    setTimeout(function(){
        resolve("成功!"); //代碼正常執行!
    }, 250);
});

myFirstPromise.then(function(successMessage){
    //successMessage的值是上面調用resolve(...)方法傳入的值.
    //successMessage參數不一定非要是字符串類型,這裏只是舉個例子
    console.log("Yay! " + successMessage);
});

四、promise的優勢

在promise未出現時,如果我們調用異步代碼塊的話是沒有辦法保持順序的,而如果又出現了異步代碼結果之間按序的需求,該如何實現呢?

以前的話通常都會將異步代碼一層一層地內嵌來實現異步代碼按序實現,但是這就造成了代碼維護困難,開發難度增大

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

這就是經典的回調地獄。而如果使用了前面介紹的promise,那麼代碼就變成了容易維護的鏈式結構

五、then方法返回的promise類型

當一個 Promise 完成(fulfilled)或者失敗(rejected)時,返回函數將被異步調用(由當前的線程循環來調度完成)。具體的返回值依據以下規則返回。如果 then 中的回調函數:

返回了一個值,那麼 then 返回的 Promise 將會成為接受狀態,並且將返回的值作為接受狀態的回調函數的參數值。

沒有返回任何值,那麼 then 返回的 Promise 將會成為接受狀態,並且該接受狀態的回調函數的參數值為 undefined。

拋出一個錯誤,那麼 then 返回的 Promise 將會成為拒絕狀態,並且將拋出的錯誤作為拒絕狀態的回調函數的參數值。

返回一個已經是接受狀態的 Promise,那麼 then 返回的 Promise 也會成為接受狀態,並且將那個 Promise 的接受狀態的回調函數的參數值作為該被返回的Promise的接受狀態回調函數的參數值。

返回一個已經是拒絕狀態的 Promise,那麼 then 返回的 Promise 也會成為拒絕狀態,並且將那個 Promise 的拒絕狀態的回調函數的參數值作為該被返回的Promise的拒絕狀態回調函數的參數值。

返回一個未定狀態(pending)的 Promise,那麼 then 返回 Promise 的狀態也是未定的,並且它的終態與那個 Promise 的終態相同;同時,它變為終態時調用的回調函數參數與那個 Promise 變為終態時的回調函數的參數是相同的。

 六、catch捕捉的錯誤

catch能捕捉promise組合中出現的錯誤,但是有兩種錯誤不能捕捉:

1、已決議的錯誤不能捕捉

//創建一個新的 Promise ,且已決議
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //這個方法永遠不會調用
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});

2、異步函數中拋出的錯誤不能捕捉

需要注意的是,作者親手實踐發現:promise包裹的異步函數執行成功后必須顯式地調用resolve和reject方法才能觸發後續then和catch方法,如果promise方法包裹的不是異步函數,是普通的同步函數,如果該同步代碼運行出錯,即便沒有調用reject方法時,後面的catch方法仍然能夠捕捉到這個錯誤,但如果同步代碼沒有出錯,沒有顯式調用resolve方法轉移的話,後續的then方法不會觸發。

七、高級示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 10px;
        }
        html{
            width: 100%;
            height: 100%;
        }
        body{
            width: 100%;
            height: 100%;
            display:flex;
            align-items: center;
            justify-content: center;
        }
        div.displaydatabox{
            width: 300px;
            height: 300px;
            border-radius: 50px;
            text-align: center;
            line-height: 300px;
            box-shadow: 0 0 10px 2px black;
        }
        div.button{
            width: 100px;
            height: 50px;
            border-radius: 21px;
            border: 2px solid orange;
            line-height: 50px;
            text-align: center;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="button">創建</div>
    <div class="button">輸入文字</div>
    <div class="button">消失</div>
    <script lang="JavaScript">
        let buttonlist=document.querySelectorAll("div.button");
        let body=document.querySelector("body");
        buttonlist[0].onclick=function()
        {
            let div=document.createElement("div");
            div.className="displaydatabox";
            body.appendChild(div);
        }
        buttonlist[2].onclick=function()
        {
            let div=document.querySelector("div.displaydatabox");
            body.removeChild(div);
        }
        buttonlist[1].onclick=function(e)
        {
            let p1=new Promise((resolve,reject)=>
            {
                setTimeout(()=>{//用setTimeout函數模擬異步函數
                    let div=document.querySelector("div.displaydatabox");
                    div.textContent="這是promise實驗";
                    //reject(1);
                    resolve(1);//調用resolve將調用第一個then
                },2000);
            }).then(function(resolve){
                return new Promise((resolve,reject)=>{
                    console.log("這是狀態咩有敲定的promise,所以不會調用後面所有的then方法");
            //resolve(1)//沒有調用resolve或者reject,所以狀態未敲定。如果調用,將輸出1和finally it has been called!! }) .then(function(e){ console.log(e); }); }).catch(function(e) { console.log(e+" 沒有塊可以輸入!!"); }).then(()=> { console.log("finally it has been called!!"); }) } </script> </body> </html>

 

站長推薦

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

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

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

沐鳴首頁_jQuery不同版本的差異對比

jQuery 一共分了 1.x、2.x、3.x 這三個大版本。

jQuery 的版本都是不向後兼容的! jQuery 的版本都是不向後兼容的! jQuery 的版本都是不向後兼容的!重要的事情說三遍哈

一、1.x、2.x、3.x 三大系列的區別

1,IE 的支持情況比較

(1)情況分析

1.x版本:兼容ie678,使用最為廣泛的,官方只做bug維護,功能不再新增。因此一般項目來說,使用1.x版本就可以了,最終版本:1.12.4

2.x版本:不兼容ie678,很少人使用,官方只做bug維護,功能不再新增。如果不考慮兼容版本低的瀏覽器可以使用2.x,最終版本:2.2.4

3.x版本:不兼容ie678,只支持最新的瀏覽器。除非特殊要求,一般不會使用3.x版本的,很多老的jQuery插件不支持這個版本。目前該版本是官方主要更新維護的版本。截至2018年6月13日,最新版本:3.3.1

(2)選擇建議

如果需要兼容 ie678:只能選擇 1.x

如果不需要兼容 ie678:可以選擇 2.x、3.x。因為 1.x 中有大部分代碼是對老舊瀏覽器做的兼容,這個就增加了運行的負擔,影響了運行效率。

2,新特性比較

(1)2.x 相較於 1.x 沒有增加什麼新特性,主要是去除了 ie678 的支持,提升了性能,減小了體檢。 (2)3.x 相較於之前版本,增加了許多新特性,也改變一些以往的特性。  

二、具體版本建議

1,版本歷史

版本號 發布日期 最新更新 大小(KB) 備註
1.0 2006年8月26日     第一個穩定版本
1.1 2007年1月14日      
1.2 2007年9月10日 1.2.6 54  
1.3 2009年1月14日 1.3.2 55.9 將 Sizzle 選擇器引擎引入核心
1.4 2010年1月14日 1.4.4 76  
1.5 2011年1月31日 1.5.2 83 延遲回調管理,ajax 模塊重寫
1.6 2011年5月3日 1.6.4 89 顯著改善 attr() 與 val() 的性能
1.7 2011年11月3日 1.7.2 (2012年3月21日) 92 新的事件 API:.on() 和 .off(),而舊的 API 仍然支持。
1.8 2012年8月9日 1.8.3 (2012年11月13日) 91.4 重寫 Sizzle 選擇器引擎,改善動畫和 $(html, props) 的靈活性。
1.9 2013年1月15日 1.9.1 (2013年2月4日) 90 移除棄用接口,清理代碼
1.10 2013年5月24日 1.10.2 (2013年7月3日) 91 修復了 1.9 和 2.0 beta 版本周期的 bug 和差異
1.11 2014年1月24日 1.11.3 (2015年4月28日) 95.9  
1.12 2016年1月8日 1.12.4 (2016年5月20日) 95  
2.0 2013年4月18日 2.0.3 (2013年7月3日) 81.1 除去對 IE 6-8 的支持以提高性能,並降低文件大小
2.1 2014年1月24日 2.1.4 (2015年4月28日) 82.4  
2.2 2016年1月8日 2.2.4 (2016年5月20日) 85.6  
3.0 2016年6月9日 3.0.0 (2016年6月9日) 86.3 Deferred、$.ajax、$.when 支持 Promises/A+,令 .data() 兼容html5
3.1 2016年7月7日 3.1.1 (2016年9月23日) 86.3 加入 jQuery.readyException,ready handler 錯誤現在不會不显示了
3.2 2017年3月16日 3.2.1 (2017年3月20日) 84.6 增加了對檢索<template>元素內容的支持,棄用了多種舊方法。
3.3 2018年1月19日 3.3.1 (2018年1月20日) 84.8 棄用舊函數,函數現在可以接受類,並支持其寫成數組格式。

 

2,1.x 常用版本

1.4.2:穩定性和兼容性都很出色,插件最多,但性能不如下面後面的幾個版本。

1.7.2:性能提升,插件第二多,ajax 和 attr 等 api 有少許修改。

1.8.3:最後一個支持 IE6 的穩定版

1.9.1:開始移除了不少方法,事件綁定推薦使用 on 方法一個代替所有的。

1.12.4:1.x 時代最後一個穩定版本,僅支持 IE8,不支持 IE6/7。

 

3,2.x、3.x 版本

除非有特殊要求(比如面向移動端),一般情況下這兩大版本使用人的確很少:

2.x 最後一個穩定版本:2.2.4

3.x 最新版本:3.3.1

站長推薦

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

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

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

杏耀註冊平台官網_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

沐鳴登錄_開發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

沐鳴總代平台_前端中的 hash 和 history 路由

前端路由有有 hash 路由和 history 路由兩種路由方式,他們的原理是什麼,又怎樣實現一個簡單的路由監聽呢?

我們在使用 vue 或者 react 等前端渲染時,通常會有 hash 路由和 history 路由兩種路由方式。

  1. hash 路由:監聽 url 中 hash 的變化,然後渲染不同的內容,這種路由不向服務器發送請求,不需要服務端的支持;

  2. history 路由:監聽 url 中的路徑變化,需要客戶端和服務端共同的支持;

我們一步步實現這兩種路由,來深入理解下底層的實現原理。我們主要實現以下幾個簡單的功能:

  1. 監聽路由的變化,當路由發生變化時,可以作出動作;

  2. 可以前進或者後退;

  3. 可以配置路由;

1. hash 路由

當頁面中的 hash 發生變化時,會觸發hashchange事件,因此我們可以監聽這個事件,來判斷路由是否發生了變化。

window.addEventListener(
    'hashchange',
    function (event) {
        const oldURL = event.oldURL; // 上一個URL
        const newURL = event.newURL; // 當前的URL
        console.log(newURL, oldURL);
    },
    false
);

1.1 實現的過程

對 oldURL 和 newURL 進行拆分后,就能獲取到更詳細的 hash 值。我們這裏從創建一個 HashRouter 的 class 開始一步步寫起:

class HashRouter {
    currentUrl = ''; // 當前的URL
    handlers = {};

    getHashPath(url) {
        const index = url.indexOf('#');
        if (index >= 0) {
            return url.slice(index + 1);
        }
        return '/';
    }
}

事件hashchange只會在 hash 發生變化時才能觸發,而第一次進入到頁面時並不會觸發這個事件,因此我們還需要監聽load事件。這裏要注意的是,兩個事件的 event 是不一樣的:hashchange 事件中的 event 對象有 oldURL 和 newURL 兩個屬性,但 load 事件中的 event 沒有這兩個屬性,不過我們可以通過 location.hash 來獲取到當前的 hash 路由:

class HashRouter {
    currentUrl = ''; // 當前的URL
    handlers = {};

    constructor() {
        this.refresh = this.refresh.bind(this);
        window.addEventListener('load', this.refresh, false);
        window.addEventListener('hashchange', this.refresh, false);
    }

    getHashPath(url) {
        const index = url.indexOf('#');
        if (index >= 0) {
            return url.slice(index + 1);
        }
        return '/';
    }

    refresh(event) {
        let curURL = '',
            oldURL = null;
        if (event.newURL) {
            oldURL = this.getHashPath(event.oldURL || '');
            curURL = this.getHashPath(event.newURL || '');
        } else {
            curURL = this.getHashPath(window.location.hash);
        }
        this.currentUrl = curURL;
    }
}

到這裏已經可以實現獲取當前的 hash 路由,但路由發生變化時,我們的頁面應該進行切換,因此我們需要監聽這個變化:

class HashRouter {
    currentUrl = ''; // 當前的URL
    handlers = {};

    // 暫時省略上面的代碼

    refresh(event) {
        // 當hash路由發生變化時,則觸發change事件
        this.emit('change', curURL, oldURL);
    }

    on(evName, listener) {
        this.handlers[evName] = listener;
    }
    emit(evName, ...args) {
        const handler = this.handlers[evName];
        if (handler) {
            handler(...args);
        }
    }
}
const router = new HashRouter();
rouer.on('change', (curUrl, lastUrl) => {
    console.log('當前的hash:', curUrl);
    console.log('上一個hash:', lastUrl);
});

1.2 調用的方式

到這裏,我們把基本的功能已經完成了。來配合一個例子就更形象了:

// 先定義幾個路由
const routes = [
    {
        path: '/',
        name: 'home',
        component: <Home />,
    },
    {
        path: '/about',
        name: 'about',
        component: <About />,
    },
    {
        path: '*',
        name: '404',
        component: <NotFound404 />,
    },
];
const router = new HashRouter();
// 監聽change事件
router.on('change', (currentUrl, lastUrl) => {
    let route = null;
    // 匹配路由
    for (let i = 0, len = routes.length; i < len; i++) {
        const item = routes[i];
        if (currentUrl === item.path) {
            route = item;
            break;
        }
    }
    // 若沒有匹配到,則使用最後一個路由
    if (!route) {
        route = routes[routes.length - 1];
    }
    // 渲染當前的組件
    reactDOM.render(route.component, document.getElementById('app'));
});

查看【hash 路由的樣例】。

2. history 路由

在 history 路由中,我們一定會使用window.history中的方法,常見的操作有:

  • back():後退到上一個路由;

  • forward():前進到下一個路由,如果有的話;

  • go(number):進入到任意一個路由,正數為前進,負數為後退;

  • pushState(obj, title, url):前進到指定的 URL,不刷新頁面;

  • replaceState(obj, title, url):用 url 替換當前的路由,不刷新頁面;

調用這幾種方式時,都會只是修改了當前頁面的 URL,頁面的內容沒有任何的變化。但前 3 個方法只是路由歷史記錄的前進或者後退,無法跳轉到指定的 URL;而pushState和replaceState可以跳轉到指定的 URL。如果有面試官問起這個問題“如何僅修改頁面的 URL,而不發送請求”,那麼答案就是這 5 種方法。

如果服務端沒有新更新的 url 時,一刷新瀏覽器就會報錯,因為刷新瀏覽器后,是真實地向服務器發送了一個 http 的網頁請求。因此若要使用 history 路由,需要服務端的支持。

2.1 應用的場景

pushState 和 replaceState 兩個方法跟 location.href 和 location.replace 兩個方法有什麼區別呢?應用的場景有哪些呢?

  1. location.href 和 location.replace 切換時要向服務器發送請求,而 pushState 和 replace 僅修改 url,除非主動發起請求;

  2. 僅切換 url 而不發送請求的特性,可以在前端渲染中使用,例如首頁是服務端渲染,二級頁面採用前端渲染;

  3. 可以添加路由切換的動畫;

  4. 在瀏覽器中使用類似抖音的這種場景時,用戶滑動切換視頻時,可以靜默修改對應的 URL,當用戶刷新頁面時,還能停留在當前視頻。

2.2 無法監聽路由的變化

當我們用 history 的路由時,必然要能監聽到路由的變化才行。全局有個popstate事件,別看這個事件名稱中有個 state 關鍵詞,但pushState和replaceState被調用時,是不會觸發觸發 popstate 事件的,只有上面列舉的前 3 個方法會觸發。可以點擊【popState 不會觸發 popstate 事件】查看。

針對這種情況,我們可以使用window.dispatchEvent添加事件:

const listener = function (type) {
    var orig = history[type];
    return function () {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
window.history.pushState = listener('pushState');
window.history.replaceState = listener('replaceState');

然後就可以添加對這兩個方法的監聽了:

window.addEventListener('pushState', this.refresh, false);
window.addEventListener('replaceState', this.refresh, false);

2.3 完整的代碼

完整的代碼如下:

class HistoryRouter {
    currentUrl = '';
    handlers = {};

    constructor() {
        this.refresh = this.refresh.bind(this);
        this.addStateListener();
        window.addEventListener('load', this.refresh, false);
        window.addEventListener('popstate', this.refresh, false);
        window.addEventListener('pushState', this.refresh, false);
        window.addEventListener('replaceState', this.refresh, false);
    }
    addStateListener() {
        const listener = function (type) {
            var orig = history[type];
            return function () {
                var rv = orig.apply(this, arguments);
                var e = new Event(type);
                e.arguments = arguments;
                window.dispatchEvent(e);
                return rv;
            };
        };
        window.history.pushState = listener('pushState');
        window.history.replaceState = listener('replaceState');
    }
    refresh(event) {
        this.currentUrl = location.pathname;
        this.emit('change', location.pathname);
        document.querySelector('#app span').innerhtml = location.pathname;
    }
    on(evName, listener) {
        this.handlers[evName] = listener;
    }
    emit(evName, ...args) {
        const handler = this.handlers[evName];
        if (handler) {
            handler(...args);
        }
    }
}
const router = new HistoryRouter();
router.on('change', function (curUrl) {
    console.log(curUrl);
});

使用方法與上面的 hash 路由一樣,這裏就不多贅述了。點擊查看【history 路由的實現應用】

我們騰訊新聞中的搶金達人活動,就是採用的這種路由方式,頁面的首次渲染採用服務端直出,二級跳轉頁面,使用前端 history 路由的前端渲染方式。

3. 總結

至此,兩種路由的原理和實現方式都介紹完畢了,歡迎拍磚。

來自:https://www.xiabingbao.com/post/fe/hash-history-router.html

站長推薦

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

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

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

沐鳴平台網站_巧用CSS3的calc()寬度計算做響應模式布局

calc()從字面我們可以把他理解為一個函數function。其實calc是英文單詞calculate(計算)的縮寫,是css3的一個新增的功能,用來指定元素的長度。比如說,你可以使用calc()給元素的border、margin、pading、font-size和width等屬性設置動態值。為何說是動態值呢?因為我們使用的表達式來得到的值。不過calc()最大的好處就是用在流體布局上,可以通過calc()計算得到元素的寬度。

calc()能做什麼?
calc()能讓你給元素的做計算,你可以給一個div元素,使用百分比、em、px和rem單位值計算出其寬度或者高度,比如說“width:calc(50% + 2em)”,這樣一來你就不用考慮元素div的寬度值到底是多少,而把這個煩人的任務交由瀏覽器去計算。

calc()語法
calc()語法非常簡單,就像我們小時候學加 (+)、減(-)、乘(*)、除(/)一樣,使用數學表達式來表示:

width: calc(expression);

其中”expression”是一個表達式,用來計算長度的表達式。

calc()的運算規則
calc()使用通用的數學運算規則,但是也提供更智能的功能:

  1. 使用“+”、“-”、“*” 和 “/”四則運算;
  2. 可以使用百分比、px、em、rem等單位;
  3. 可以混合使用各種單位進行計算;
  4. 表達式中有“+”和“-”時,其前後必須要有空格,如”widht: calc(12%+5em)”這種沒有空格的寫法是錯誤的;
  5. 表達式中有“*”和“/”時,其前後可以沒有空格,但建議留有空格。

瀏覽器的兼容性

我們來個例子,我們做一個三列並排的模塊,寬度按百分比、有padding值、有border值、還有margin-right,而且這三個值是px,

li{
float:left;

width:33.3333%;
height:50px;

padding:10px;
margin-right:10px;

background:#FF6666;
border:5px solid #DAC8A7;
}

效果圖:

它是不會好好並列的,在這種情況下就不好算了,就算算出來也有那麼一點誤差,不是嗎?現在我們就用到了calc(),

li{
float:left;

//width:33.3333%;
height:50px;

padding:10px;
margin-right:15px;

background:#FF6666;
border:5px solid #DAC8A7;

width:calc(33.3333% - (10px + 5px) * 2 - 15px )

}

意思是(width-(padding+border)*2-margin)
現在可以並排了

好了,到這就告一段絡了,再稍微優化一下左右邊15px的空距,讓兩邊都挨邊。就在父級上加個margin-right:-15px,OK 搞定,

現在拿這個去做響應模式應該很方便了

站長推薦

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

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

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

沐鳴怎麼當代理?_網頁打印css print

最近做表單打印,遂整理了一些打印相關的內容。

說到網頁打印,首先想到的便是@media查詢(即網頁css),通過使用媒體類型print即可解決實際應用的大多數問題,比如實現只打印網頁的某部分內容,調整字體大小、修改布局等使打印出來的紙質文件更簡潔明了。代碼如下:

@media print{

    /*隱藏不打印的元素*/
    .no-print{
        display:none;
    }
    /*其他打印樣式*/
}

但是,就打印表單來說,僅解決上述問題是不夠的,我們無法忍受表單存在打印分頁時內容被截斷、分頁显示頂部沒有留白等問題。那麼如何解決這些問題呢?這就要使用到css打印樣式了,即@page,用來指定頁面盒子的各個方面。

eg1:尺寸、頁邊距設置

@page{
     size: 5.5in 8.5in;       
     margin: 30px;
}

注:這裏除了可以用長度值聲明尺寸,還可使用紙質尺寸關鍵字”A4″或“legal”;亦可通過關鍵字指定頁面方向“portrait”、“landscape”,默認為portrait垂直方向。當margin設置不起作用時檢查打印機是否邊距是否設置了默認以外的值。

eg2:設置第一頁、奇數頁或偶數頁樣式(以首頁為例)

@page :first { 

    /*首頁設置*/

}

注:left、right分別為偶數頁、奇數頁選擇器。

eg3:避免表格斷開

@page{
    table{
        page-break-after: avoid;
    }  
}

注:page-break-after對tr、td不起作用所以當以整體出現的時候要在同一個table中。

eg4:避免某行文字斷裂

@page{
    table{
        page-break-inside: avoid;
    }  
}

站長推薦

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

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

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