沐鳴娛樂業務:_try..catch 不能捕獲的錯誤有哪些?注意事項又有哪些?

今天的內容中,我們來學習一下使用try、catch、finally和throw進行錯誤處理。我們還會講一下 js 中內置的錯誤對象(Error, SyntaxError, ReferenceError等)以及如何定義自定義錯誤。

1.使用 try..catch..finally..throw

在 js 中處理錯誤,我們主要使用try、catch、finally和throw關鍵字。

  • try塊包含我們需要檢查的代碼
  • 關鍵字throw用於拋出自定義錯誤
  • catch塊處理捕獲的錯誤
  • finally 塊是最終結果無論如何,都會執行的一個塊,可以在這個塊裏面做一些需要善後的事情

1.1 try

每個try塊必須與至少一個catch或finally塊,否則會拋出SyntaxError錯誤。

我們單獨使用try塊進行驗證:

try {
  throw new Error('Error while executing the code');
}
ⓧ Uncaught SyntaxError: Missing catch or finally after try

1.2 try..catch

建議將try與catch塊一起使用,它可以優雅地處理try塊拋出的錯誤。

try {
  throw new Error('Error while executing the code');
} catch (err) {
  console.error(err.message);
}
Error while executing the code

1.2.1 try..catch 與 無效代碼

try..catch 無法捕獲無效的 JS 代碼,例如try塊中的以下代碼在語法上是錯誤的,但它不會被catch塊捕獲。

try {
  ~!$%^&*
} catch(err) {
  console.log("這裏不會被執行");
}
  Uncaught SyntaxError: Invalid or unexpected token

1.2.2 try..catch 與 異步代碼

同樣,try..catch無法捕獲在異步代碼中引發的異常,例如setTimeout:

try {
  setTimeout(function() {
    noSuchvariable;   // undefined variable
  }, 1000);
} catch (err) {
  console.log("這裏不會被執行");
}

未捕獲的ReferenceError將在1秒后引發:

Uncaught ReferenceError: noSuchVariable is not defined

所以 ,我們應該在異步代碼內部使用 try..catch 來處理錯誤:

setTimeout(function() {
  try {
    noSuchVariable;
  } catch(err) {
    console.log("error is caught here!");
  }
}, 1000);

1.2.3 嵌套 try..catch

我們還可以使用嵌套的try和catch塊向上拋出錯誤,如下所示:

try {
  try {
    throw new Error('Error while executing the inner code');
  } catch (err) {
    throw err;
  }
} catch (err) {
  console.log("Error caught by outer block:");
  console.error(err.message);
}
Error caught by outer block:
 ⓧ Error while executing the code

1.3 try..finally

不建議僅使用 try..finally 而沒有 catch 塊,看看下面會發生什麼:

try {
  throw new Error('Error while executing the code');
} finally {
  console.log('finally');
}
finally
 ⓧ Uncaught Error: Error while executing the code

這裏注意兩件事:

  • 即使從try塊拋出錯誤后,也會執行finally塊
  • 如果沒有catch塊,錯誤將不能被優雅地處理,從而導致未捕獲的錯誤

1.4 try..catch..finally

建議使用try…catch塊和可選的finally塊。

try {
  console.log("Start of try block");
  throw new Error('Error while executing the code');
  console.log("End of try block -- never reached");
} catch (err) {
  console.error(err.message);
} finally {
  console.log('Finally block always run');
}
console.log("Code execution outside try-catch-finally block continue..");
Start of try block
 ⓧ Error while executing the code
Finally block always run
Code execution outside try-catch-finally block continue..

這裏還要注意兩件事:

  • 在try塊中拋出錯誤后往後的代碼不會被執行了
  • 即使在try塊拋出錯誤之後,finally塊仍然執行

finally塊通常用於清理資源或關閉流,如下所示:

try {
  openFile(file);
  readFile(file);
} catch (err) {
  console.error(err.message);
} finally {
  closeFile(file);
}

1.5 throw

throw語句用於引發異常。

throw <expression>
// throw primitives and functions
throw "Error404";
throw 42;
throw true;
throw {toString: function() { return "I'm an object!"; } };

// throw error object
throw new Error('Error while executing the code');
throw new SyntaxError('Something is wrong with the syntax');
throw new ReferenceError('Oops..Wrong reference');

// throw custom error object
function ValidationError(message) {
  this.message = message;
  this.name = 'ValidationError';
}
throw new ValidationError('Value too high');

2. 異步代碼中的錯誤處理

對於異步代碼的錯誤處理可以Promise和async await。

2.1 Promise 中的 then..catch

我們可以使用then()和catch()鏈接多個 Promises,以處理鏈中單個 Promise 的錯誤,如下所示:

Promise.resolve(1)
  .then(res => {
      console.log(res);  // 打印 '1'

      throw new Error('something went wrong');  // throw error

      return Promise.resolve(2);  // 這裏不會被執行
  })
  .then(res => {
      // 這裏也不會執行,因為錯誤還沒有被處理
      console.log(res);    
  })
  .catch(err => {
      console.error(err.message);  // 打印 'something went wrong'
      return Promise.resolve(3);
  })
  .then(res => {
      console.log(res);  // 打印 '3'
  })
  .catch(err => {
      // 這裏不會被執行
      console.error(err);
  })

我們來看一個更實際的示例,其中我們使用fetch調用API,該 API 返回一個promise對象,我們使用catch塊優雅地處理 API 失敗。

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}

fetch("http://httpstat.us/500")
    .then(handleErrors)
    .then(response => console.log("ok"))
    .catch(error => console.log("Caught", error));
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)

2.2 try..catch 和 async await

在 async await 中 使用 try..catch 比較容易:

(async function() {
    try {
        await fetch("http://httpstat.us/500");
    } catch (err) {
        console.error(err.message);
    }
})();

讓我們看同一示例,其中我們使用fetch調用API,該API返回一個promise對象, 我們使用try..catch塊優雅地處理API失敗。

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
}

(async function() {
    try {
      let response = await fetch("http://httpstat.us/500");
      handleErrors(response);
      let data = await response.json();
      return data;
    } catch (error) {
        console.log("Caught", error)
    }
})();
Caught Error: Internal Server Error
    at handleErrors (<anonymous>:3:15)
    at <anonymous>:11:7

3. JS 中的內置錯誤

3.1 Error

JavaScript 有內置的錯誤對象,它通常由try塊拋出,並在catch塊中捕獲,Error 對象包含以下屬性:

  • name:是錯誤的名稱,例如 “Error”, “SyntaxError”, “ReferenceError” 等。
  • message:有關錯誤詳細信息的消息。
  • stack:是用於調試目的的錯誤的堆棧跟蹤。

我們創建一個Error 對象,並查看它的名稱和消息屬性:

const err = new Error('Error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
console.log("stack:", err.stack);
name: Error
message: Error while executing the code
stack: Error: Error while executing the code
    at <anonymous>:1:13

JavaScript 有以下內置錯誤,這些錯誤是從 Error 對象繼承而來的

3.2 EvalError

EvalError 表示關於全局eval()函數的錯誤,這個異常不再由 JS 拋出,它的存在是為了向後兼容。

3.3 RangeError

當值超出範圍時,將引發RangeError。

 [].length = -1
ⓧ Uncaught RangeError: Invalid array length

3.4 ReferenceError

當引用一個不存在的變量時,將引發 ReferenceError

 x = x + 1;
 Uncaught ReferenceError: x is not defined

3.5 SyntaxError

當你在 JS 代碼中使用任何錯誤的語法時,都會引發SyntaxError。

 function() { return 'Hi!' }
ⓧ Uncaught SyntaxError: Function statements require a function name  1 = 1 ⓧ Uncaught SyntaxError: Invalid left-hand side in assignment  JSON.parse("{ x }"); ⓧ Uncaught SyntaxError: Unexpected token x in JSON at position 2

3.6 TypeError

如果該值不是預期的類型,則拋出TypeError。

 1();
 Uncaught TypeError: 1 is not a function

 null.name;
 Uncaught TypeError: Cannot read property 'name' of null

3.7 URIError

如果以錯誤的方式使用全局 URI 方法,則會拋出URIError。

 decodeURI("%%%");
ⓧ Uncaught URIError: URI malformed

4. 定義並拋出自定義錯誤

我們也可以用這種方式定義自定義錯誤。

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = "CustomError";
  } 
};

const err = new CustomError('Custom error while executing the code');

console.log("name:", err.name);
console.log("message:", err.message);
name: CustomError
message: Custom error while executing the code

我們還可以進一步增強CustomError對象以包含錯誤代碼

class CustomError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "CustomError";
    this.code = code;
  } 
};

const err = new CustomError('Custom error while executing the code', "ERROR_CODE");

console.log("name:", err.name);
console.log("message:", err.message);
console.log("code:", err.code);
name: CustomError
message: Custom error while executing the code
code: ERROR_CODE

在try..catch塊中使用它:

try{
  try {
    null.name;
  }catch(err){
    throw new CustomError(err.message, err.name);  //message, code
  }
}catch(err){
  console.log(err.name, err.code, err.message);
}

CustomError TypeError Cannot read property ‘name’ of null

原文:https://codings.com/

站長推薦

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

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

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

沐鳴註冊平台_vue-cli4首次加載速度優化

GZIP壓縮

GZIP壓縮有動態跟靜態兩種
可以將由前端打包GZIP,通過 gzip_static:on會尋找以.gz結尾的文件,直接返回,不會佔用cpu進行壓縮,如果找不到則不進行壓縮

nginx

  #開啟和關閉gzip模式
    gzip on|off;
    
    #gizp壓縮起點,文件大於1k才進行壓縮
    gzip_min_length 1k;
    
    # gzip 壓縮級別,1-9,数字越大壓縮的越好,也越佔用CPU時間
    gzip_comp_level 5;
    
    # 進行壓縮的文件類型。
    gzip_types text/plain application/JavaScript application/x-JavaScript text/css application/xml text/javascript ;
    
    #nginx對於靜態文件的處理模塊,開啟後會尋找以.gz結尾的文件,直接返回,不會佔用cpu進行壓縮,如果找不到則不進行壓縮
    gzip_static on|off
    
    # 是否在http header中添加vary: Accept-Encoding,建議開啟
    gzip_vary on;

    # 設置壓縮所需要的緩衝區大小,以4k為單位,如果文件為7k則申請2*4k的緩衝區 
    gzip_buffers 2 4k;

    # 設置gzip壓縮針對的HTTP協議版本
    gzip_http_version 1.1;

vue.config.js

// npm i -D compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: {
/* 打包時開啟GZIP壓縮 */
  plugins: [
    new CompressionPlugin({
          algorithm: 'gzip', // 使用gzip壓縮
          test: /\.js$|\.html$|\.css$/, // 匹配文件名
          filename: '[path].gz[query]', // 壓縮后的文件名(保持原文件名,後綴加.gz)
          minRatio: 1, // 壓縮率小於1才會壓縮
          threshold: 10240, // 對超過10k的數據壓縮
          deleteOriginalAssets: false, // 是否刪除未壓縮的源文件,謹慎設置,如果希望提供非gzip的資源,可不設置或者設置為false(比如刪除打包后的gz后還可以加載到原始資源文件)
    }),
  ]
}

CDN

const isProd = process.env.NODE_ENV === 'production'
const cdn = {
  externals: {
    // 業務使用示例 => key:包名 value:庫文件對應的全局對象字符串=>可以沖源碼最後獲取
    vue: 'Vue',
    'vue-router': 'VueRouter',
    vuex: 'Vuex',
    axios: 'axios',
    'ant-design-vue': 'antd',
    'vue-ls': 'VueStorage',
  },
  // import的css不要放進來,externals無法過濾掉
  css: [],
  js: [
    '//unpkg.com/vue@2.6.12/dist/vue.min.js',
    '//unpkg.com/vue-router@3.4.9/dist/vue-router.min.js',
    '//unpkg.com/vuex@3.5.1/dist/vuex.min.js',
    '//unpkg.com/axios@0.21.0/dist/axios.min.js',
    '//unpkg.com/ant-design-vue@1.7.2/dist/antd.min.js',
    '//unpkg.com/vue-ls@3.2.1/dist/vue-ls.min.js',
  ]
};

configureWebpack: config => {
  config.externals = isProd ? cdn.externals : {}
},
chainWebpack: config => {
    config.plugin('html').tap(args => {
            // html中添加cdn
            args[0].cdn = cdn
            return args
          })
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title><%= htmlWebpackPlugin.options.title %></title>
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1825329_krt0759dhsh.css" />
  <!-- htmlWebpackPlugin.options.cdn 在vue.config.js內配置 -->
  <!-- 使用CDN的CSS文件 -->
  <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
  <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
  <% } %>
</head>
<body>
  <noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
      Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- 使用CDN的JS文件 -->
  <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
  <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  <% } %>
</body>
</html>

路由按需加載

使用 ()=>import(‘xxx’)按需加載路由組件,可以添加magic comments
打包後會單獨生成chunk.[hash].js文件,進一步減小主包大小

// Magic Comments 詳情查看下方鏈接
// https://webpack.docschina.org/api/module-methods/#magic-comments
{
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: "my-chunk-name" */'@/views/login/Login'),
    hidden: true,
  },

站長推薦

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

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

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

沐鳴主管:_Js中forEach跳出循環體

背景

在forEach中,不能使用 continue 和 break ,可以使用 return 或 return false 跳出循環,效果與 for 中 continue 一樣。注意該方法無法一次結束所有循環。

跳出本次循環

forEach 跳出本次循環,使用return

    [1,2,3,4,5].forEach(function(item,index){
        if(item == 3){
            return
        }
        console.log(3)// item == 3時,執行不到該部分,結束本次循環
    })

跳出整個循環

forEach 跳出整個循環,需要拋出異常

try {
    [1,2,3,4,5].forEach(function(item,index){
        if(item == 3){
            thorw new Error(); //結束整體循環
        }
    })
} catch(e) {

}

跳出嵌套循環

forEach 跳出嵌套循環

try {
    ["a","b","c"].forEach(function(item,index){

        try {
            [1,2,3,4,5].forEach(function(item,index){
                if(item == 3){
                    thorw new Error(); //結束整體循環
                }
            })
        } finally{}//try不能單獨存在

        <!--catch(e) {-->
            //內層的catch不能存在,不然會捕獲異常,只結束內層forEach
        <!--}-->

    })
} catch(e) { //在最外層捕獲異常,可結束嵌套循環

}

站長推薦

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

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

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

沐鳴代理_JavaScript中 ?

起因

最近在研究 cron 表達式和時間的互相轉化問題,然後突然看到了一個有意思的語法,於是就拿來研究了一下。

一、js 中神奇的 ?

說到 ? ,大家肯定都不陌生吧,立馬就能夠想到三元表達式,當然還有其它的語法,比如雙問號語法 ?? ,還有在正則表達式中。

1、三元表達式

 var flag = true;
var log = flag ? '真' : '假';
console.log(log);  //  真

大家都知道這個,就是指 ? 前面的表達式為真時會執行 : 前面的語句,否則執行 : 後面的,所以這個地方必定輸出為真。

2、??

有了前面提到的 第1點,我相信這一個很好理解。

說到雙問號語法,就不得不說 || 、 && 這兩個語法了,但是我相信大家都已經很清楚這兩個語法了。

無非就是邏輯運算 或 和 與。

var flag = true;
var log = flag || '邏輯或';
console.log(log);  //  true
var flag = false;
var log = flag || '邏輯或';
console.log(log);  //  邏輯或
var flag = null;
var log = flag || false;
console.log(log);  //  false

嗯…言簡意賅。  

var flag = true;
var log = flag && '邏輯與';
console.log(log);  //  邏輯與
var flag = false;
var log = flag && '邏輯與';
console.log(log);  //  false
var flag = null;
var log = flag && false;
console.log(log);  //  null

嗯…挺明顯的吧,短路操作。  

重頭戲來了,雙 ?? 。

var flag = true;
var log = flag ?? '??';
console.log(log);  //  true
var flag = false;
var log = flag ?? '??';
console.log(log);  //  false
var flag = undefined;
var log = flag ?? '??';
console.log(log);  //  ??
var flag = null;
var log = flag ?? '??';
console.log(log);  //  ??

咳咳,其實感覺和 || 很像,只不過只有在前面的表達式為 undefined 或者 null 時,才會執行後面的語句,否則不論前面的表達式為真還是為假都會執行前面的語句,而 || 的前面只要為假時就會執行後面的語句了。

二、?

如果你以為我只是想說這個,那你就大錯特錯了,因為這些老掉牙的都說過不知道多少遍了,大家都懂,但是下面要說的這個我之前是真的沒見過,可能是我孤陋寡聞吧。
提前尷尬一波。。。

?

大家在取數組或者對象中的值時是不是一定會用到

var arr = [0, 1, 2];
arr[0]; // 0
arr[1]; // 1

var obj = {a: 1, b: 2, c: 3};
obj['a']; // 1
obj['b']; // 2

諸如此類的方法。

但是如果在取值前 arr 或者 obj 為 undefined 或者 null 那麼將會報錯,
所以有了這神奇的操作。

var arr = [0, 1, 2];
arr?.[0]; // 0
arr?.[1]; // 1

var obj = {a: 1, b: 2, c: 3};
obj?.['a']; // 1
obj?.['b']; // 2

可以看到取值其實是一樣的,只不過當 arr 或者 obj 為 undefined 或者 null 時並不會報錯,而是返回 undefined , 如下圖。

來自:https://www.cnblogs.com/nyya/archive/2021/01/08/14251761.html

站長推薦

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

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

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

沐鳴平台首頁_必然會用到的 axios 中自帶的工具方法

在 axios 中,使用到了很多基礎的工具方法,這些方法我們也可以提煉下,看是否能應用到我們自己的實際項目中

1. 是否是絕對鏈接

所謂的絕對鏈接指的是有協議的鏈接,例如https://, weixin://, file://等,或者只有//開頭的鏈接,都屬於絕對鏈接,其他的則屬於相對鏈接。

/**
 * Determines whether the specified URL is absolute
 *
 * @param {string} url The URL to test
 * @returns {boolean} True if the specified URL is absolute, otherwise false
 */
module.exports = function isAbsoluteURL(url) {
    // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
    // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
    // by any combination of letters, digits, plus, period, or hyphen.
    return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
};

2. 將 baseURL 和 requestedURL 拼接成一個 URL

這裏通常 baseURL 是比較固定的,在寫配置時,可以只寫 requestedURL 部分,然後再將 baseURL 和 requestedURL 拼接成一個完成的 URL。

在完成這個功能之前,我們首先要實現將一個 url 合法的前後部分拼接起來的方法,這裏假定所有的傳入都是合法的,是因為在調用該方法前已經做好了判斷:

/**
 * Creates a new URL by combining the specified URLs
 *
 * @param {string} baseURL The base URL
 * @param {string} relativeURL The relative URL
 * @returns {string} The combined URL
 */
module.exports = function combineURLs(baseURL, relativeURL) {
    // 將baseURL最後的斜杠和relativeURL最前面的斜杠去掉
    return relativeURL ? baseURL.replace(/\/+$/, "") + "/" + relativeURL.replace(/^\/+/, "") : baseURL;
};

這時,再要拼接 baseURL 和 requestedURL 時,就要判斷 baseUrl 是否存在,或者 path 是否是一個絕對鏈接,只有在 baseURL 存在,且 requestedURL 不是絕對鏈接時,才將 baseURL 和 requestedURL 拼接到一起:

/**
 * Creates a new URL by combining the baseURL with the requestedURL,
 * only when the requestedURL is not already an absolute URL.
 * If the requestURL is absolute, this function returns the requestedURL untouched.
 *
 * @param {string} baseURL The base URL
 * @param {string} requestedURL Absolute or relative URL to combine
 * @returns {string} The combined full path
 */
module.exports = function buildFullPath(baseURL, requestedURL) {
    if (baseURL && !isAbsoluteURL(requestedURL)) {
        return combineURLs(baseURL, requestedURL);
    }
    return requestedURL;
};

在使用 url 之前,先調用下buildFullPath方法,就可以確定這一定是一個絕對鏈接(要保證 baseURL 和 requestURL 至少有 1 個是絕對鏈接)。

3. 迭代執行數據中的每一項

我們有時會對一個 array 類型或者 object 類型的數據進行循環迭代,執行同一個操作,例如編碼其中的每一項等。但在不知道數據類型的前提下,我們可以使用下面的這個方法:

/**
 * Iterate over an Array or an Object invoking a function for each item.
 *
 * If `obj` is an Array callback will be called passing
 * the value, index, and complete array for each item.
 *
 * If 'obj' is an Object callback will be called passing
 * the value, key, and complete object for each property.
 *
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
    // Don't bother if no value provided
    // 如果為空則直接返回
    if (obj === null || typeof obj === "undefined") {
        return;
    }

    // Force an array if not already something iterable
    // 如果既不是array類型,也不是object類型,則將其放到一個數據中
    // 方便進行循環處理
    if (typeof obj !== "object") {
        /*eslint no-param-reassign:0*/
        obj = [obj];
    }

    // 這裏isArray採用 Object.prototype.toString.call 進行判斷
    // Object.prototype.toString.call(obj) === '[object Array]'
    if (isArray(obj)) {
        // Iterate over array values
        // 迭代數據中的每一項
        for (var i = 0, l = obj.length; i < l; i++) {
            fn.call(null, obj[i], i, obj);
        }
    } else {
        // Iterate over object keys
        // 迭代object類型數據中的每一項
        for (var key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                fn.call(null, obj[key], key, obj);
            }
        }
    }
}

4. 將 object 類型的參數拼接到 url 後面

我們在開發過程中,object 類型的數據操作起來比較方便,但在進行 get 請求時或者需要打開帶參數的 URL 時,都需要把 object 類型的數據進行轉換。實現這個功能最常見的庫就是qs了:

import { stringify } from "qs";

const params = {
    name: "wenzi",
    score: "98",
    company: "tencent",
};

// qs中的stringify自帶編碼
console.log(stringify(params)); // name=wenzi&score=98&company=tencent

在 axios 中,為了方便進行用戶進行配置,這裏改為了根據用戶傳入的數據變換方式來處理數據:

function encode(val) {
    return encodeURIComponent(val).replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%20/g, "+").replace(/%5B/gi, "[").replace(/%5D/gi, "]");
}

/**
 * Build a URL by appending params to the end
 *
 * @param {string} url 要拼接的基本url
 * @param {object} [params] 將要追加的參數
 * @returns {string} 已拼接好的URL
 */
module.exports = function buildURL(url, params, paramsSerializer) {
    /*eslint no-param-reassign:0*/
    // 若沒有參數,則不需要拼接,直接返回url接口
    if (!params) {
        return url;
    }

    var serializedParams;
    if (paramsSerializer) {
        // 若用戶傳入了變換數據的函數,則執行
        serializedParams = paramsSerializer(params);
    } else if (utils.isURLSearchParams(params)) {
        // 若是一個URLSearchParams類型的數據,直接轉為string類型
        serializedParams = params.toString();
    } else {
        var parts = [];

        // params是其他類型時
        // 對params進行循環
        utils.forEach(params, function serialize(val, key) {
            if (val === null || typeof val === "undefined") {
                return;
            }

            if (utils.isArray(val)) {
                // 若值是一個數據,則後端可能就是要接收數組格式的數據
                // 這裏把key添加一個[]
                key = key + "[]";
            } else {
                // 將val設置為數組,繼續循環
                val = [val];
            }

            utils.forEach(val, function parseValue(v) {
                if (utils.isDate(v)) {
                    v = v.toISOString();
                } else if (utils.isObject(v)) {
                    v = jsON.stringify(v);
                }
                parts.push(encode(key) + "=" + encode(v));
            });
        });

        // 取出的所有的參數進行拼接
        serializedParams = parts.join("&");
    }

    if (serializedParams) {
        // 判斷url中是否存在hash路徑,若存在,則只獲取前面的部分
        var hashmarkIndex = url.indexOf("#");
        if (hashmarkIndex !== -1) {
            url = url.slice(0, hashmarkIndex);
        }

        // 拼接
        url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;
    }

    return url;
};

5. 總結

這些工具方法雖然是 axios 中的,但我們平時中用到的也比較多,這裏提煉出來,也希望能給大家在平時開發中,提供一些幫助。

來自:https://www.xiabingbao.com/post/request/axios-some-utils.html

站長推薦

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

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

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

沐鳴註冊平台官網_做前端的你有沒有覺得很吃力?

前端工作兩年多。大部分前端原理、框架都能完全運用。工作中幾乎遇不到解決不了的問題(除了那些噁心無法實現的需求)現在經常被安排去面試新人(被安排去面試,主要是工作能力比較突出的原因,公司還有很多3年以上的員工)。下面說說我的學習方法或許對你有用!

一. 打好基礎不用說

html、css、JavaScript 三大件,完全掌握。不懂得就直接查 MDN。html重點掌握語義化。區分塊級和內聯標籤。其他查文檔就好了。還有就是定義 head 裏面一些meta 了解下。

css。重點看盒子模型,定位,層級,過渡,動畫和 transform。知道原理和規則。大部分工作都是照着設計稿化。掌握上面幾個99%還原也不難。接下來重點學習幾種常見的布局。完了之後去搞flex。最後搞下sass、less。基本就差不多了。

JavaScript。重點來了,紅寶書看一遍掌握基礎,進階去看《你不知道的 JavaScript》。就這兩套足夠了,別搞那麼多。每個知識點搞懂。ES6基本沒啥問題。下面幾個問題優搞懂,優先級如下:

  1. this 用法,相關原理

  2. 原型/原型鏈

  3. 閉包

  4. 面向對象相關

  5. 同步異步/回調/promise/async、await

  6. 模塊化 Commonjs, AMD

先搞這懂這些比較難的概念,對你js理解更加深入。接下來在開始看框架方面

二、框架方面

前期要會用,後期要懂原理。

新人先搞 vue。vue 算是比較簡單的框架了,上手容易。照着官方文檔來問題不大。原理方面要提高自己認識。學習怎麼看源碼。github常去逛逛。

學習框架之前,我其實特別建議,新人先去了解 Babel 和 webpack 不僅僅是使用。一些原理方面的東西工作中也會用到。babel 裏面會有教你如何編譯代碼。webpack教你如和打包文件。自己手寫編譯器和打包工具也不是特別難。反正對之後看vue、react源碼幫助挺大。

搞完 Vue 全家桶,去了解下 react,React hooks 學習下新的理念。再回過頭來看Vue。你會發現他們是如此的相似去又不同。

多去實踐總結,對整體框架理解會越來越深刻。

三、如何看源碼

新人剛開始看源碼,會陷入兩個困境中。一是無從下手。二是看了之後感覺沒啥收穫。

這個也很正常。一般我們熟知的框架都有個幾千甚至上萬個PR。太大細節會幹擾你。掌握整個節奏和流程。學習原理也比較吃力。就連找個入口都像大海撈針一樣。建議從下面幾個方面入手:

  1. 挑簡單的上手。別一開始就搞 vue、react、webpack。太難,會直接勸退新人。不要為了面試而去讀。反而效果不好,面試稍微問深入一點就答不出來了。平時有興趣多琢磨琢磨。按照難易程度,函數庫 < 組件庫 < 框架 < 工程化 分別典型代表 lodash < vant < vue < webpack

  2. 手擼簡易模型。像vue, webpack, babel 都有簡易項目給你擼。有的創始人(尤哥)還直播手擼。國外的更多,youtube 一搜一大堆。就算不看源碼,照着寫出了簡易 demo 對原理和理解提升都是很大的。

  3. 調試開源項目。先把項目拉下來。在vscode裏面跑下,核心函數多打幾個斷點。看看裏面變量是怎麼diff的。對理解更深刻了。

看了源碼是別人的,學到了是自己的。學習時候邊記筆記,邊思考原理,總結經驗。下面來談談前端工程化怎麼弄。

四、前端工程化

現在最流行的打包工具 webpack 用起來。當然直接用 vue-cli2、vue-cli3、create-react 都是可以的。但是 webpack 相關還是得掌握。

首先重點搞下babel、webpack。學習下編譯,打包的原理。自己配置下 webpack。嘗試自己去寫下下 webpack 的 loader 和 plugin。學習這些之前要懂一點 node.js, node.js 不需要全部學習。一般就日常用到讀寫文件fs接口,path 路徑接口。這些 api 都不難寫幾個 demo 就懂了。基本上webpack 裏面配置文件也沒用到多少 node 的東西。最後自己學會配置webpack的配置文件。

如果想深想去優化打包體積和速度,就需要去了解很多webpack插件。webpack 裏面最核心的就是插件了。

當然前端工程化不僅僅是這些,CI/CD可持續集成, Umi 了解下。shell各種腳本自動化命令、代碼生成技術了解下。

五、性能優化的方案

一般來說。性能優化沒什麼系統化的文檔供人學習。完全靠一些經驗和自己的實踐。

我們常提到性能好壞是由什麼來衡量呢?

訪問頁面地址 –> 頁面首次加載數據 –> 渲染出完整頁面的時長

非首次情況下,命中緩存的加載緩存數據 –> 渲染出完整頁面的時長。

一般我從下面幾個方面着手去做,一般問題都不大。

減小資源(靜態資源,後端加載的數據)大小

  • 壓縮代碼HTML/CSS/JS

  • 壓縮圖片、音視頻大小

  • Tree-Sharking 消除無用代碼

以上webpack都可以搞定

避免同一時間的過多次數請求

  • CSS 實現雪碧圖:使用background-position共享一張圖

  • 圖片懶加載:監聽滾動后offsetTop,  使用>

    列表懶加載(分批加載):監聽滾動后offsetTop, 發送請求加載下一頁的數據

  • 路由懶加載

  • 代碼分包分塊加載(webpack)

  • 預加載技術

  • 小程序分包、預下載等。

利用緩存(空間換時間)

  • CDN 內容分發:獲取更近網絡節點緩存下來的靜態資源

  • 瀏覽器緩存(自帶)

  • 部分資源保存在LocalStorage或者APP緩存中(手動操作)

其他

  • SSR 服務端渲染:解決SPA框架帶來JS動態渲染頁面帶來的延遲和白屏問題。

這些都可以去實踐的,難度不大。難度大的地方可能是 dom 節點成千上萬的時候渲染的性能問題。這個場景遇到的很少,方案很多。不同人有不同解決方案,有功夫可以自己去嘗試嘗試。

上面提到很多點都可以深入到很深。由於篇幅原因,點到即止。

純粹是把一些我以前走過的彎路掰直了再分享給大家。畢竟不是《前端入門到精通》哈哈哈

六、學習移動端web開發

前端現在為什麼這麼火?各個公司都還挺缺優秀的前端。原因在於 技術紅利 。

移動端web流行起來之後,特別是H5和小程序,帶動了多少前端就業,前端迅速取代了安卓和iOS 的大量崗位。

回到正題:所以作為前端人,移動web一般是都要接觸的。不同於PC 端。

移動端有哪些東西呢?不需要全部懂,差不多知道就行了。要用的時候再去學。

  1. 絕對單位換相對單位:px => rem / vw / rpx

  2. 彈性布局:使用flex、grid布局

  3. hairline (1px的粗線處理):使用偽元素 + transform: scale(倍數) 縮放線框

  4. WebView 環境了解下

  5. 安卓iOS 兼容踩坑:點擊延遲、穿透、滾動不流暢、安全區域等等。

  6. 小程序開發相關踩坑

  7. JSBridge: H5 與App 通信

  8. H5動畫製作

  9. 跨平台框架:react native、weex、flutter 等等

簡單的說移動web 就是:html/css/js 跑在手機app 裏面的WebView(web運行環境)。

小程序/公眾號就是在這個基礎上,將自己APP裏面的WebView 租售給其他人使用。

微信APP —– 提供SDK —-> 微信webview —– 提供運行環境—–> 公眾號h5 / 小程序

為什麼微信可以容納幾乎無限的H5/小程序頁面呢?

因為公眾號/小程序的代碼都存儲在雲端,通過不同的路由就可以給幾乎無數的開發者使用。

使得微信成為一個運行環境+入口的存在。

七、做前端我有沒有覺得吃力?

剛做時前端不吃力。因為我是軟件工程專業,學前端之前,學過 C/C++、Java、php、.net 成績還不錯基本都是90多分。感覺自己干後端也不吃力。實驗室裏面的項目都是前後都寫。最愛 php,當時最崇拜鳥哥。本以為以後就走上PHP後端工程師的道路了,成為鳥哥那樣的大神。

由於項目需要的原因,後來漸漸開始學起學 HTML、CSS、JavaScript 這些語法相關的東西。剛接觸時沒有感覺太大難度。

當時就想着怎麼把頁面搞好看,搞各種動畫炫技。寫一個小球從下面彈出來的效果,換各種姿勢彈出。當時覺得前端真的有意思,就入了前端的坑。入坑前,以為前端就是搞各種漂亮的頁面,各種特性驚艷別人。

隨着接觸的越來越深。接觸到了 AJAX, jQuery ,Bootstrap,前端開始注重體驗。各種框架橫空出世 backbone => Angular => React => Vue 眼花繚亂。

折騰了 JQuery 開始折騰 backbone 覺得前端還能這樣玩。有些迷茫了,感覺腦袋快要炸了,那段時間特別焦慮,瘋狂看書寫代碼,怎麼這麼多東西要學啊。

JavaScript 也不精通,到原型就不理解了,雖然有C++,JAVA面向對象的知識,但JavaScript 你怎麼和別人玩的不是一個套路啊。當時就都用ES6了, 行,學。都用Sass了,行,學。不學也可以,看不懂別人的代碼呀。

畢業前為準備校招前端工程師,真的很吃力。就怕校招面試時,自己啥都不懂。

功夫不負有心人,校招時候順利拿到了自己滿意的Offer。記得當時和面試官對答入流,好像找到知音一樣。面試官也是過來人,基本能問的都問了。

拿到Offer之後就去了實習。實習第一個任務:將一個ES6的後台管理系統重構成 Vue2.0 全家桶的項目。有個導師帶,但是她和我是不同項目,出了架構以外代碼都是自己寫。

這個階段還是收穫了很多:

  1. git 命令特別熟練。commit、stash、merge/rebase、cherry-pick、push/pull/fetch、reset等等基本都敲都特別多了

  2. 嚴格了代碼規範。Eslint、prettier 都用起來了

  3. 會自己寫業務組件,會封裝高級組件、寫常規頁面了。基本上大部分不是特別複雜的交互都ok。難一點多找下資料可以做出來。

  4. 學會管理API了。自己嘗試封裝了 axios。統一處理錯誤和彈窗。

  5. 會抽取公共css、JavaScript 函數,編寫CSS 變量和JavaScript 常量了

  6. webpack 能看懂配置文件了。

實習過後順利轉正。轉正之後,換了另外一個導師帶,加入到項目組作為一個比較大的項目的核心開發。基本不再做管理系統了。主要做一個saas 平台。涉及比較難的富文本編輯,UI 拖拽生產文章,數據可視化生產報表等等。還寫了幾個谷歌瀏覽器插件。

  1. 開始提升寫頁面效率,寫的比較快了。

  2. 研究 webpack 的插件打包編譯效率

  3. 研究 babel 編譯原理

  4. 研究了 Vue 編譯的一些原理

  5. 研究了 一些圖表的使用,多半使用的echart。常規圖表基本都用過。

  6. CI & CD 自己去搭建。學了一些 shell 腳本開發。研究了 docker 相關的東西。

  7. 嘗試去寫基礎組件,搭建基礎組件庫。

  8. 學習 React 相關的語法。

  9. 研究富文本編輯,圖片壓縮裁剪原理等等

  10. 寫一個簡單的微信公眾號,接觸到了 H5 開發。

工作第一年,基本上主戰場在PC 端。**前半年挺吃力,後半年熟練后游刃有餘。**會懟產品,會噴設計,會和後端兄弟配合默契。和團隊感情也很深了。

無奈項目由於某些原因終止,團隊解散,調到新團隊。在老東家工作一年多后,由於個人原因離開去了一家新的的公司,主戰場從PC 到了手機。開始接觸移動H5、hybrid 開發。

八、學習吃力的原因肯定是學習方法不太正確。總結下一般怎麼才能真正學到東西

  1. 詳略得當:前端知識太多,抓重點學,不要像背字典一樣。

  2. 不要急着寫代碼,先理清流程(以一個函數為單位,可以先寫註釋)再寫代碼。

  3. 看視頻看講解是會誤以為自己會了,其實並沒有。

  4. 學會總結:一句話可以講清楚的事情,不要多說一句。減少心智負擔。

  5. 不介意複製代碼,但是要知道這個代碼裏面大致實現原理。感興趣自己重寫一個。

  6. 較大的項目,不要急着看代碼。可以先把項目跑起來。通過改代碼裏面的參數來理解裏面的核心流程。

  7. demo 式編程。對於新框架,參考demo來上手更快更容易理解。

  8. 漸進式編程。對於比較複雜的功能/需求。不要想着一氣呵成。先實現一個核心,每次往上面加細節,有點像繪畫。

  9. 斷點單步調試很有用,定位bug會更快。當然有些不易調試的應用選擇打log。一次打 log 要多打點,免得打完log,有得再加。

  10. 黑盒太多的項目,實在找不到bug原因。發給同事幫忙看。可能很快就能看出來。當局者迷,旁觀者清。(很多時候是拼寫的問題)

站長推薦

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

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

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

沐鳴平台註冊登錄_給 eslint 寫一個插件

eslint 是很有名的 linter,地球上每一個 JavaScript 程序員都應該知道。

linter 是一種代碼靜態分析工具,它可以幫你找到代碼中可能存在的錯誤與 bug,也能找出代碼風格的問題,不過因為只是靜態分析,對 js 這種動態類型的語言所能做的就比較有限了,畢竟在 js 中,變量的類型如果不執行就不容易知道,有些錯誤就不那麼容易被找出來,雖然如此,能做的檢查還是很多了。

安裝

安裝 eslint 本身只需要安裝 eslint 本身就夠了,而且 eslint 自帶一些規則,不安裝任何插件就做到基本的檢查,但一般還是需要安裝一些插件。

$ yarn add --dev eslint

eslint 除了可以安裝插件外,還可以安裝另外兩個東西,總共有 3 種:

  • plugin:eslint 的插件可以幫 eslint 增加規則,另外也可以通過配置文件讓程序員添加自己的規則,插件可以提供一份默認的推薦配置
  • config:可以重複使用的規則配置文件,比較有名的是 standard 和 airbnb 的規則,配置文件有可能會有依賴的插件,需要自己去安裝
  • parser:用來擴充 eslint 可以處理的語法,有用 babel 轉換 js 的 babel-eslint ,讓 eslint 可以處理實驗性的語法;@typescript-eslint/parser 可以讓 eslint 處理 Typescript;還有 vue-eslint-parser 用來處理 vue 代碼。

使用

雖然安裝很簡單,但不對 eslint 進行配置是什麼都不能做的,所以還要提供一個基本的配置,而 eslint 提供一個簡單的初始化命令,通過執行這個命令並回答幾個問題,eslint 就會產生一個基本的配置:

$ yarn eslint --init

eslint 的配置文件可以是 js、json 或 yml 的格式,在這裏我們用 js 格式,文件要取名為 .eslintrc.js,這裏就用基本的配置,即只用 eslint:recommended 這組設置,如果有其它的插件也像這樣進行基本的設置:

module.exports = {
  extends: 'eslint:recommended',
}

eslint 的配置文件有幾個基本的配置項,在這裏也順便說幾個我常用的 config 的插件,首先是 config 的部分:

  • eslint-config-standard:很有名的配置,它還需要另外安裝 4 個插件
  • eslint-config-prettier:用來關掉排版相關配置項的配置文件,因為要交給 prettier 處理,關掉就不會引發衝突了。

我還沒有列出 standard 所相依的插件:

  • eslint-plugin-simple-import-sort:能夠自動排序 import 的一個插件
  • eslint-plugin-eslint-comments:用來檢查 eslint 的特殊註解的一個插件,eslint 可以用特殊的註解開關規則,這些等下會講到,這個插件的用途是不允許關閉了規則后不再打開,以及關掉所有規則。

把上面的內容都寫到配置文件中應該是這樣:

module.exports = {
  extends: [
    'standard',
    // 加上 prettier 的配置,關掉部份樣式檢查,順序很重要
    'prettier',
    'prettier/standard',
    // 如果是插件提供的配置項需要以 `plugin:` 開始
    'plugin:eslint-comments/recommended',
  ],
  // 額外的規則,這裏也可以決定是否要關掉某些規則
  rules: {
    // 設置 plugin `eslint-plugin-simple-import-sort` 的 `sort` 規則是 `error`,也就是不符合時是會報錯的
    // 另外還可以設置為 `warn` 只警告,或是 `off` 關掉
    // 有的規則也有選項,這是就要用 ['error', {<options>}] 這種像 babel 的格式了
    'simple-import-sort/sort': 'error',
  },
  // 配置指定的環境,這會影響到判斷哪些是全局變量
  env: {
    browser: true
  },
  // 設置 eslint 自己的 parser 用的是哪一版本的 js ,一般設置為 eslint --init 就行了
  parserOptions: {
    ecmaVersion: 12,
  },
}

運作原理

eslint 跟 babel 很相似,都是先把文件轉成 AST,如果想查看 eslint 轉出來的 AST ,可以到 AST Explorer 選擇 espree 解析器,這是 eslint 內置的解析器,它和 babel 的解析器不太一樣,應該說是 babel 的解析器和別人不一樣才對,ECMAScript 定義了一套 js 的 AST 該怎樣定義的規則,是 babel 和別人不同,另外 eslint 的解析器需要很詳細的信息,不能只有代碼的同步而已,而這樣才能做好 lint 的工作。

它的運作方式也像 babel 一樣,讓 plugin XML visitor 對特定的節點進行檢查,如果發現有問題就通過它的 API 來報告,也可以通過它的 API 提供修正的程序。

寫一個自己的 eslint 插件

接下來寫一個 eslint 插件,雖說是寫插件,但實際上寫的是 eslint 的規則,假設我們希望 js 的對象是這樣的(比如 vue 的 object):

export default {
  name: 'Foo',

  props: {},

  data: () => ({}),
}

像上面這樣中間都有個空行,可以用兩種很簡單的方法來判斷是不是 vue 的對象:

  • 在 export default 之後
  • 包含在 Vue.extend 中

eslint 的規則大致分為meta 和 create 兩個部分:

  • meta:這個規則的描述,如果這個規則可以被自動修復,也必須要定義在這裏
  • create:建立規則的 AST visitor,規則的檢查是在這裏做的

與 babel 插件很像,第一步是先打開 AST Explorer,選 eslint 用的解析器 espree,這裏要替換的是 ObjectExpress

module.exports = {
  meta: {
    // 可以被修復的規則一定要定義,這裏除了 'whitespace' 外還有 'code' ,不過知識分類上的問題
    // 這裏因為是要加換行,所以選 'whitespace'
    fixable: 'whitespace',
    // 可以定義可能會出現的信息,這就可以進行統一管理。這是 eslint 的推薦做法
    // 你也可以直接把數據寫在 context.report
    messages: {
      requireNewline: 'require newline between',
    },
  },
  create: function (context) {
    return {
      ObjectExpression(node) {
        if (
          // 判斷副節點是否為 export default
          node.parent.type === 'ExportDefaultDeclaration' ||
          // 或父節點是 `Vue.extend`
          (node.parent.type === 'CallExpression' && isVueExtend(node.parent.callee))
        ) {
          // 得到 source code 對象,後面的 fixer 需要用到
          const sourceCode = context.getSourceCode()
          // 用 for 循環把對象的屬性每兩個氛圍一組,檢查中間有沒有加空行
          for (let i = 0; i < node.properties.length - 1; ++i) {
            // 這裏的判斷方法很簡單,上一個屬性結尾的行號必須與下一個屬性結尾的行號相差 2 以上
            // 也就是中間有兩個以上的空行
            if (node.properties[i + 1].loc.start.line - node.properties[i].loc.end.line < 2) {
              context.report({
                // 用預先定義的數據
                messageId: 'requireNewline',
                // 或是你可以直接把數據寫進來
                // message: 'require newline between',
                // 指定出錯的位置,因為是在兩個屬性之間,所以就用上一個的 end 與后一個的 start 來指定
                loc: {
                  start: node.properties[i].loc.end,
                  end: node.properties[i + 1].loc.start,
                },
                // 如果出錯的位置正好是某個 AST 的節點,那也可以傳入節點
                // node: node
                fix(fixer) {
                  // 這裡是自動修復的部分稍後再加上
                },
              })
            }
          }
        }
      },
    }
  },
}

正常來說 eslint 的插件需要照着 eslint 的命名規則才能加載,不過為了方便測試,就直接調用 eslint 的函數把自定義的規則加入:

// eslint 的 Linter
const { Linter } = require('eslint')
// 我們要定義的規則
const rule = require('./space-between-properties')

const linter = new Linter()

// 為規則設定一個 id
const id = 'space-between-properties'
// 加入規則定義,也就是上面的那個東西
linter.defineRule(id, rule)

// 執行規則
const res = linter.verify(
  ` export default { name: 'Foo', props: {}, } `,

  {
    rules: {
      [id]: 'error',
    },
    parserOptions: {
      sourceType: 'module',
      ecmaVersion: 2015,
    },
  }
)

// 如果有錯誤的話就打印出來
if (res.length) {
  console.log(res)
}

不出意外的話應該會看到有內容輸出,接着要加上自動修復的部分:

// 接上面的 fix 部份
fix(fixer) {
  // 取得兩個節點中間的 token
  const tokens = sourceCode.getTokensBetween(node.properties[i], node.properties[i + 1])
  // “通常”中間只會有逗號,所以唯一的節點就是逗號
  const comma = tokens[0]
  // 要求 eslint 在都好後面加上換行
  return fixer.insertTextAfterRange(comma.range, '\n')
}

修復也很簡單,就是在逗號後面加上換行而已,不過上面也特別說了是“通常”,其實這個插件你只要在 , 後面加上註解就會出現問題了

eslint 會在最後一次把修復加上去,然後再跑一次所有規則,如果還是有可以修復的問題就再跑一次,直到沒有可以自動修復的問題為止,所以也不用擔心會破壞其他插件所提供的規則。不過如果出現了規則互相衝突會怎樣呢,如果有興趣的話可以自己來試試。

    站長推薦

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

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

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

    沐鳴平台網站_尾調用與尾遞歸

    本講將對尾調用與尾遞歸進行介紹:函數的最後一條執行語句是調用一個函數的形式即為尾調用;函數尾調用自身則為尾遞歸,通過改寫循環即可輕鬆寫出尾遞歸函數。在語言支持尾調用優化的條件下,尾調用能節省很大一部分內存空間。

    什麼是尾調用

    問:何為尾調用?

    說人話:函數的最後一條執行語句是調用一個函數,這種形式就稱為尾調用。

    讓我們看看以下幾個例子。

    // 正確的尾調用:函數/方法的最後一行是去調用function2()這個函數
    public int function1(){
        return function2();
    }
    
    // 錯誤例子1:調用完函數/方法后,又多了賦值操作
    public int function1(){
        int x = function2();
        return x;
    }
    
    // 錯誤例子2:調用完函數后,又多了運算操作
    public int function1(){
        return function2() + 1;
    }
    
    // 錯誤例子3:f(x)的最後一個動作其實是return null
    public void function1(){
        function2();
    }

    尾調用優化

    以Java為例。JVM會為每個新創建的線程都創建一個棧(stack)。棧是用來存儲棧幀(stack frame)的容器;而棧幀是用來保存線程狀態的容器,其主要包括方法的局部變量表(local variable table),操作數棧(operand stack),動態連接(dynamic linking)和方法返回地址(return address)等信息。

    (注:Java語言目前還不支持尾調用優化,但尾調用優化的原理是相通的。)

    棧會對棧幀進行壓棧和出棧操作:每當一個Java方法被執行時都會新創建一個棧幀(壓棧,push),方法調用結束后即被銷毀(出棧,pop)。

    在方法A的內部調用方法B,就會在A的棧幀上疊加一個B的棧幀。在一個活動的線程中,只有在棧頂的棧幀才是有效的,它被稱為當前棧幀(Current Stack Frame),這個棧幀所關聯的方法則被稱為當前方法(Current Method)。只有當方法B運行結束,將結果返回到A后,B的棧幀才會出棧。

    舉個例子。

    public int eat(){
        return 5;
    }
    
    public int action(){
        int x = eat();
        return x;
    }
    
    public int imitate(){
        int x = action();
        return x;
    }
    
    public static void main(String[] args){
        imitate();
    }

    這段代碼對應的棧的狀況則為如下:

    首先,在main線程調用了 imitate() 方法,便將它的棧幀壓入棧中。

    在 imitate() 方法里,調用了 action() 方法,由於這不是個尾調用,在調用完 action() 方法后仍存在一個運算操作,因此將 action a c t i o n  的棧幀壓入棧中后,JVM認為 imitate() 方法還沒執行完,便仍然保留着 imitate i m i t a t e  的棧幀。

    同理: action() 方法里對 eat() 方法的調用也不是尾調用,JVM認為在調用完 eat() 方法后, action() 方法仍未執行結束。因此保留 action a c t i o n  的棧幀,並繼續往棧中壓入 eat e a t  的棧幀。

    eat() 方法執行完后,其對應棧幀就會出棧; action() 方法和 imitate() 方法在執行完后其對應的棧幀也依次出棧。

    但假如我們對上述示例代碼改寫成如下所示:

    public int eat(){
        return 5;
    }
    
    public int action(){
        return eat();
    }
    
    public int imitate(){
        return action();
    }
    
    public static void main(String[] args){
        imitate();
    }

    那麼如果尾調用優化生效,棧對應的狀態就會為如下:

    首先,仍然是將 imitate() 方法的棧幀壓入棧中。

    在 imitate() 方法中對 action() 方法進行了尾調用,因此在調用 action() 方法時就意味着 imitate() 方法執行結束: imitate i m i t a t e  棧幀出棧, action a c t i o n  棧幀入棧。

    同理: action a c t i o n  棧幀出棧, eat e a t  棧幀入棧。

    最後, eat() 方法執行完畢,全流程結束。

    我們可以看到,由於尾調用是函數的最後一條執行語句,無需再保留外層函數的棧幀來存儲它的局部變量以及調用前地址等信息,所以棧從始至終就只保留着一個棧幀。這就是 尾調用優化(tail call optimization) ,節省了很大一部分的內存空間。

    但上面只是理論上的理想情況,把代碼改寫成尾調用的形式只是一個前提條件,棧是否真的如我們所願從始至終只保留着一個棧楨還得取決於語言是否支持。例如python就不支持,即使寫成了尾遞歸的形式,棧該爆還是會爆。

    尾遞歸

    問:何為尾遞歸?

    說人話:函數尾調用自身,這個形式就稱為尾遞歸。

    在手把手教你寫遞歸這篇文章中我們提過,遞歸對空間的消耗大,例如計算 factorial(1000) ,就需要保存1000個棧幀,很容易就導致棧溢出。

    但假如我們將其改為尾遞歸,那對於那些支持尾調用優化的語言來說,就能做到只保存1個棧幀,有效避免了棧溢出。

    那尾遞歸函數要怎麼寫呢?

    一個比較實用的方法就是先寫出用循環實現的版本,再把循環中用到的局部變量都改為函數的參數即可。這樣再進入下一層函數時就不需要再用到上一層函數的環境了,到最後一層時就包含了前面所有層的計算結果,就不要再返回了。

    例如階乘函數。

    public int fac(int n) {
        int result = 1;
        for (int index = 1; index <= n; index++)
            result = index * result;
        return result;
    }

    在這個用循環實現的版本中,可以看到用到了 result,index r e s u l t , i n d e x  這兩個局部變量,那就將其改為函數的參數。並且通過循環可以看出邊界條件是當 index == n 時; n n  從頭到尾不會變; index i n d e x  在每次進入下一層循環時會遞增, result r e s u l t  在每次進入下一層循環時會有變動。我們把這些改動直接照搬,改寫就非常容易了。

    所以用尾遞歸實現的版本即為如下:

    public int fac(int n, int index, int result) {
        if (index == n)
            return index * result;
        else 
            return fac(n, index + 1, index * result);
    }

    再舉個例子,斐波那契數列(0, 1, 1, 2, 3, 5, 8, 13…)

    其循環實現版本如下:

    public int fibo(int n) {
        int a = 0;
        int b = 1;
        int x = 0;
        for (int index = 0; index < n; index++){
        	x = b;
            b = a + b;
            a = x;
        }
        return a;
    }

    局部變量有 a,b,index a , b , i n d e x  ( x x  作為 a,b a , b  賦值的中間變量,在遞歸中可以不需要用到),把這些局部變量放到參數列表。邊界條件為當 index == n 時;並且,在進入下一層循環時, a a  的值會變為 b b  , b b  的值會變為 a+b a + b  , index i n d e x  的值加1,把這些改動照搬。

    public int fibo(int n, int a, int b, int index) {
        if (index == n) 
            return a;
        else 
            return fibo(n, b, a + b, index + 1);
    }

    原文 http://www.cnblogs.com/linj7/p/14163265.html

    站長推薦

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

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

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

    沐鳴網址_為什麼要用 setTimeout 模擬 setInterval ?

    setInterval 是一個宏任務。用多了你就會發現它並不是準確無誤,極端情況下還會出現一些令人費解的問題。下面我們一一羅列..

    推入任務隊列后的時間不準確

    定時器代碼:

    setInterval(fn(), N);

    上面這句代碼的意思其實是fn()將會在 N 秒之後被推入任務隊列

    所以,在 setInterval 被推入任務隊列時,如果在它前面有很多任務或者某個任務等待時間較長比如網絡請求等,那麼這個定時器的執行時間和我們預定它執行的時間可能並不一致。

    比如:

    let startTime = new Date().getTime();
    let count = 0;
    //耗時任務
    setInterval(function() {
      let i = 0;
      while (i++ < 1000000000);
    }, 0);
    setInterval(function() {
      count++;
      console.log(
        "與原設定的間隔時差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
      );
    }, 1000);
    // 輸出:
    // 與原設定的間隔時差了: 699 毫秒
    // 與原設定的間隔時差了: 771 毫秒
    // 與原設定的間隔時差了: 887 毫秒
    // 與原設定的間隔時差了: 981 毫秒
    // 與原設定的間隔時差了: 1142 毫秒
    // 與原設定的間隔時差了: 1822 毫秒
    // 與原設定的間隔時差了: 1891 毫秒
    // 與原設定的間隔時差了: 2001 毫秒
    // 與原設定的間隔時差了: 2748 毫秒
    // ...

    可以看出來,相差的時間是越來越大的,越來越不準確。

    函數操作耗時過長導致的不準確

    考慮極端情況,假如定時器裏面的代碼需要進行大量的計算(耗費時間較長),或者是 DOM 操作。這樣一來,花的時間就比較長,有可能前一次代碼還沒有執行完,后一次代碼就被添加到隊列了。也會到時定時器變得不準確,甚至出現同一時間執行兩次的情況。

    最常見的出現的就是,當我們需要使用 ajax 輪詢服務器是否有新數據時,必定會有一些人會使用 setInterval,然而無論網絡狀況如何,它都會去一遍又一遍的發送請求,最後的間隔時間可能和原定的時間有很大的出入。

    // 做一個網絡輪詢,每一秒查詢一次數據。
    let startTime = new Date().getTime();
    let count = 0;
    
    setInterval(() => {
        let i = 0;
        while (i++ < 10000000); // 假設的網絡延遲
        count++;
        console.log(
            "與原設定的間隔時差了:",
            new Date().getTime() - (startTime + count * 1000),
            "毫秒"
        );
    }, 1000)
    輸出:
    // 與原設定的間隔時差了: 567 毫秒
    // 與原設定的間隔時差了: 552 毫秒
    // 與原設定的間隔時差了: 563 毫秒
    // 與原設定的間隔時差了: 554 毫秒(2次)
    // 與原設定的間隔時差了: 564 毫秒
    // 與原設定的間隔時差了: 602 毫秒
    // 與原設定的間隔時差了: 573 毫秒
    // 與原設定的間隔時差了: 633 毫秒

    setInterval 缺點 與 setTimeout 的不同

    再次強調,定時器指定的時間間隔,表示的是何時將定時器的代碼添加到消息隊列,而不是何時執行代碼。所以真正何時執行代碼的時間是不能保證的,取決於何時被主線程的事件循環取到,並執行。

    setInterval(function, N) //即:每隔N秒把function事件推到消息隊列中

    上圖可見,setInterval 每隔 100ms 往隊列中添加一個事件;100ms 后,添加 T1 定時器代碼至隊列中,主線程中還有任務在執行,所以等待,some event 執行結束后執行 T1 定時器代碼;又過了 100ms,T2 定時器被添加到隊列中,主線程還在執行 T1 代碼,所以等待;又過了 100ms,理論上又要往隊列里推一個定時器代碼,但由於此時 T2 還在隊列中,所以 T3 不會被添加(T3 被跳過),結果就是此時被跳過;這裏我們可以看到,T1 定時器執行結束后馬上執行了 T2 代碼,所以並沒有達到定時器的效果。

    綜上所述,setInterval 有兩個缺點:

    • 使用 setInterval 時,某些間隔會被跳過;
    • 可能多個定時器會連續執行;

    可以這麼理解:每個 setTimeout 產生的任務會直接 push 到任務隊列中;而 setInterval 在每次把任務 push 到任務隊列前,都要進行一下判斷(看上次的任務是否仍在隊列中,如果有則不添加,沒有則添加)。

    因而我們一般用 setTimeout 模擬 setInterval,來規避掉上面的缺點。

    來看一個經典的例子來說明他們的不同:

    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000);
    }

    做過的朋友都知道:是一次輸出了 5 個 5;
    那麼問題來了:是每隔 1 秒輸出一個 5 ?還是一秒后立即輸出 5 個 5?
    答案是:一秒后立即輸出 5 個 5
    因為 for 循環了五次,所以 setTimeout 被 5 次添加到時間循環中,等待一秒后全部執行。

    為什麼是一秒后輸出了 5 個 5 呢?
    簡單來說,因為 for 是主線程代碼,先執行完了,才輪到執行 setTimeout。

    當然為什麼輸出不是 1 到 5,這個涉及到作用域的問題了,這裏就不解釋了。

    setTimeout 模擬 setInterval

    綜上所述,在某些情況下,setInterval 缺點是很明顯的,為了解決這些弊端,可以使用 settTimeout() 代替。

    • 在前一個定時器執行完前,不會向隊列插入新的定時器(解決缺點一)
    • 保證定時器間隔(解決缺點二)

    具體實現如下:

    1.寫一個 interval 方法

    let timer = null
    interval(func, wait){
        let interv = function(){
            func.call(null);
            timer=setTimeout(interv, wait);
        };
        timer= setTimeout(interv, wait);
     },

    2.和 setInterval() 一樣使用它

    interval(function() {}, 20);

    3.終止定時器

    if (timer) {
      window.clearSetTimeout(timer);
      timer = null;
    }

    參考

    • 為什麼要用 setTimeout 模擬 setInterval ?
    • 用 settTimeout()代替 setInterval()

    來自:https://segmentfault.com/a/1190000038829248

    站長推薦

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

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

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

    沐鳴主管:_Vue3.0面試題

    前言

    昨晚做了一個夢,夢見自己到了一家大廠面試,面試官走近房間,坐了下來:是楊溜溜吧?國際慣例,先來個自我介紹吧。

    於是我巴拉巴拉開始了長達兩分鐘的自我介紹,與此同時,面試官邊聽邊看我的簡歷,邊看邊皺眉,結束后問:看你之前的項目經常用到vue,對vue熟悉嗎?

    我嘴角一笑,心裏暗喜:幸好有專門看Vue的面試題,看來這次穩了。於是謙虛又裝逼的回答:還行吧,您隨便問。

    於是面試官看我口氣那麼大,心想:喲嚯,來了一個裝逼的,勞資今天就只問Vue。

    來,先介紹一下Vue的響應式系統

    Vue為MVVM框架,當數據模型data變化時,頁面視圖會得到響應更新,其原理對data的getter/setter方法進行攔截(Object.defineProperty或者Proxy),利用發布訂閱的設計模式,在getter方法中進行訂閱,在setter方法中發布通知,讓所有訂閱者完成響應。

    在響應式系統中,Vue會為數據模型data的每一個屬性新建一個訂閱中心作為發布者,而監聽器watch、計算屬性computed、視圖渲染template/render三個角色同時作為訂閱者,對於監聽器watch,會直接訂閱觀察監聽的屬性,對於計算屬性computed和視圖渲染template/render,如果內部執行獲取了data的某個屬性,就會執行該屬性的getter方法,然後自動完成對該屬性的訂閱,當屬性被修改時,就會執行該屬性的setter方法,從而完成該屬性的發布通知,通知所有訂閱者進行更新。

    computed與watch的區別

    計算屬性computed和監聽器watch都可以觀察屬性的變化從而做出響應,不同的是:

    計算屬性computed更多是作為緩存功能的觀察者,它可以將一個或者多個data的屬性進行複雜的計算生成一個新的值,提供給渲染函數使用,當依賴的屬性變化時,computed不會立即重新計算生成新的值,而是先標記為臟數據,當下次computed被獲取時候,才會進行重新計算並返回。

    而監聽器watch並不具備緩存性,監聽器watch提供一個監聽函數,當監聽的屬性發生變化時,會立即執行該函數。

    介紹一下Vue的生命周期

    beforeCreate:是new Vue()之後觸發的第一個鈎子,在當前階段data、methods、computed以及watch上的數據和方法都不能被訪問。

    created:在實例創建完成后發生,當前階段已經完成了數據觀測,也就是可以使用數據,更改數據,在這裏更改數據不會觸發updated函數。可以做一些初始數據的獲取,在當前階段無法與Dom進行交互,如果非要想,可以通過vm.$nextTick來訪問Dom。

    beforeMount:發生在掛載之前,在這之前template模板已導入渲染函數編譯。而當前階段虛擬Dom已經創建完成,即將開始渲染。在此時也可以對數據進行更改,不會觸發updated。

    mounted:在掛載完成后發生,在當前階段,真實的Dom掛載完畢,數據完成雙向綁定,可以訪問到Dom節點,使用$refs屬性對Dom進行操作。

    beforeUpdate:發生在更新之前,也就是響應式數據發生更新,虛擬dom重新渲染之前被觸發,你可以在當前階段進行更改數據,不會造成重渲染。

    updated:發生在更新完成之後,當前階段組件Dom已完成更新。要注意的是避免在此期間更改數據,因為這可能會導致無限循環的更新。

    beforeDestroy:發生在實例銷毀之前,在當前階段實例完全可以被使用,我們可以在這時進行善後收尾工作,比如清除計時器。

    destroyed:發生在實例銷毀之後,這個時候只剩下了dom空殼。組件已被拆解,數據綁定被卸除,監聽被移出,子實例也統統被銷毀。

    為什麼組件的data必須是一個函數

    一個組件可能在很多地方使用,也就是會創建很多個實例,如果data是一個對象的話,對象是引用類型,一個實例修改了data會影響到其他實例,所以data必須使用函數,為每一個實例創建一個屬於自己的data,使其同一個組件的不同實例互不影響。

    組件之間是怎麼通信的

    • 父子組件通信

    父組件 -> 子組件:prop

    子組件 -> 父組件:$on/$emit

    獲取組件實例:使用$parent/$children,$refs.xxx,獲取到實例后直接獲取屬性數據或調用組件方法

    • 兄弟組件通信

    Event Bus:每一個Vue實例都是一個Event Bus,都支持$on/$emit,可以為兄弟組件的實例之間new一個Vue實例,作為Event Bus進行通信。

    Vuex:將狀態和方法提取到Vuex,完成共享

    • 跨級組件通信

    使用provide/inject

    Event Bus:同兄弟組件Event Bus通信

    Vuex:將狀態和方法提取到Vuex,完成共享

    Vue事件綁定原理說一下

    每一個Vue實例都是一個Event Bus,當子組件被創建的時候,父組件將事件傳遞給子組件,子組件初始化的時候是有$on方法將事件註冊到內部,在需要的時候使用$emit觸發函數,而對於原生native事件,使用addEventListener綁定到真實的DOM元素上。

    slot是什麼?有什麼作用?原理是什麼?

    slot又名插槽,是Vue的內容分發機制,組件內部的模板引擎使用slot元素作為承載分發內容的出口。插槽slot是子組件的一個模板標籤元素,而這一個標籤元素是否显示,以及怎麼显示是由父組件決定的。

    slot又分三類,默認插槽,具名插槽和作用域插槽。

    • 默認插槽:又名匿名查抄,當slot沒有指定name屬性值的時候一個默認显示插槽,一個組件內只有有一個匿名插槽。
    • 具名插槽:帶有具體名字的插槽,也就是帶有name屬性的slot,一個組件可以出現多個具名插槽。
    • 作用域插槽:默認插槽、具名插槽的一個變體,可以是匿名插槽,也可以是具名插槽,該插槽的不同點是在子組件渲染作用域插槽時,可以將子組件內部的數據傳遞給父組件,讓父組件根據子組件的傳遞過來的數據決定如何渲染該插槽。

    實現原理:當子組件vm實例化時,獲取到父組件傳入的slot標籤的內容,存放在vm.$slot中,默認插槽為vm.$slot.default,具名插槽為vm.$slot.xxx,xxx 為插槽名,當組件執行渲染函數時候,遇到slot標籤,使用$slot中的內容進行替換,此時可以為插槽傳遞數據,若存在數據,則可稱該插槽為作用域插槽。

    Vue模板渲染的原理是什麼?

    vue中的模板template無法被瀏覽器解析並渲染,因為這不屬於瀏覽器的標準,不是正確的html語法,所有需要將template轉化成一個JavaScript函數,這樣瀏覽器就可以執行這一個函數並渲染出對應的html元素,就可以讓視圖跑起來了,這一個轉化的過程,就成為模板編譯。

    模板編譯又分三個階段,解析parse,優化optimize,生成generate,最終生成可執行函數render。

    • parse階段:使用大量的正則表達式對template字符串進行解析,將標籤、指令、屬性等轉化為抽象語法樹AST。
    • optimize階段:遍歷AST,找到其中的一些靜態節點並進行標記,方便在頁面重渲染的時候進行diff比較時,直接跳過這一些靜態節點,優化runtime的性能。
    • generate階段:將最終的AST轉化為render函数字符串。

    template預編譯是什麼?

    對於 Vue 組件來說,模板編譯只會在組件實例化的時候編譯一次,生成渲染函數之後在也不會進行編譯。因此,編譯對組件的 runtime 是一種性能損耗。

    而模板編譯的目的僅僅是將template轉化為render function,這個過程,正好可以在項目構建的過程中完成,這樣可以讓實際組件在 runtime 時直接跳過模板渲染,進而提升性能,這個在項目構建的編譯template的過程,就是預編譯。

    那template和jsx的有什麼分別?

    對於 runtime 來說,只需要保證組件存在 render 函數即可,而我們有了預編譯之後,我們只需要保證構建過程中生成 render 函數就可以。

    在 webpack 中,我們使用vue-loader編譯.vue文件,內部依賴的vue-template-compiler模塊,在 webpack 構建過程中,將template預編譯成 render 函數。

    與 react 類似,在添加了jsx的語法糖解析器babel-plugin-transform-vue-jsx之後,就可以直接手寫render函數。

    所以,template和jsx的都是render的一種表現形式,不同的是:

    JSX相對於template而言,具有更高的靈活性,在複雜的組件中,更具有優勢,而 template 雖然顯得有些獃滯。但是 template 在代碼結構上更符合視圖與邏輯分離的習慣,更簡單、更直觀、更好維護。

    說一下什麼是Virtual DOM

    Virtual DOM 是 DOM 節點在 JavaScript 中的一種抽象數據結構,之所以需要虛擬DOM,是因為瀏覽器中操作DOM的代價比較昂貴,頻繁操作DOM會產生性能問題。虛擬DOM的作用是在每一次響應式數據發生變化引起頁面重渲染時,Vue對比更新前後的虛擬DOM,匹配找出盡可能少的需要更新的真實DOM,從而達到提升性能的目的。

    介紹一下Vue中的Diff算法

    在新老虛擬DOM對比時

    • 首先,對比節點本身,判斷是否為同一節點,如果不為相同節點,則刪除該節點重新創建節點進行替換
    • 如果為相同節點,進行patchVnode,判斷如何對該節點的子節點進行處理,先判斷一方有子節點一方沒有子節點的情況(如果新的children沒有子節點,將舊的子節點移除)
    • 比較如果都有子節點,則進行updateChildren,判斷如何對這些新老節點的子節點進行操作(diff核心)。
    • 匹配時,找到相同的子節點,遞歸比較子節點

    在diff中,只對同層的子節點進行比較,放棄跨級的節點比較,使得時間複雜從O(n^3)降低值O(n),也就是說,只有當新舊children都為多個子節點時才需要用核心的Diff算法進行同層級比較。

    key屬性的作用是什麼

    在對節點進行diff的過程中,判斷是否為相同節點的一個很重要的條件是key是否相等,如果是相同節點,則會盡可能的復用原有的DOM節點。所以key屬性是提供給框架在diff的時候使用的,而非開發者。

    說說Vue2.0和Vue3.0有什麼區別

    1. 重構響應式系統,使用Proxy替換Object.defineProperty,使用Proxy優勢:
    • 可直接監聽數組類型的數據變化
    • 監聽的目標為對象本身,不需要像Object.defineProperty一樣遍歷每個屬性,有一定的性能提升
    • 可攔截apply、ownKeys、has等13種方法,而Object.defineProperty不行
    • 直接實現對象屬性的新增/刪除
    1. 新增Composition API,更好的邏輯復用和代碼組織
    2. 重構 Virtual DOM
    • 模板編譯時的優化,將一些靜態節點編譯成常量
    • slot優化,將slot編譯為lazy函數,將slot的渲染的決定權交給子組件
    • 模板中內聯事件的提取並重用(原本每次渲染都重新生成內聯函數)
    1. 代碼結構調整,更便於Tree shaking,使得體積更小
    2. 使用Typescript替換Flow

    為什麼要新增Composition API,它能解決什麼問題

    Vue2.0中,隨着功能的增加,組件變得越來越複雜,越來越難維護,而難以維護的根本原因是Vue的API設計迫使開發者使用watch,computed,methods選項組織代碼,而不是實際的業務邏輯。

    另外Vue2.0缺少一種較為簡潔的低成本的機制來完成邏輯復用,雖然可以minxis完成邏輯復用,但是當mixin變多的時候,會使得難以找到對應的data、computed或者method來源於哪個mixin,使得類型推斷難以進行。

    所以Composition API的出現,主要是也是為了解決Option API帶來的問題,第一個是代碼組織問題,Compostion API可以讓開發者根據業務邏輯組織自己的代碼,讓代碼具備更好的可讀性和可擴展性,也就是說當下一個開發者接觸這一段不是他自己寫的代碼時,他可以更好的利用代碼的組織反推出實際的業務邏輯,或者根據業務邏輯更好的理解代碼。

    第二個是實現代碼的邏輯提取與復用,當然mixin也可以實現邏輯提取與復用,但是像前面所說的,多個mixin作用在同一個組件時,很難看出property是來源於哪個mixin,來源不清楚,另外,多個mixin的property存在變量命名衝突的風險。而Composition API剛好解決了這兩個問題。

    都說Composition API與react Hook很像,說說區別

    從React Hook的實現角度看,React Hook是根據useState調用的順序來確定下一次重渲染時的state是來源於哪個useState,所以出現了以下限制

    • 不能在循環、條件、嵌套函數中調用Hook
    • 必須確保總是在你的React函數的頂層調用Hook
    • useEffect、useMemo等函數必須手動確定依賴關係

    而Composition API是基於Vue的響應式系統實現的,與React Hook的相比

    • 聲明在setup函數內,一次組件實例化只調用一次setup,而React Hook每次重渲染都需要調用Hook,使得React的GC比Vue更有壓力,性能也相對於Vue來說也較慢
    • Compositon API的調用不需要顧慮調用順序,也可以在循環、條件、嵌套函數中使用
    • 響應式系統自動實現了依賴收集,進而組件的部分的性能優化由Vue內部自己完成,而React Hook需要手動傳入依賴,而且必須必須保證依賴的順序,讓useEffect、useMemo等函數正確的捕獲依賴變量,否則會由於依賴不正確使得組件性能下降。

    雖然Compositon API看起來比React Hook好用,但是其設計思想也是借鑒React Hook的。

    SSR有了解嗎?原理是什麼?

    在客戶端請求服務器的時候,服務器到數據庫中獲取到相關的數據,並且在服務器內部將Vue組件渲染成HTML,並且將數據、HTML一併返回給客戶端,這個在服務器將數據和組件轉化為HTML的過程,叫做服務端渲染SSR。

    而當客戶端拿到服務器渲染的HTML和數據之後,由於數據已經有了,客戶端不需要再一次請求數據,而只需要將數據同步到組件或者Vuex內部即可。除了數據意外,HTML也結構已經有了,客戶端在渲染組件的時候,也只需要將HTML的DOM節點映射到Virtual DOM即可,不需要重新創建DOM節點,這個將數據和HTML同步的過程,又叫做客戶端激活。

    使用SSR的好處:

    • 有利於seo:其實就是有利於爬蟲來爬你的頁面,因為部分頁面爬蟲是不支持執行JavaScript的,這種不支持執行JavaScript的爬蟲抓取到的非SSR的頁面會是一個空的HTML頁面,而有了SSR以後,這些爬蟲就可以獲取到完整的HTML結構的數據,進而收錄到搜索引擎中。

    • 白屏時間更短:相對於客戶端渲染,服務端渲染在瀏覽器請求URL之後已經得到了一個帶有數據的HTML文本,瀏覽器只需要解析HTML,直接構建DOM樹就可以。而客戶端渲染,需要先得到一個空的HTML頁面,這個時候頁面已經進入白屏,之後還需要經過加載並執行 JavaScript、請求後端服務器獲取數據、JavaScript 渲染頁面幾個過程才可以看到最後的頁面。特別是在複雜應用中,由於需要加載 JavaScript 腳本,越是複雜的應用,需要加載的 JavaScript 腳本就越多、越大,這會導致應用的首屏加載時間非常長,進而降低了體驗感。

    更多詳情查看徹底理解服務端渲染 – SSR原理

    結束

    面試官點了點頭,嗯呢,這小伙還可以,懂得還挺多,可以弄進來寫業務。

    我也暗自竊喜,幸虧沒問到我不會的,然後我坐那傻笑,笑着笑着,突然聽到我的鬧鈴響了,然後,我夢醒了。

    然後,新的搬磚的一天又開始了。

    原文來自:https://github.com/yacan8/blog/issues/31

    站長推薦

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

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

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