沐鳴平台_JavaScript如何獲取網頁的寬高,以及如何兼容(各種坑詳解)

很多場景下會需要獲取當前網頁的寬高來達到一些效果,但是獲取網頁的寬高這裏面還是有一點坑的,這裏我進行了總結和詳解

方式一:window.innerWidth / window.innerHeight

這種方式只支持IE9以及以上版本的瀏覽器


網頁高度,打開F12控制台當然高度會不同

方式二:document.documentElement.clientWidth

console.log(document.documentElement);
console.log(document.documentElement.clientWidth);
console.log(document.documentElement.clientHeight);

document.documentElement獲取到的是html,從而獲取到了整個網頁

可用於IE9以下,當然也支持IE9以及以上 可以用來兼容

方式三:混雜模式/怪異模式 下的寬高的獲取

怪異模式:沒有寫文檔聲明(就是第一行的那個< !DOCTYPE html >)就是怪異模式,這種模式下會有一些不同的渲染方式,感興趣的小夥伴可以去了解(這裏講解的獲取寬高就是怪異模式特點之一)。

console.log(document.compatMode);
console.log(document.body.clientWidth);
console.log(document.body.clientHeight);

這樣獲取的方式只有在混雜模式下可以正確獲取,在標準模式下不會報錯,但是獲取到的寬高不是正確的。
document.compatMode可以知道當前文檔是混雜還是標準(BackCompat混雜,css1Compat標準)

你以為結束了?
問題才出現!~
所以,問題來了:怎麼兼容?(別認為兼容不重要,不兼容就有很大的報錯風險啊喂!畢竟你不知道用戶會不會用IE6打開網頁@_@)
封裝:兼容了高低版本瀏覽器,標準/混雜模式

function getWid_Hei(){
let width,height;
if(window.innerWidth){
width = window.innerWidth;
height = window.innerHeight;
}else if(document.compatMode === "BackCompat"){
width = document.body.clientWidth;
height = document.body.clientHeight;
}else{
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
}
return {
width:width,
height:height
}
}
let {width,height} = getWid_Hei();
console.log(width,height);

作者:楓恭弘=叶 恭弘在夏天

原文鏈接:https://blog.csdn.net/weixin_44552302/article/details/116589722

站長推薦

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

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

沐鳴平台網址_34個JavaScript簡寫優化技術

開發者的生活總是在學習新的東西,跟上變化不應該比現在更難,我的動機是介紹所有 JavaScript 的最佳實踐,比如簡寫功能,作為一個前端開發者,我們必須知道,讓我們的生活在 2021 年變得更輕鬆。

你可能做了很長時間的 JavaScript 開發,但有時你可能沒有更新最新的特性,這些特性可以解決你的問題,而不需要做或編寫一些額外的代碼。這些技術可以幫助您編寫乾淨和優化的 JavaScript 代碼。此外,這些主題可以幫助你為 2021 年的 JavaScript 面試做準備。

1.如果有多個條件

我們可以在數組中存儲多個值,並且可以使用數組 include 方法。


     
  
  1. //Longhand
  2. if (
  3.   x === "abc" ||
  4.   x === "def" ||
  5.   x === "ghi" ||
  6.   x === "jkl"
  7. ) {
  8.   //logic
  9. }
  10. //Shorthand
  11. if (["abc", "def", "ghi", "jkl"].includes(x)) {
  12.   //logic
  13. }

2.如果為真…否則簡寫

這對於我們有 if-else 條件,裏面不包含更大的邏輯時,是一個較大的捷徑。我們可以簡單的使用三元運算符來實現這個簡寫。


     
  
  1. // Longhand
  2. let test: boolean;
  3. if (x > 100) {
  4.   test = true;
  5. } else {
  6.   test = false;
  7. }
  8. // Shorthand
  9. let test = x > 10 ? true : false;
  10. //or we can use directly
  11. let test = x > 10;
  12. console.log(test);

當我們有嵌套條件時,我們可以採用這種方式。


     
  
  1. let x = 300,
  2.   test2 =
  3.     x > 100
  4.       ? "greater 100"
  5.       : x < 50
  6.       ? "less 50"
  7.       : "between 50 and 100";
  8. console.log(test2); // "greater than 100"

3.聲明變量

當我們要聲明兩個具有共同值或共同類型的變量時,可以使用此簡寫形式。


     
  
  1. //Longhand
  2. let test1;
  3. let test2 = 1;
  4. //Shorthand
  5. let test1,
  6.   test2 = 1;

4.Null, Undefined,空檢查

當我們創建新的變量時,有時我們想檢查我們引用的變量的值是否為空或 undefined。JavaScript 確實有一個非常好的簡寫工具來實現這些功能。


     
  
  1. // Longhand
  2. if (test1 !== null || test1 !== undefined || test1 !== "") {
  3.   let test2 = test1;
  4. }
  5. // Shorthand
  6. let test2 = test1 || "";

5.null 值檢查和分配默認值


     
  
  1. let test1 = null,
  2.   test2 = test1 || "";
  3. console.log("null check", test2); // output will be ""
  4. 6.undefined 值檢查和分配默認值
  5. js
  6. let test1 = undefined,
  7.   test2 = test1 || "";
  8. console.log("undefined check", test2); // output will be ""

正常值檢查


     
  
  1. let test1 = "test",
  2.   test2 = test1 || "";
  3. console.log(test2); // output: 'test'

7.將值分配給多個變量

當我們處理多個變量並希望將不同的值分配給不同的變量時,此簡寫技術非常有用。


     
  
  1. //Longhand
  2. let test1, test2, test3;
  3. test1 = 1;
  4. test2 = 2;
  5. test3 = 3;
  6. //Shorthand
  7. let [test1, test2, test3] = [1, 2, 3];

8.賦值運算符簡寫

我們在編程中處理很多算術運算符,這是將運算符分配給 JavaScript 變量的有用技術之一。


     
  
  1. // Longhand
  2. test1 = test1 + 1;
  3. test2 = test2 - 1;
  4. test3 = test3 * 20;
  5. // Shorthand
  6. test1++;
  7. test2--;
  8. test3 *= 20;

9.如果存在簡寫

這是我們大家都在使用的常用簡寫之一,但仍然值得一提。


     
  
  1. // Longhand
  2. if (test1 === true) or if (test1 !== "") or if (test1 !== null)
  3. // Shorthand //it will check empty string,null and undefined too
  4. if (test1)

注意:如果 test1 有任何值,它將在 if 循環後進入邏輯,該運算符主要用於 null 或 undefined 的檢查。

10.多個條件的 AND(&&)運算符

如果僅在變量為 true 的情況下才調用函數,則可以使用 && 運算符。


     
  
  1. //Longhand
  2. if (test1) {
  3.   callMethod();
  4. }
  5. //Shorthand
  6. test1 && callMethod();

11.foreach 循環簡寫

這是迭代的常用簡寫技術之一。


     
  
  1. // Longhand
  2. for (var i = 0; i < testData.length; i++)
  3. // Shorthand
  4. for (let i in testData) or  for (let i of testData)

每個變量的數組


     
  
  1. function testData(element, index, array) {
  2.   console.log("test[" + index + "] = " + element);
  3. }
  4. [11, 24, 32].forEach(testData);
  5. // logs: test[0] = 11, test[1] = 24, test[2] = 32

12.return 中比較

我們也可以在 return 語句中使用比較。它將避免我們的 5 行代碼,並將它們減少到 1 行。


     
  
  1. // Longhand
  2. let test;
  3. function checkReturn() {
  4.   if (!(test === undefined)) {
  5.     return test;
  6.   } else {
  7.     return callMe("test");
  8.   }
  9. }
  10. var data = checkReturn();
  11. console.log(data); //output test
  12. function callMe(val) {
  13.   console.log(val);
  14. }
  15. // Shorthand
  16. function checkReturn() {
  17.   return test || callMe("test");
  18. }

13.箭頭函數


     
  
  1. //Longhand
  2. function add(a, b) {
  3.   return a + b;
  4. }
  5. //Shorthand
  6. const add = (a, b) => a + b;

更多示例。


     
  
  1. function callMe(name) {
  2.   console.log("Hello", name);
  3. }
  4. callMe = (name) => console.log("Hello", name);

14.短函數調用

我們可以使用三元運算符來實現這些功能。


     
  
  1. // Longhand
  2. function test1() {
  3.   console.log("test1");
  4. }
  5. function test2() {
  6.   console.log("test2");
  7. }
  8. var test3 = 1;
  9. if (test3 == 1) {
  10.   test1();
  11. } else {
  12.   test2();
  13. }
  14. // Shorthand
  15. (test3 === 1 ? test1 : test2)();

15. Switch 簡寫

我們可以將條件保存在鍵值對象中,並可以根據條件使用。


     
  
  1. // Longhand
  2. switch (data) {
  3.   case 1:
  4.     test1();
  5.     break;
  6.   case 2:
  7.     test2();
  8.     break;
  9.   case 3:
  10.     test();
  11.     break;
  12.   // And so on...
  13. }
  14. // Shorthand
  15. var data = {
  16.   1: test1,
  17.   2: test2,
  18.   3: test,
  19. };
  20. data[something] && data[something]();

16.隱式返回簡寫

使用箭頭函數,我們可以直接返回值,而不必編寫 return 語句。


     
  
  1. //longhand
  2. function calculate(diameter) {
  3.   return Math.PI * diameter
  4. }
  5. //shorthand
  6. calculate = diameter => (
  7.   Math.PI * diameter;
  8. )

17.小數基數指數


     
  
  1. // Longhand
  2. for (var i = 0; i < 10000; i++) { ... }
  3. // Shorthand
  4. for (var i = 0; i < 1e4; i++) {

18.默認參數值


     
  
  1. //Longhand
  2. function add(test1, test2) {
  3.   if (test1 === undefined) test1 = 1;
  4.   if (test2 === undefined) test2 = 2;
  5.   return test1 + test2;
  6. }
  7. //shorthand
  8. add = (test1 = 1, test2 = 2) => test1 + test2;
  9. add(); //output: 3

19.擴展運算符簡寫


     
  
  1. //longhand
  2. // joining arrays using concat
  3. const data = [1, 2, 3];
  4. const test = [4, 5, 6].concat(data);
  5. //shorthand
  6. // joining arrays
  7. const data = [1, 2, 3];
  8. const test = [4, 5, 6, ...data];
  9. console.log(test); // [ 4, 5, 6, 1, 2, 3]

對於克隆,我們也可以使用擴展運算符。


     
  
  1. //longhand
  2. // cloning arrays
  3. const test1 = [1, 2, 3];
  4. const test2 = test1.slice();
  5. //shorthand
  6. // cloning arrays
  7. const test1 = [1, 2, 3];
  8. const test2 = [...test1];

20.模板文字

如果您厭倦了在單個字符串中使用 + 來連接多個變量,那麼這種簡寫可以消除您的頭痛。


     
  
  1. //longhand
  2. const welcome = "Hi " + test1 + " " + test2 + ".";
  3. //shorthand
  4. const welcome = `Hi ${test1} ${test2}`;

21.多行字符串簡寫

當我們在代碼中處理多行字符串時,可以使用以下功能:


     
  
  1. //longhand
  2. const data =
  3.   "abc abc abc abc abc abc\n\t" +
  4.   "test test,test test test test\n\t";
  5. //shorthand
  6. const data = `abc abc abc abc abc abc
  7.          test test,test test test test`;

22.對象屬性分配


     
  
  1. let test1 = "a";
  2. let test2 = "b";
  3. //Longhand
  4. let obj = { test1: test1, test2: test2 };
  5. //Shorthand
  6. let obj = { test1, test2 };

23.將字符串轉換成数字


     
  
  1. //Longhand
  2. let test1 = parseInt("123");
  3. let test2 = parseFloat("12.3");
  4. //Shorthand
  5. let test1 = +"123";
  6. let test2 = +"12.3";

24.用解構簡寫


     
  
  1. //longhand
  2. const test1 = this.data.test1;
  3. const test2 = this.data.test2;
  4. const test2 = this.data.test3;
  5. //shorthand
  6. const { test1, test2, test3 } = this.data;

25.用 Array.find 簡寫

當我們確實有一個對象數組並且我們想要根據對象屬性查找特定對象時,find 方法確實很有用。


     
  
  1. const data = [
  2.   {
  3.     type: "test1",
  4.     name: "abc",
  5.   },
  6.   {
  7.     type: "test2",
  8.     name: "cde",
  9.   },
  10.   {
  11.     type: "test1",
  12.     name: "fgh",
  13.   },
  14. ];
  15. function findtest1(name) {
  16.   for (let i = 0; i < data.length; ++i) {
  17.     if (data[i].type === "test1" && data[i].name === name) {
  18.       return data[i];
  19.     }
  20.   }
  21. }
  22. //Shorthand
  23. filteredData = data.find(
  24.   (data) => data.type === "test1" && data.name === "fgh"
  25. );
  26. console.log(filteredData); // { type: 'test1', name: 'fgh' }

26.查找條件簡寫

如果我們有代碼來檢查類型,根據類型需要調用不同的方法,我們可以選擇使用多個 else ifs 或者 switch,但是如果我們有比這更好的簡寫方法呢?


     
  
  1. // Longhand
  2. if (type === "test1") {
  3.   test1();
  4. } else if (type === "test2") {
  5.   test2();
  6. } else if (type === "test3") {
  7.   test3();
  8. } else if (type === "test4") {
  9.   test4();
  10. } else {
  11.   throw new Error("Invalid value " + type);
  12. }
  13. // Shorthand
  14. var types = {
  15.   test1: test1,
  16.   test2: test2,
  17.   test3: test3,
  18.   test4: test4,
  19. };
  20. var func = types[type];
  21. !func && throw new Error("Invalid value " + type);
  22. func();

27.按位索引簡寫

當我們遍曆數組以查找特定值時,我們確實使用 indexOf() 方法,如果找到更好的方法該怎麼辦?讓我們看看這個例子。


     
  
  1. //longhand
  2. if (arr.indexOf(item) > -1) {
  3.   // item found
  4. }
  5. if (arr.indexOf(item) === -1) {
  6.   // item not found
  7. }
  8. //shorthand
  9. if (~arr.indexOf(item)) {
  10.   // item found
  11. }
  12. if (!~arr.indexOf(item)) {
  13.   // item not found
  14. }

按位(〜)運算符將返回除-1 以外的任何值的真實值。否定它就像做 ~~ 一樣簡單。另外,我們也可以使用 include() 函數:


     
  
  1. if (arr.includes(item)) {
  2.   // true if the item found
  3. }

28.Object.entries()

此函數有助於將對象轉換為對象數組。


     
  
  1. const data = { test1: "abc", test2: "cde", test3: "efg" };
  2. const arr = Object.entries(data);
  3. console.log(arr);
  4. /** Output:
  5. [ [ 'test1', 'abc' ],
  6.   [ 'test2', 'cde' ],
  7.   [ 'test3', 'efg' ]
  8. ]
  9. **/

29.Object.values()

這也是 ES8 中引入的一項新功能,該功能執行與 Object.entries() 類似的功能,但沒有關鍵部分:


     
  
  1. const data = { test1: "abc", test2: "cde" };
  2. const arr = Object.values(data);
  3. console.log(arr);
  4. /** Output:
  5. [ 'abc', 'cde']
  6. **/

30.雙按位簡寫

雙重 NOT 按位運算符方法僅適用於 32 位整數)


     
  
  1. // Longhand
  2. Math.floor(1.9) === 1; // true
  3. // Shorthand
  4. ~~1.9 === 1; // true

31.重複一個字符串多次

要一次又一次地重複相同的字符,我們可以使用 for 循環並將它們添加到同一循環中,但是如果我們有一個簡寫方法呢?


     
  
  1. //longhand
  2. let test = "";
  3. for (let i = 0; i < 5; i++) {
  4.   test += "test ";
  5. }
  6. console.log(str); // test test test test test
  7. //shorthand
  8. "test ".repeat(5);

32.在數組中查找最大值和最小值


     
  
  1. const arr = [1, 2, 3];
  2. Math.max(…arr); // 3
  3. Math.min(…arr); // 1

33.從字符串中獲取字符


     
  
  1. let str = 'abc';
  2. //Longhand
  3. str.charAt(2); // c
  4. //Shorthand
  5. Note: If we know the index of the array then we can directly use index insted of character.If we are not sure about index it can throw undefined
  6. str[2]; // c

34.數學指數冪函數的簡寫


     
  
  1. //longhand
  2. Math.pow(2, 3); // 8
  3. //shorthand
  4. 2 ** 3; // 8

原文作者:Atit

翻譯:張張

站長推薦

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

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

,

開發者的生活總是在學習新的東西,跟上變化不應該比現在更難,我的動機是介紹所有 JavaScript 的最佳實踐,比如簡寫功能,作為一個前端開發者,我們必須知道,讓我們的生活在 2021 年變得更輕鬆。

你可能做了很長時間的 JavaScript 開發,但有時你可能沒有更新最新的特性,這些特性可以解決你的問題,而不需要做或編寫一些額外的代碼。這些技術可以幫助您編寫乾淨和優化的 JavaScript 代碼。此外,這些主題可以幫助你為 2021 年的 JavaScript 面試做準備。

1.如果有多個條件

我們可以在數組中存儲多個值,並且可以使用數組 include 方法。


  
  
  1. //Longhand
  2. if (
  3.   x === "abc" ||
  4.   x === "def" ||
  5.   x === "ghi" ||
  6.   x === "jkl"
  7. ) {
  8.   //logic
  9. }
  10. //Shorthand
  11. if (["abc", "def", "ghi", "jkl"].includes(x)) {
  12.   //logic
  13. }

2.如果為真…否則簡寫

這對於我們有 if-else 條件,裏面不包含更大的邏輯時,是一個較大的捷徑。我們可以簡單的使用三元運算符來實現這個簡寫。


  
  
  1. // Longhand
  2. let test: boolean;
  3. if (x > 100) {
  4.   test = true;
  5. } else {
  6.   test = false;
  7. }
  8. // Shorthand
  9. let test = x > 10 ? true : false;
  10. //or we can use directly
  11. let test = x > 10;
  12. console.log(test);

當我們有嵌套條件時,我們可以採用這種方式。


  
  
  1. let x = 300,
  2.   test2 =
  3.     x > 100
  4.       ? "greater 100"
  5.       : x < 50
  6.       ? "less 50"
  7.       : "between 50 and 100";
  8. console.log(test2); // "greater than 100"

3.聲明變量

當我們要聲明兩個具有共同值或共同類型的變量時,可以使用此簡寫形式。


  
  
  1. //Longhand
  2. let test1;
  3. let test2 = 1;
  4. //Shorthand
  5. let test1,
  6.   test2 = 1;

4.Null, Undefined,空檢查

當我們創建新的變量時,有時我們想檢查我們引用的變量的值是否為空或 undefined。JavaScript 確實有一個非常好的簡寫工具來實現這些功能。


  
  
  1. // Longhand
  2. if (test1 !== null || test1 !== undefined || test1 !== "") {
  3.   let test2 = test1;
  4. }
  5. // Shorthand
  6. let test2 = test1 || "";

5.null 值檢查和分配默認值


  
  
  1. let test1 = null,
  2.   test2 = test1 || "";
  3. console.log("null check", test2); // output will be ""
  4. 6.undefined 值檢查和分配默認值
  5. js
  6. let test1 = undefined,
  7.   test2 = test1 || "";
  8. console.log("undefined check", test2); // output will be ""

正常值檢查


  
  
  1. let test1 = "test",
  2.   test2 = test1 || "";
  3. console.log(test2); // output: 'test'

7.將值分配給多個變量

當我們處理多個變量並希望將不同的值分配給不同的變量時,此簡寫技術非常有用。


  
  
  1. //Longhand
  2. let test1, test2, test3;
  3. test1 = 1;
  4. test2 = 2;
  5. test3 = 3;
  6. //Shorthand
  7. let [test1, test2, test3] = [1, 2, 3];

8.賦值運算符簡寫

我們在編程中處理很多算術運算符,這是將運算符分配給 JavaScript 變量的有用技術之一。


  
  
  1. // Longhand
  2. test1 = test1 + 1;
  3. test2 = test2 - 1;
  4. test3 = test3 * 20;
  5. // Shorthand
  6. test1++;
  7. test2--;
  8. test3 *= 20;

9.如果存在簡寫

這是我們大家都在使用的常用簡寫之一,但仍然值得一提。


  
  
  1. // Longhand
  2. if (test1 === true) or if (test1 !== "") or if (test1 !== null)
  3. // Shorthand //it will check empty string,null and undefined too
  4. if (test1)

注意:如果 test1 有任何值,它將在 if 循環後進入邏輯,該運算符主要用於 null 或 undefined 的檢查。

10.多個條件的 AND(&&)運算符

如果僅在變量為 true 的情況下才調用函數,則可以使用 && 運算符。


  
  
  1. //Longhand
  2. if (test1) {
  3.   callMethod();
  4. }
  5. //Shorthand
  6. test1 && callMethod();

11.foreach 循環簡寫

這是迭代的常用簡寫技術之一。


  
  
  1. // Longhand
  2. for (var i = 0; i < testData.length; i++)
  3. // Shorthand
  4. for (let i in testData) or  for (let i of testData)

每個變量的數組


  
  
  1. function testData(element, index, array) {
  2.   console.log("test[" + index + "] = " + element);
  3. }
  4. [11, 24, 32].forEach(testData);
  5. // logs: test[0] = 11, test[1] = 24, test[2] = 32

12.return 中比較

我們也可以在 return 語句中使用比較。它將避免我們的 5 行代碼,並將它們減少到 1 行。


  
  
  1. // Longhand
  2. let test;
  3. function checkReturn() {
  4.   if (!(test === undefined)) {
  5.     return test;
  6.   } else {
  7.     return callMe("test");
  8.   }
  9. }
  10. var data = checkReturn();
  11. console.log(data); //output test
  12. function callMe(val) {
  13.   console.log(val);
  14. }
  15. // Shorthand
  16. function checkReturn() {
  17.   return test || callMe("test");
  18. }

13.箭頭函數


  
  
  1. //Longhand
  2. function add(a, b) {
  3.   return a + b;
  4. }
  5. //Shorthand
  6. const add = (a, b) => a + b;

更多示例。


  
  
  1. function callMe(name) {
  2.   console.log("Hello", name);
  3. }
  4. callMe = (name) => console.log("Hello", name);

14.短函數調用

我們可以使用三元運算符來實現這些功能。


  
  
  1. // Longhand
  2. function test1() {
  3.   console.log("test1");
  4. }
  5. function test2() {
  6.   console.log("test2");
  7. }
  8. var test3 = 1;
  9. if (test3 == 1) {
  10.   test1();
  11. } else {
  12.   test2();
  13. }
  14. // Shorthand
  15. (test3 === 1 ? test1 : test2)();

15. Switch 簡寫

我們可以將條件保存在鍵值對象中,並可以根據條件使用。


  
  
  1. // Longhand
  2. switch (data) {
  3.   case 1:
  4.     test1();
  5.     break;
  6.   case 2:
  7.     test2();
  8.     break;
  9.   case 3:
  10.     test();
  11.     break;
  12.   // And so on...
  13. }
  14. // Shorthand
  15. var data = {
  16.   1: test1,
  17.   2: test2,
  18.   3: test,
  19. };
  20. data[something] && data[something]();

16.隱式返回簡寫

使用箭頭函數,我們可以直接返回值,而不必編寫 return 語句。


  
  
  1. //longhand
  2. function calculate(diameter) {
  3.   return Math.PI * diameter
  4. }
  5. //shorthand
  6. calculate = diameter => (
  7.   Math.PI * diameter;
  8. )

17.小數基數指數


  
  
  1. // Longhand
  2. for (var i = 0; i < 10000; i++) { ... }
  3. // Shorthand
  4. for (var i = 0; i < 1e4; i++) {

18.默認參數值


  
  
  1. //Longhand
  2. function add(test1, test2) {
  3.   if (test1 === undefined) test1 = 1;
  4.   if (test2 === undefined) test2 = 2;
  5.   return test1 + test2;
  6. }
  7. //shorthand
  8. add = (test1 = 1, test2 = 2) => test1 + test2;
  9. add(); //output: 3

19.擴展運算符簡寫


  
  
  1. //longhand
  2. // joining arrays using concat
  3. const data = [1, 2, 3];
  4. const test = [4, 5, 6].concat(data);
  5. //shorthand
  6. // joining arrays
  7. const data = [1, 2, 3];
  8. const test = [4, 5, 6, ...data];
  9. console.log(test); // [ 4, 5, 6, 1, 2, 3]

對於克隆,我們也可以使用擴展運算符。


  
  
  1. //longhand
  2. // cloning arrays
  3. const test1 = [1, 2, 3];
  4. const test2 = test1.slice();
  5. //shorthand
  6. // cloning arrays
  7. const test1 = [1, 2, 3];
  8. const test2 = [...test1];

20.模板文字

如果您厭倦了在單個字符串中使用 + 來連接多個變量,那麼這種簡寫可以消除您的頭痛。


  
  
  1. //longhand
  2. const welcome = "Hi " + test1 + " " + test2 + ".";
  3. //shorthand
  4. const welcome = `Hi ${test1} ${test2}`;

21.多行字符串簡寫

當我們在代碼中處理多行字符串時,可以使用以下功能:


  
  
  1. //longhand
  2. const data =
  3.   "abc abc abc abc abc abc\n\t" +
  4.   "test test,test test test test\n\t";
  5. //shorthand
  6. const data = `abc abc abc abc abc abc
  7.          test test,test test test test`;

22.對象屬性分配


  
  
  1. let test1 = "a";
  2. let test2 = "b";
  3. //Longhand
  4. let obj = { test1: test1, test2: test2 };
  5. //Shorthand
  6. let obj = { test1, test2 };

23.將字符串轉換成数字


  
  
  1. //Longhand
  2. let test1 = parseInt("123");
  3. let test2 = parseFloat("12.3");
  4. //Shorthand
  5. let test1 = +"123";
  6. let test2 = +"12.3";

24.用解構簡寫


  
  
  1. //longhand
  2. const test1 = this.data.test1;
  3. const test2 = this.data.test2;
  4. const test2 = this.data.test3;
  5. //shorthand
  6. const { test1, test2, test3 } = this.data;

25.用 Array.find 簡寫

當我們確實有一個對象數組並且我們想要根據對象屬性查找特定對象時,find 方法確實很有用。


  
  
  1. const data = [
  2.   {
  3.     type: "test1",
  4.     name: "abc",
  5.   },
  6.   {
  7.     type: "test2",
  8.     name: "cde",
  9.   },
  10.   {
  11.     type: "test1",
  12.     name: "fgh",
  13.   },
  14. ];
  15. function findtest1(name) {
  16.   for (let i = 0; i < data.length; ++i) {
  17.     if (data[i].type === "test1" && data[i].name === name) {
  18.       return data[i];
  19.     }
  20.   }
  21. }
  22. //Shorthand
  23. filteredData = data.find(
  24.   (data) => data.type === "test1" && data.name === "fgh"
  25. );
  26. console.log(filteredData); // { type: 'test1', name: 'fgh' }

26.查找條件簡寫

如果我們有代碼來檢查類型,根據類型需要調用不同的方法,我們可以選擇使用多個 else ifs 或者 switch,但是如果我們有比這更好的簡寫方法呢?


  
  
  1. // Longhand
  2. if (type === "test1") {
  3.   test1();
  4. } else if (type === "test2") {
  5.   test2();
  6. } else if (type === "test3") {
  7.   test3();
  8. } else if (type === "test4") {
  9.   test4();
  10. } else {
  11.   throw new Error("Invalid value " + type);
  12. }
  13. // Shorthand
  14. var types = {
  15.   test1: test1,
  16.   test2: test2,
  17.   test3: test3,
  18.   test4: test4,
  19. };
  20. var func = types[type];
  21. !func && throw new Error("Invalid value " + type);
  22. func();

27.按位索引簡寫

當我們遍曆數組以查找特定值時,我們確實使用 indexOf() 方法,如果找到更好的方法該怎麼辦?讓我們看看這個例子。


  
  
  1. //longhand
  2. if (arr.indexOf(item) > -1) {
  3.   // item found
  4. }
  5. if (arr.indexOf(item) === -1) {
  6.   // item not found
  7. }
  8. //shorthand
  9. if (~arr.indexOf(item)) {
  10.   // item found
  11. }
  12. if (!~arr.indexOf(item)) {
  13.   // item not found
  14. }

按位(〜)運算符將返回除-1 以外的任何值的真實值。否定它就像做 ~~ 一樣簡單。另外,我們也可以使用 include() 函數:


  
  
  1. if (arr.includes(item)) {
  2.   // true if the item found
  3. }

28.Object.entries()

此函數有助於將對象轉換為對象數組。


  
  
  1. const data = { test1: "abc", test2: "cde", test3: "efg" };
  2. const arr = Object.entries(data);
  3. console.log(arr);
  4. /** Output:
  5. [ [ 'test1', 'abc' ],
  6.   [ 'test2', 'cde' ],
  7.   [ 'test3', 'efg' ]
  8. ]
  9. **/

29.Object.values()

這也是 ES8 中引入的一項新功能,該功能執行與 Object.entries() 類似的功能,但沒有關鍵部分:


  
  
  1. const data = { test1: "abc", test2: "cde" };
  2. const arr = Object.values(data);
  3. console.log(arr);
  4. /** Output:
  5. [ 'abc', 'cde']
  6. **/

30.雙按位簡寫

雙重 NOT 按位運算符方法僅適用於 32 位整數)


  
  
  1. // Longhand
  2. Math.floor(1.9) === 1; // true
  3. // Shorthand
  4. ~~1.9 === 1; // true

31.重複一個字符串多次

要一次又一次地重複相同的字符,我們可以使用 for 循環並將它們添加到同一循環中,但是如果我們有一個簡寫方法呢?


  
  
  1. //longhand
  2. let test = "";
  3. for (let i = 0; i < 5; i++) {
  4.   test += "test ";
  5. }
  6. console.log(str); // test test test test test
  7. //shorthand
  8. "test ".repeat(5);

32.在數組中查找最大值和最小值


  
  
  1. const arr = [1, 2, 3];
  2. Math.max(…arr); // 3
  3. Math.min(…arr); // 1

33.從字符串中獲取字符


  
  
  1. let str = 'abc';
  2. //Longhand
  3. str.charAt(2); // c
  4. //Shorthand
  5. Note: If we know the index of the array then we can directly use index insted of character.If we are not sure about index it can throw undefined
  6. str[2]; // c

34.數學指數冪函數的簡寫


  
  
  1. //longhand
  2. Math.pow(2, 3); // 8
  3. //shorthand
  4. 2 ** 3; // 8

沐鳴網址_webgl變換:深入圖形平移

在以前的文章里,不管是繪製圖形,繪製點亦或者是改變色值,所有的內容都是靜態的。

在 webgl 里,圖形的運動分為 平移、旋轉、縮放 三種類型。

接下來,我們會從零基礎開始,一點一點來深入了解圖形如何進行運動。

首先來從零開始了解下圖形的平移

1. 圖形平移

首先我們來看如何實現圖形的平移操作。

平移的操作就是將圖形的原始坐標加上對應的移動距離。首先來看下平移的實現

const vertexShaderSource = "" +
      "attribute vec4 apos;" + // 定義一個坐標
      "uniform float x;" + // 處理 x 軸移動
      "uniform float y;" + // 處理 y 軸移動
      "void main(){" +
      " gl_Position.x = apos.x + x;" +
      " gl_Position.y = apos.y + y;" +
      " gl_Position.z = 0.0;" + // z軸固定
      " gl_Position.w = 1.0;" +
      "}";
const fragmentShaderSource = "" +
      "void main(){" +
      " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
      "}";

// initShader已經實現了很多次,本次就不再贅述了
const program = initShader(gl,vertexShaderSource,fragmentShaderSource);

const buffer = gl.createBuffer();
const data = new Float32Array([
  0.0,0.0,
  -0.5,-0.5,
  0.5,-0.5,
]);

gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);

const aposlocation = gl.getAttribLocation(program,'apos');
const xlocation = gl.getUniformLocation(program,'x');
const ylocation = gl.getUniformLocation(program,'y');

gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(aposlocation);

let x = 0.0;
let y = 0.0;
function run () {
  gl.uniform1f(xlocation,x += 0.01);
  gl.uniform1f(ylocation,y += 0.01);

  gl.drawArrays(gl.TRIANGLES,0,3);
  // 使用此方法實現一個動畫
  requestAnimationFrame(run)
}
run()

解釋:

  • 首先聲明一個變量 x 和變量 y ,用來處理 x軸 和 y軸 的坐標。這裏使用的是 uniform 變量,因為平移的操作對於圖形上的所有頂點都有影響。
  • 通過 gl_Position.[xyzw] 來分別設置 x、y、z、w 的值。用於改變圖形位置。
  • 使用 gl.uniform1f 來為 x 和 y 賦值
  • 使用 requestAnimationFrame 實現一個緩動動畫。方便觀察效果。
  • 其他的操作,緩衝區,繪製,賦值,激活,

可以看到,這樣處理圖形移動的話很好理解,但是因為一個移動,我們聲明了兩個 uniform 變量來實現。並且分開設置的 xyz 坐標,非常的不方便。

所以,在處理webgl變換(平移、縮放、旋轉)的時候,通常使用矩陣來實現。接下來就來看看,如何使用矩陣實現圖形的平移。

2. 平移矩陣

推導平移矩陣的步驟:

  • 獲取平移前後的圖形坐標(三維)
  • 計算平移前後的差值
  • 帶入到平移矩陣
  • 處理圖形頂點
  • 獲得平移后的圖形

2.1 平移矩陣的推導

首先讓我們來看一幅圖片。

這幅圖片的意義就是我們將橙色的三角形移動到藍色虛線三角形處。

移動之後的藍色虛線三角形的三個坐標分別為

  • x’ = x + x1
  • y’ = y + y1
  • z’ = z + z1
  • w=1 齊次坐標為1

2.2 獲得平移矩陣

在 webgl 中,通常使用矩陣來實現圖形變換。下面我們來看看矩陣如何表示。

左側是平移之前的原始坐標,中間的是一個平移矩陣,經過兩者相乘,可以得到一個平移之後的坐標。

現在我們來看下平移矩陣如何計算得出

首先通過上述圖片中的矩陣我們來得到幾個方程式。用左側的列分別乘矩陣的行,可以得到一下公式

  • ax + by + cz + w = x’
  • ex + fy + gz + h = y’
  • ix + jy + kz + l = z’
  • mx + ny + oz + p = w’

公式合併:

第一節 里的四個方程式和第二節里的四個方程式合併,可以得到如下結果:

  • ax + by + cz + w = x + x1’:只有當 a = 1,b = c = 0, w = x1 的時候,等式左右兩邊成立
  • ex + fy + gz + h = y + y1’:只有當 f = 1, e = g = 0, h = y1 的時候,等式左右兩邊成立
  • ix + jy + kz + l = z + z1’:只有當 k = 1,i = j = 0, l = z1 的時候,等式左右兩邊成立
  • mx + ny + oz + p = 1’:只有當 m = n = o = 0, p = 1 的時候,等式左右兩邊成立

經過上述方程式,可以得到一個平移的矩陣:

| 1 0 0 x |

| 0 1 0 y |

| 0 0 1 z |

| 0 0 0 1 |

之後將平移矩陣和原始坐標相乘,就可以得到平移之後的坐標。

3. 矩陣實戰

來看看使用矩陣如何處理圖形的平移。

第一步,創建着色器源代碼
const vertexShaderSource = "" +
      "attribute vec4 apos;" +
      "uniform mat4 mat;" + // 創建一個 uniform 變量,代表平移矩陣
      "void main(){" +
      " gl_Position = mat * apos;" + // 矩陣與原始坐標相乘
      "}";
const fragmentShaderSource = "" +
      "void main(){" +
      " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
      "}";
第二步,創建平移矩陣
let Tx = 0.1;    //x坐標的位置
let Ty = 0.1;    //y坐標的位置
let Tz = 0.0;    //z坐標的位置
let Tw = 1.0;    //差值
const mat = new Float32Array([
  1.0,0.0,0.0,0.0,
  0.0,1.0,0.0,0.0,
  0.0,0.0,1.0,0.0,
  Tx,Ty,Tz,Tw,
]);

這裏可以看到,使用的矩陣和我們推導出來的矩陣不太一樣,推導的平移矩陣里 xyzw 位於矩陣的右側,現在是位於矩陣的底部,這是為什麼呢?

這是因為在 webgl 中,矩陣的使用需要按照 左上右下 的對角線做一次翻轉。所以使用的矩陣,xyzw 位於底部

第三步,繪製一個三角形
const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
const aposlocation = gl.getAttribLocation(program,'apos');
const data =  new Float32Array([
  0.0,0.0,
  -.3,-.3,
  .3,-.3
]);

const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);

gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(aposlocation);

gl.drawArrays(gl.TRIANGLES,0,3); // 第五步的時候會重寫
第四步,獲取矩陣變量,給矩陣賦值
const matlocation = gl.getUniformLocation(program,'mat');
gl.uniformMatrix4fv(matlocation,false,mat);

這裏使用 gl.uniformMatrix4fv 來給矩陣賦值。

第五步,添加緩動動畫
function run () {
  Tx += 0.01
  Ty += 0.01
  const mat = new Float32Array([
    1.0,0.0,0.0,0.0,
    0.0,1.0,0.0,0.0,
    0.0,0.0,1.0,0.0,
    Tx,Ty,Tz,Tw,
  ]);
  gl.uniformMatrix4fv(matlocation,false,mat);
  gl.drawArrays(gl.TRIANGLES,0,3);

  // 使用此方法實現一個動畫
  requestAnimationFrame(run)
}
run()

4. 完整代碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<canvas id="webgl" width="500" height="500"></canvas>
<script>
  const gl = document.getElementById('webgl').getContext('webgl');
  const vertexShaderSource = "" +
    "attribute vec4 apos;" +
    "uniform mat4 mat;" +
    "void main(){" +
    " gl_Position = mat * apos;" +
    "}";
  const fragmentShaderSource = "" +
    "void main(){" +
    " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
    "}";

  const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
  const aposlocation = gl.getAttribLocation(program,'apos');
  const matlocation = gl.getUniformLocation(program,'mat');

  const data =  new Float32Array([
    0.0,0.0,
    -.3,-.3,
    .3,-.3
  ]);
  const buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
  gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);

  gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
  gl.enableVertexAttribArray(aposlocation);

  let Tx = 0.1;    //x坐標的位置
  let Ty = 0.1;    //y坐標的位置
  let Tz = 0.0;    //z坐標的位置
  let Tw = 1.0;    //差值
  function run () {
    Tx += 0.01
    Ty += 0.01
    const mat = new Float32Array([
      1.0,0.0,0.0,0.0,
      0.0,1.0,0.0,0.0,
      0.0,0.0,1.0,0.0,
      Tx,Ty,Tz,Tw,
    ]);
    gl.uniformMatrix4fv(matlocation,false,mat);
    gl.drawArrays(gl.TRIANGLES,0,3);

    // 使用此方法實現一個動畫
    requestAnimationFrame(run)
  }
  run()
  function initShader(gl,vertexShaderSource,fragmentShaderSource){
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

    gl.shaderSource(vertexShader,vertexShaderSource);
    gl.shaderSource(fragmentShader,fragmentShaderSource);

    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);

    const program = gl.createProgram();

    gl.attachShader(program,vertexShader);
    gl.attachShader(program,fragmentShader)

    gl.linkProgram(program);
    gl.useProgram(program);
    return program;
  }
</script>
</body>
</html>

至此,通過矩陣控製圖形移動就全部實現完成了。

站長推薦

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

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

沐鳴娛樂業務:_HarmonyOS 華為鴻蒙概述

系統定位

HarmonyOS是一款“面向未來”、面向全場景(移動辦公、運動健康、社交通信、媒體娛樂等)的分佈式操作系統。在傳統的單設備系統能力的基礎上,HarmonyOS提出了基於同一套系統能力、適配多種終端形態的分佈式理念,能夠支持多種終端設備。

對消費者而言,HarmonyOS能夠將生活場景中的各類終端進行能力整合,可以實現不同的終端設備之間的快速連接、能力互助、資源共享,匹配合適的設備、提供流暢的全場景體驗。

對應用開發者而言,HarmonyOS採用了多種分佈式技術,使得應用程序的開發實現與不同終端設備的形態差異無關。這能夠讓開發者聚焦上層業務邏輯,更加便捷、高效地開發應用。

對設備開發者而言,HarmonyOS採用了組件化的設計方案,可以根據設備的資源能力和業務特徵進行靈活裁剪,滿足不同形態的終端設備對於操作系統的要求。

技術架構

HarmonyOS整體遵從分層設計,從下向上依次為:內核層、系統服務層、框架層和應用層。系統功能按照“系統 > 子系統 > 功能/模塊”逐級展開,在多設備部署場景下,支持根據實際需求裁剪某些非必要的子系統或功能/模塊。HarmonyOS技術架構如圖1所示。

內核層

內核子系統:HarmonyOS採用多內核設計,支持針對不同資源受限設備選用適合的OS內核。內核抽象層(KAL,Kernel Abstract Layer)通過屏蔽多內核差異,對上層提供基礎的內核能力,包括進程/線程管理、內存管理、文件系統、網絡管理和外設管理等。

驅動子系統:HarmonyOS驅動框架(HDF)是HarmonyOS硬件生態開放的基礎,提供統一外設訪問能力和驅動開發、管理框架。

系統服務層

系統服務層是HarmonyOS的核心能力集合,通過框架層對應用程序提供服務。該層包含以下幾個部分:

系統基本能力子系統集:為分佈式應用在HarmonyOS多設備上的運行、調度、遷移等操作提供了基礎能力,由分佈式軟總線、分佈式數據管理、分佈式任務調度、方舟多語言運行時、公共基礎庫、多模輸入、圖形、安全、AI等子系統組成。其中,方舟運行時提供了C/C++/js多語言運行時和基礎的系統類庫,也為使用方舟編譯器靜態化的Java程序(即應用程序或框架層中使用Java語言開發的部分)提供運行時。

基礎軟件服務子系統集:為HarmonyOS提供公共的、通用的軟件服務,由事件通知、電話、多媒體、DFX(Design For X) 、MSDP&DV等子系統組成。

增強軟件服務子系統集:為HarmonyOS提供針對不同設備的、差異化的能力增強型軟件服務,由智慧屏專有業務、穿戴專有業務、IoT專有業務等子系統組成。

硬件服務子系統集:為HarmonyOS提供硬件服務,由位置服務、生物特徵識別、穿戴專有硬件服務、IoT專有硬件服務等子系統組成。

根據不同設備形態的部署環境,基礎軟件服務子系統集、增強軟件服務子系統集、硬件服務子系統集內部可以按子系統粒度裁剪,每個子系統內部又可以按功能粒度裁剪。

框架層

框架層為HarmonyOS的應用程序提供了Java/C/C++/js等多語言的用戶程序框架和Ability框架,以及各種軟硬件服務對外開放的多語言框架API;同時為採用HarmonyOS的設備提供了C/C++/JS等多語言的框架API,不同設備支持的API與系統的組件化裁剪程度相關。

應用層

應用層包括系統應用和第三方非系統應用。HarmonyOS的應用由一個或多個FA(Feature Ability)或PA(Particle Ability)組成。其中,FA有UI界面,提供與用戶交互的能力;而PA無UI界面,提供後台運行任務的能力以及統一的數據訪問抽象。基於FA/PA開發的應用,能夠實現特定的業務功能,支持跨設備調度與分發,為用戶提供一致、高效的應用體驗。

站長推薦

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

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

沐鳴娛樂怎麼樣?_Web 動畫幀率(FPS)計算

我們知道,動畫其實是由一幀一幀的圖像構成的。有 Web 動畫那麼就會存在該動畫在播放運行時的幀率。而幀率在不同設備不同情況下又是不一樣的。

有的時候,一些複雜或者重要動畫,我們需要實時監控它們的幀率,或者說是需要知道它們在不同設備的運行狀況,從而更好的優化它們,本文就是介紹 Web 動畫幀率(FPS)計算方法。

 

流暢動畫的標準

首先,理清一些概念。FPS 表示的是每秒鐘畫面更新次數。我們平時所看到的連續畫面都是由一幅幅靜止畫面組成的,每幅畫面稱為一幀,FPS 是描述“幀”變化速度的物理量。

理論上說,FPS 越高,動畫會越流暢,目前大多數設備的屏幕刷新率為 60 次/秒,所以通常來講 FPS 為 60 frame/s 時動畫效果最好,也就是每幀的消耗時間為 16.67ms。

當然,經常玩 FPS 遊戲的朋友肯定知道,吃雞/CSGO 等 FPS 遊戲推薦使用 144HZ 刷新率的显示器,144Hz 显示器特指每秒的刷新率達到 144Hz 的显示器。相較於普通显示器每秒60的刷新速度,畫面显示更加流暢。因此144Hz显示器比較適用於視角時常保持高速運動的第一人稱射擊遊戲。

不過,這個只是显示器提供的高刷新率特性,對於我們 Web 動畫而言,是否支持還要看瀏覽器,而大多數瀏覽器刷新率為 60 次/秒。

直觀感受,不同幀率的體驗:

  • 幀率能夠達到 50 ~ 60 FPS 的動畫將會相當流暢,讓人倍感舒適;
  • 幀率在 30 ~ 50 FPS 之間的動畫,因各人敏感程度不同,舒適度因人而異;
  • 幀率在 30 FPS 以下的動畫,讓人感覺到明顯的卡頓和不適感;
  • 幀率波動很大的動畫,亦會使人感覺到卡頓。

OK,那麼我們該如何準確的獲取我們頁面動畫當前的 FPS 值呢?

 

法一:藉助 Chrome 開發者工具

Chrome 提供給開發者的功能十分強大,在開發者工具中,我們進行如下選擇調出 FPS meter 選項:

通過這個按鈕,可以開啟頁面實時 Frame Rate (幀率) 觀測及頁面 GPU 使用率。

缺點

但是這個方法缺點太多了,

  • 這個只能一次觀測一到幾個頁面,而且需要人工實時觀測
  • 數據只能是主觀感受,並沒有一個十分精確的數據不斷上報或者被收集

因此,我們需要更加智能的方法。

法二:藉助 Frame Timing API

在介紹下面這種方法前,繼續做一些基礎知識的科普。

Blink 內核早期架構

以 Chrome 瀏覽器內核 Blink 渲染頁面為例。對早期的 Chrome 瀏覽器而言,每個頁面 Tab 對應一個獨立的 renderer 進程,Renderer 進程中包含了主線程和合成線程。早期 Chrome 內核架構:

其中,主線程主要負責:

  • JavaScript 的計算與執行
  • css 樣式計算
  • Layout 計算
  • 將頁面元素繪製成位圖(paint),也就是光柵化(Raster)
  • 將位圖給合成線程

合成線程則主要負責:

  • 將位圖(GraphicsLayer 層)以紋理(texture)的形式上傳給 GPU
  • 計算頁面的可見部分和即將可見部分(滾動)
  • css 動畫處理
  • 通知 GPU 繪製位圖到屏幕上

OK,雲里霧裡的,什麼東西。其實知道了這兩個線程之後,下一個概念是釐清 CSS 動畫與 js 動畫的細微區別(當然它們都是 Web 動畫)。

 

js 動畫與 CSS 動畫的細微區別

對於 JS 動畫而言,它們運行時的幀率即是主線程和合成線程加起來消耗的時間。對於流暢動畫而言,我們希望它們每一幀的耗時保持在 16.67ms 之內;

而對於 CSS 動畫而言,由於其流程不受主線程的影響,所以希望能得到合成線程的消耗的時間,而合成線程的繪製頻率也反映了滾動和 CSS 動畫的流程性。

上面主要想得出的一個結論是。如果我們能夠知道主線程和合成線程每一幀消耗的時間,那麼我們就能大致得出對應的 Web 動畫的幀率。那麼上面說到的 Frame Timing API 是否可以幫助我們拿到這個時間點呢。

 

什麼是 Frame Timing API ?

Frame Timing API 是 Web Performance Timing API 標準中的其中一位成員。

Web Performance Timing API 是 W3C 推出的一套性能 API 標準,用於幫助開發者對網站各方面的性能進行精確的分析與控制,提升 Web 網站性能。

它包含許多子類 API,完成不同的功能,大致如下(摘自使用性能API快速分析web前端性能,當然你也可以看英文原版介紹:Web Performance Timing API ):

怎麼使用呢?以 Navigation Timing, Performance Timeline, Resource Timing 為例子,對於兼容它的瀏覽器,它以只讀屬性的形式對外暴露掛載在 window.performance 上。

在調試台 console 中打印 window.performance ,查看其中的 timing 屬性:

這對象內一連串的變量表示什麼呢,它表示我們頁面整個加載過程中每一個重要的時間點,可以詳細看看這張圖:

通過這張圖以及上面的 window.performance.timing,我們就可以輕鬆的統計出頁面每個重要節點的耗時,這就是 Web Performance Timing API 的強大之處,感興趣的可以詳細去研究研究,使用在頁面統計上。

Frame Timing API 示意

好的,終於可以回歸正題,藉助 Web Performance Timing API 中的 Frame Timing API,可以輕鬆的拿到每一幀中,主線程以及合成線程的時間。或者更加容易,直接拿到每一幀的耗時。

獲取 Render 主線程和合成線程的記錄,每條記錄包含的信息基本如下,代碼示意,(參考至Developer feedback needed: Frame Timing API):

var rendererEvents = window.performance.getEntriesByType("renderer");
var compositeThreadEvents = window.performance.getEntriesByType("composite");

或者是:

var observer = new PerformanceObserver(function(list) {
var perfEntries = list.getEntries();
for (var i = 0; i < perfEntries.length; i++) {
console.log("frame: ", perfEntries[i]);
}
});

// subscribe to Frame Timing
observer.observe({entryTypes: ['frame']});

每條記錄包含的信息基本如下:

{
sourceFrameNumber: 120,
startTime: 1342.549374253
cpuTime: 6.454313323
}

每個記錄都包括唯一的 Frame Number、Frame 開始時間以及 cpuTime 時間。通過計算每一條記錄的 startTime ,我們就可以算出每兩幀間的間隔,從而得到動畫的幀率是否能夠達到 60 FPS。

不過!看看 Web Performance Timing API 整體的兼容性:

Frame Timing API 雖好,但是,現在 Frame Timing API 的兼容性不算很友好,額,不友好到什麼程度呢。還沒有任何瀏覽器支持,處於實驗性階段,屬於面向未來編程。這你 TM 逗我呢?說了這麼久完全不能用…..

 

法三:藉助 requestAnimationFrame API

費了這麼多筆墨描述 Frame Timing API 但最後因為兼容性問題完全沒辦法使用。不過不代表這麼長篇幅的描述沒有用,從上面的介紹,我們得知,如果我們可以到得到每一幀中的固定一個時間點,那麼兩者相減,也能夠近似得到一幀所消耗的時間。

那麼,我們再另闢蹊徑。這次,我們藉助兼容性不錯的 requestAnimationFrame API。

// 語法
window.requestAnimationFrame(callback);

requestAnimationFrame 大家應該都不陌生,方法告訴瀏覽器您希望執行動畫並請求瀏覽器調用指定的函數在下一次重繪之前更新動畫。

當你準備好更新屏幕畫面時你就應用此方法。這會要求你的動畫函數在瀏覽器下次重繪前執行。回調的次數常是每秒 60 次,大多數瀏覽器通常匹配 W3C 所建議的刷新率。

 

使用 requestAnimationFrame 計算 FPS 原理

原理是,正常而言 requestAnimationFrame 這個方法在一秒內會執行 60 次,也就是不掉幀的情況下。假設動畫在時間 A 開始執行,在時間 B 結束,耗時 x ms。而中間 requestAnimationFrame 一共執行了 n 次,則此段動畫的幀率大致為:n / (B – A)。

核心代碼如下,能近似計算每秒頁面幀率,以及我們額外記錄一個 allFrameCount,用於記錄 rAF 的執行次數,用於計算每次動畫的幀率 :

var rAF = function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
}();

var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();

var loop = function () {
var now = Date.now();
var fs = (now - lastFameTime);
var fps = Math.round(1000 / fs);

lastFameTime = now;
// 不置 0,在動畫的開頭及結尾記錄此值的差值算出 FPS
allFrameCount++;
frame++;

if (now > 1000 + lastTime) {
var fps = Math.round((frame * 1000) / (now - lastTime));
console.log(`${new Date()} 1S內 FPS:`, fps);
frame = 0;
lastTime = now;
};

rAF(loop);
}

loop();

OK,尋找一個有動畫不斷運行的頁面進行測試,可以看到代碼運行如下:

這裏,我使用了我之前製作的一個頁面進行了測試,使用 Chrome 同時調出頁面的 FPS meter,對比兩邊的實時 FPS 值,基本吻合。

測試頁面,Solar System。

對比右上角的 Frame Rate,幀率基本一致。在大部分情況下,這種方法可以很好的得出 Web 動畫的幀率。

如果我們需要統計某個特定動畫過程的幀率,只需要在動畫開始和結尾兩處分別記錄 allFrameCount 這個數值大小,再除以中間消耗的時間,也可以得出特定動畫過程的 FPS 值。

值得注意的是,這個方法計算的結果和真實的幀率肯定是存在誤差的,因為它是將每兩次主線程執行 JavaScript 的時間間隔當成一幀,而非上面說的主線程加合成線程所消耗的時間為一幀。但是對於現階段而言,算是一種可取的方法。

原文鏈接:https://www.cnblogs.com/coco1s/p/8029582.html

站長推薦

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

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

沐鳴登錄測速_Vue項目兩大最佳實踐

一勞永逸的組件註冊

通常在組件使用前,需要引入后再註冊,但如果高頻組件多了,每次都這樣做,不僅新增很多代碼,效率還低!我們應該如何優化呢?

其實,我們可以藉助下webpack的 require.context() 方法來創建自己的(模塊)上下文,從而實現自動動態require組件。

我們先在components文件夾(這裏面都是些高頻組件)添加一個叫global.js的文件,在這個文件里使用require.context 動態將需要的高頻組件統統打包進來,然後在main.js文件中引入global.js的文件。

//  global.js文件
import vue from 'vue'
function changeStr (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
const requireComponent = require.context('./', false, /\.vue$/)
// 查找同級目錄下以vue結尾的組件
const install = () => {
requireComponent.keys().forEach(fileName => {
let config = requireComponent(fileName)
console.log(config) // ./child1.vue 然後用正則拿到child1
let componentName = changeStr(
fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
)
Vue.component(componentName, config.default || config)
})
}
export default {
install // 對外暴露install方法
}

最後我們就可以隨時隨地在頁面中使用這些高頻組件,無需再手動一個個引入了。

高精度權限控制—自定義指令directive

我們通常給一個元素添加 v-if / v-show 來做權限管理,但如果判斷條件繁瑣且多個地方需要判斷,這種方式的代碼不僅不優雅而且冗餘。

針對這種情況,我們可以通過全局自定義指令來處理:我們先在新建個 array.js 文件,用於存放與權限相關的全局函數;

// array.js

export function checkArray (key) {

let arr = ['1', '2', '3', '4', 'demo']

let index = arr.indexOf(key)

if (index > -1) {

return true // 有權限

} else {

return false // 無權限

}

}

然後在將 array 文件掛載到全局中:

// main.js

import { checkArray } from "./common/array";

Vue.directive("permission", {

inserted (el, binding) {

let permission = binding.value; // 獲取到 v-permission的值

if (permission) {

let hasPermission = checkArray(permission);

if (!hasPermission) { // 沒有權限 移除Dom元素

el.parentNode && el.parentNode.removeChild(el);

}

}

}

});

最後我們在頁面中就可以通過自定義指令v-permission 來判斷:

<div class="btns">
<button v-permission="'1'">權限按鈕1</button> // 會显示

<button v-permission="'10'">權限按鈕2</button> // 無显示
<button v-permission="'demo'">權限按鈕3</button> // 會显示

</div>

站長推薦

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

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

沐鳴娛樂業務:_利用JavaScript內置日期和時間格式化時間

一、基礎知識(date對象的方法)

getFullYear( ) 用於返回一個表示年份的4位數

getMonth( ) 返回表示月份的数字,但是返回值是 0(一月) 到 11(十二月) 之間的一個整數

getDate( ) 返回某一天

getHours( ) 返回時間的小時字段

getMinutes( ) 返回時間的分鐘字段

getSeconds( ) 返回時間的秒,返回值是 0 ~ 59 之間的一個整數

二、格式化日期

案例:將此刻的時間格式化(因為時間在流逝,所以結果會不同喲!)

代碼如下:


效果呈現:

三、封裝函數實現格式化日期(方便咱們後面使用)

代碼如下:

效果呈現:

四、總結

雖然這個案例比較簡單但是還是有需要注意的地方哈!比如我們在得到月份的時候必須要+1,否則返回的月份會比真實的月份少1。這也在提醒我們在敲代碼的時候一定要細心細心再細心!

原文鏈接:https://blog.csdn.net/Britney10_21/article/details/116712598

站長推薦

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

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

沐鳴代理:_JavaScript刪除對象的某個屬性的方法

1、delete

delete是刪除對象的屬性沒有任何剩菜,剩下的唯一真正的方法,但它的工作比其“替代”設置慢100倍 object[key] = undefined
var myObject = {
"ircEvent": "PRIVMSG",
"method": "newURI",
"regex": "^http://.*"
};
delete myObject.regex;

console.log(myObject);

delete 操作符會從某個對象上移除指定屬性。成功刪除的時候回返回 true,否則返回 false。但是,以下情況需要重點考慮:

如果你試圖刪除的屬性不存在,那麼delete將不會起任何作用,但仍會返回true

如果對象的原型鏈上有一個與待刪除屬性同名的屬性,那麼刪除屬性之後,對象會使用原型鏈上的那個屬性(也就是說,delete操作只會在自身的屬性上起作用)

任何使用 var 聲明的屬性不能從全局作用域或函數的作用域中刪除。

這樣的話,delete操作不能刪除任何在全局作用域中的函數(無論這個函數是來自於函數聲明或函數表達式)

除了在全局作用域中的函數不能被刪除,在對象(object)中的函數是能夠用delete操作刪除的。

任何用let或const聲明的屬性不能夠從它被聲明的作用域中刪除。

不可設置的(Non-configurable)屬性不能被移除。這意味着像Math, Array, Object內置對象的屬性以及使用Object.defineProperty()方法設置為不可設置的屬性不能被刪除。

2、 obj.field = undefined;

這個選擇不是這個問題的正確答案!但是,如果你小心使用它,你可以大大加快一些算法。如果您delete在循環中使用並且在性能方面存在問題,請閱讀詳細解釋

var obj = {
field: 1
};
obj.field = undefined;

3、數組中使用delete

在數組中,與普通的舊對象不同,使用delete在表單中留下垃圾,null在數組中創建一個“洞”, 而且length不變。

var array = [1, 2, 3, 4];
delete array[2];
/* Expected result --> [1, 2, 4]
* Actual result --> [1, 2, null, 4]
*/

4、數組中使用splice

arrayObject.splice(index,howmany,item1,…..,itemX)

index: 必需。整數,規定添加/刪除項目的位置,使用負數可從數組結尾處規定位置。

howmany: 必需。要刪除的項目數量。如果設置為 0,則不會刪除項目。

item1: 可選。向數組添加的新項目。

var arr = new Array(6)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"
arr[3] = "James"
arr[4] = "Adrew"
arr[5] = "Martin"

document.write(arr + "<br />")
arr.splice(2,0,"William")
document.write(arr + "<br />")
/*
*George,John,Thomas,James,Adrew,Martin
*George,John,William,Thomas,James,Adrew,Martin
*/

來自:https://mp.weixin.qq.com/s/dDAg1Ncq6e2qKD9LH4EWOw

站長推薦

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

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

沐鳴首頁_分享 8 個可以提高開發效率的 JavaScript 庫

現代前端開發相比以前來說幸福很多了,過去為了兼容 IE、火狐等瀏覽器環境焦頭爛額,為了減少代碼加速文件的加載盡可能的編寫原生 JavaScript,能不用插件就不用(插件由於要考慮通用性,會增加很多兼容代碼)。這一切變化首要功能應該要給谷歌瀏覽器提供的開源核心,360 瀏覽器為中國用戶普及高級瀏覽器的努力,網絡提速政策的實時。

隨着 5G 技術的發展普及,未來將有大量的應用場景將由前端來承擔。本文跟大家分享 8 個可以提高開發效率的 JavaScript 庫,不需要花時間自己去編寫常用的基礎函數,避免重複造輪子,讓更多的精力去優化應用場景的邏輯,也提高開發效率。當然作為團隊,可以通過學習這些開源 JavaScript 庫,構建適用於團隊的基礎框架。

1. Lodash

Lodash 是最實用的 JavaScript 庫之一,擁有大量的函數特性集,像數組、對象、字符串、数字等類型的常見處理函數,語法上面通俗易懂,容易上手,也是一個學習編碼技術的好庫。

主頁: https://lodash.com/

一下是簡單的示例代碼:

const _ = require("lodash");
const objIntro = {
title: "devpoint",
city: "Shenzhen",
};
const arrMonths = ["一月", "二月"];
_.forEach(objIntro, (value, key) => {
console.log(`${key}:${value}`);
});
// title:devpoint
// city:Shenzhen
_.forEach(arrMonths, (value, index) => {
console.log(`${index}:${value}`);
});
// 0:一月
// 1:二月

2.Luxon

Luxon 是一個非常流行的 日期/時間 操作庫,是新項目的最佳選擇,因為它使用更現代的 Intl 對象,具有解析、操作和創建與時間相關的所有功能。當然還有一個類似的 JavaScript 庫是 Moment 。

Intl 對象是 ECMAScript 國際化 API 的一個命名空間,它提供了精確的字符串對比、数字格式化,和日期時間格式化 【 詳細介紹 】。

主頁: https://moment.github.io/luxon/

const luxon = require("luxon");

const convertTime = (timestamp, format = "yyyy-MM-dd hh:mm") => {
return luxon.DateTime.fromMillis(timestamp).toFormat(format);
};
const now = luxon.DateTime.now();
const nowUtc = now.toString(); // 2021-06-12T12:07:51.897+08:00
const nowTimestamp = now.toMillis(); // 1623470871897
const formatTime = convertTime(nowTimestamp); // 2021-06-12 12:07
console.log(nowUtc); // 2021-06-12T12:07:51.897+08:00
console.log(nowTimestamp); // 1623470871897
console.log(formatTime); // 2021-06-12 12:07

3.NanoID

創建記錄唯一的 key ,一般採用自遞增的 ID 或者採用 UUID,這裏推薦一個創建唯一 ID 的 JavaScript 庫 NanoID。

NanoID 創建一個由字母数字組成的 ID,通常比 UUID 要小,它是用於創建安全並且唯一 ID 的最小庫之一,本身可以縮小並壓縮到僅只有 108 字節。

主頁: https://github.com/ai/nanoid/

const { nanoid } = require("nanoid");
const key = nanoid(); console.log(key); // U6XRwZsfcDuexQ7m55qdy

4.Passport

在現代 Web 應用程序中,身份驗證可以採用多種形式。傳統上,用戶通過提供用戶名和密碼進行登錄。隨着社交網絡的興起,使用 OAuth 提供商(如 Facebook 或 谷歌)的單點登錄已成為一種流行的身份驗證方法,公開 API 的服務通常需要基於令牌的憑據來保護訪問。如果自己需要去實現整個邏輯還是挺繁瑣的,如果你有這方便的需求,可以考慮 Passport 。

主頁: http://www.passportjs.org/

app.post("/login", passport.authenticate("local"), function (req, res) {
res.redirect("/users/" + req.user.username); });

5.Faker

Faker 是一種將虛假數據快速添加到應用程序中以進行測試的腳本庫,通過它可以生成姓名、圖片、產品信息、銀行信息等虛擬信息。

主頁: https://github.com/marak/Faker.js/

const faker = require("faker");
const randomName = faker.name.findName();
const randomEmail = faker.internet.email();
const randomProductName = faker.commerce.productName();
console.log(randomName); // Dr. Debbie Rowe
console.log(randomEmail); // Tyreek15@gmail.com
console.log(randomProductName); // Intelligent Frozen Keyboard

6.Axios

Axios 是一個非常熟悉的基於 promise 的 HTTP 庫,易於使用,常用於 vue 項目中。

主頁: https://github.com/axios/axios

axios.get("/auth?id=89")
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

7.axios-mock-adapter

axios-mock-adapter 是一款基於 axios 的請求模擬器,用來模擬 HTTP 請求,對於前端項目開發非常實用,可以在後台服務未完成之前按照 API 協議模擬相應的請求及響應。在以 vue 做後台管理項目中,可以使用它來實現請求攔截並模擬後台回復

主頁: https://www.npmjs.com/package/axios-mock-adapter

8.Nodemon

在開發 Web 應用程序時,特別是基於 node.js 的後台服務,Nodemon 是個不容錯過工具。在《 使用 Docker 開發Node應用程序 》中簡單介紹過其使用,用來監聽 node.js 項目中文件的更改並自動重啟服務的工具。可以使開發變得更容易,節省了每次更改時自己重新啟動服務器的時間。

主頁: https://www.npmjs.com/package/nodemon

總結

合理利用開源 JavaScript 庫,避免重複製造輪子,以助於提升效率是個不錯的選擇,同樣對於提升 JavaScript 編碼水平,這些開源庫是個不錯的教材。

來自:https://my.oschina.net/lav/blog/5077207

站長推薦

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

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

沐鳴平台註冊登錄_深入解析如何實現 call、apply 和 bind

這是 js 原生方法原理探究系列的第一篇文章。本文會介紹如何實現 call、apply 和 bind 方法。關於這幾個方法的具體用法,MDN 以及網上的文章已經描述得很清楚,這裏不再贅述。

手寫實現 call

ES3 版本

Function.prototype.myCall = function(thisArg){
if(typeof this != 'function'){
throw new Error('The caller must be a function')
}
if(thisArg === undefined || thisArg === null){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
var args = []
for(var i = 1;i < arguments.length;i ++){
args.push('arguments[' + i + ']')
}
thisArg.fn = this
var res = eval('thisArg.fn(' + args + ')')
delete thisArg.fn
return res
}

ES6 版本

Function.prototype.myCall = function(thisArg,...args){
if(typeof this != 'function'){
throw new Error('The caller must be a function')
}
if(thisArg === undefined || thisArg === null){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
thisArg.fn = this
const res = thisArg.fn(...args)
delete thisArg.fn
return res
}

通過 call 調用函數的時候,可以通過傳給 call 的 thisArg 指定函數中的 this。而只要使得函數是通過 thisArg 調用的,就能實現這一點,這就是我們的主要目標。

實現要點

最終是通過函數去調用 myCall 的,所以 myCall 和 call 一樣掛載在函數原型上。同時,也正因為是通過函數去調用 myCall 的,所以在 myCall 內部我們可以通過 this 拿到 myCall 的調用者,也就是實際執行的那個函數。

按理說, myCall 是掛載在函數原型上,當我們通過一個非函數去調用 myCall 的時候,肯定會拋出錯誤,那麼為什麼還要在 myCall 中檢查調用者的類型,並自定義一個錯誤呢?這是因為,當一個調用者 obj = {} 是一個對象,但是繼承自 Function 的時候( obj.__proto__ = Function.prototype ),它作為一個非函數實際上也是可以調用 myCall 方法的,這時候如果不進行類型檢查以確保它是個函數,那麼後面直接將它當作函數調用的時候,就會拋出錯誤了

傳給 call 的 thisArg 如果是 null 或者 undefined,那麼 thisArg 實際上會指向全局對象;如果 thisArg 是一個基本類型,那麼可以使用 Object() 做一個裝箱操作,將其轉化為一個對象 —— 主要是為了確保後續可以以方法調用的方式去執行函數。那麼可不可以寫成 thisArg = thisArg ? Object(thisArg) : globalThis 呢?其實是不可以的,如果 thisArg 是布爾值 false,那麼會導致 thisArg 最終等於 globalThis,但實際上它應該等於 Boolean {false} 。

前面說過,可以在 myCall 里通過 this 拿到實際執行的那個函數,所以 thisArg.fn = this 相當於將這個函數作為 thisArg 的一個方法,後面我們就可以通過 thisArg 對象去調用這個函數了。

thisArg.fn = this 相當於是給 thisArg 增加了一個 fn 屬性,所以返回執行結果之前要 delete 這個屬性。此外,為了避免覆蓋 thisArg 上可能存在的同名屬性 fn,這裏也可以使用 const fn = Symbol(‘fn’) 構造一個唯一屬性,然後 thisArg[fn] = this 。

ES3 版本和 ES6 版本主要的區別在於參數的傳遞以及函數的執行上:

(1)ES6 因為引入了剩餘參數,所以不管實際執行函數的時候傳入了多少個參數,都可以通過 args 數組拿到這些參數,同時因為引入了展開運算符,所以可以展開 args 參數數組,把參數一個個傳遞給函數執行

(2)但在 ES3 中沒有剩餘參數這個東西,所以在定義 myCall 的時候只接收一個 thisArg 參數,然後在函數體中通過 arguments 類數組拿到所有參數。我們需要的是 arguments 中除第一個元素(thisArg)之外的所有元素,怎麼做呢?如果是 ES6,直接 […arguments].slice(1) 就可以了,但這是 ES3,於是我們只能從索引 1 開始遍歷 arguments,然後 push 到一個 args 數組中了。而且還要注意的是,這裏 push 進去的是字符串形式的參數,這主要是為了方便後續通過 eval 執行函數的時候,將參數一個一個傳遞給函數。

(3)為什麼必須通過 eval 才能執行函數呢?因為我們不知道函數實際上要接收多少個參數,況且也用不了展開運算符,所以只能構造一個可執行的字符串表達式,顯式地傳入函數的所有參數。

手寫實現 apply

apply 的用法和 call 很類似,因此實現也很類似。需要注意的區別是,call 在接受一個 thisArg 參數之後還可以接收多個參數(即接受的是參數列表),而 apply 在接收一個 thisArg 參數之後,通常第二個參數是一個數組或者類數組對象:

fn.call(thisArg,arg1,arg2,...)
fn.apply(thisArg,[arg1,arg2,...])

如果第二個參數傳的是 null 或者 undefined,那麼相當於是整體只傳了 thisArg 參數。

ES3 版本

Function.prototype.myApply = function(thisArg,args){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
if(thisArg === null || thisArg === undefined){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
if(args === null || args === undefined){
args = []
} else if(!Array.isArray(args)){
throw new Error('CreateListFromArrayLike called on non-object')
}
var _args = []
for(var i = 0;i < args.length;i ++){
_args.push('args[' + i + ']')
}
thisArg.fn = this
var res = _args.length ? eval('thisArg.fn(' + _args + ')'):thisArg.fn()
delete thisArg.fn
return res
}

ES6 版本

Function.prototype.myApply = function(thisArg,args){
if(typeof thisArg != 'function'){
throw new Error('the caller must be a function')
}
if(thisArg === null || thisArg === undefined){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
if(args === null || args === undefined){
args = []
}
// 如果傳入的不是數組,仿照 apply 拋出錯誤
else if(!Array.isArray(args)){
throw new Error('CreateListFromArrayLike called on non-object')
}
thisArg.fn = this
const res = thisArg.fn(...args)
delete thisArg.fn
return res
}

實現要點

基本上和 call 的實現是差不多的,只是我們需要檢查第二個參數的類型。

手寫實現 bind

bind 也可以像 call 和 apply 那樣給函數綁定一個 this,但是有一些不同的要點需要注意:

bind 不是指定完 this 之後直接調用原函數,而是基於原函數返回一個內部完成了 this 綁定的新函數

原函數的參數可以分批次傳遞,第一批可以在調用 bind 的時候作為第二個參數傳入,第二批可以在調用新函數的時候傳入,這兩批參數最終會合併在一起,一次傳遞給新函數去執行

新函數如果是通過 new 方式調用的,那麼函數內部的 this 會指向實例,而不是當初調用 bind 的時候傳入的 thisArg。換句話說,這種情況下的 bind 相當於是無效的

ES3 版本

這個版本更接近 MDN 上的 polyfill 版本。

Function.prototype.myBind = function(thisArg){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
var fnToBind = this
var args1 = Array.prototype.slice.call(arguments,1)
var fnBound = function(){
// 如果是通過 new 調用
return fnToBind.apply(this instanceof fnBound ? this:thisArg,args1.concat(args2))
}
// 實例繼承
var Fn = function(){}
Fn.prototype = this.prototype
fnBound.prototype = new Fn()
return fnBound
}

ES6 版本

Function.prototype.myBind = function(thisArg,...args1){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
const fnToBind = this
return function fnBound(...args2){
// 如果是通過 new 調用的
if(this instanceof fnBound){
return new fnToBind(...args1,...args2)
} else {
return fnToBind.apply(thisArg,[...args1,...args2])
}
}
}

實現要點

bind 實現內部 this 綁定,需要藉助於 apply ,這裏假設我們可以直接使用 apply 方法

先看比較簡單的 ES6 版本:

(1) 參數獲取 :因為 ES6 可以使用剩餘參數,所以很容易就可以獲取執行原函數所需要的參數,而且也可以用展開運算符輕鬆合併數組。

(2) 調用方式 :前面說過,如果返回的新函數 fnBound 是通過 new 調用的,那麼其內部的 this 會是 fnBound 構造函數的實例,而不是當初我們指定的 thisArg,因此 this instanceof fnBound 會返回 true,這種情況下,相當於我們指定的 thisArg 是無效的,new 返回的新函數等價於 new 原來的舊函數,即 new fnBound 等價於 new fnToBind,所以我們返回一個 new fnToBind 即可;反之,如果 fnBound 是普通調用,則通過 apply 完成 thisArg 的綁定,再返回最終結果。從這裏可以看出,bind 的 this 綁定,本質上是通過 apply 完成的。

再來看比較麻煩一點的 ES3 版本:

(1) 參數獲取 :現在我們用不了剩餘參數了,所以只能在函數體內部通過 arguments 獲取所有參數。對於 myBind ,我們實際上需要的是除開第一個傳入的 thisArg 參數之外的剩餘所有參數構成的數組,所以這裏可以通過 Array.prototype.slice.call 借用數組的 slice 方法(arguments 是類數組,無法直接調用 slice)

這裏的借用有兩個目的:一是除去 arguments 中的第一個參數,二是將除去第一個參數之後的 arguments 轉化為數組(slice 本身的返回值就是一個數組,這也是類數組轉化為數組的一種常用方法)。同樣地,返回的新函數 fnBound 後面調用的時候也可能傳入參數,再次借用 slice 將 arguments 轉化為數組

(2) 調用方式 :同樣,這裏也要判斷 fnBound 是 new 調用還是普通調用。在 ES6 版本的實現中,如果是 new 調用 fnBound,那麼直接返回 new fnToBind() ,這實際上是最簡單也最容易理解的方式,我們在訪問實例屬性的時候,天然就是按照 實例 => 實例.__proto__ = fnToBind.prototype 這樣的原型鏈來尋找的,可以確保實例成功訪問其構造函數 fnToBInd 的原型上面的屬性;

但在 ES3 的實現中(或者在網上部分 bind 方法的實現中),我們的做法是返回一個 fnToBind.apply(this) ,實際上相當於返回一個 undefined 的函數執行結果,根據 new 的原理,我們沒有在構造函數中自定義一個返回對象,因此 new 的結果就是返回實例本身,這點是不受影響的。

這個返回語句的問題在於,它的作用僅僅只是確保 fnToBind 中的 this 指向 new fnBound 之後返回的實例,而並沒有確保這個實例可以訪問 fnToBind 的原型上面的屬性。實際上,它確實不能訪問,因為它的構造函數是 fnBound 而不是 fnToBind,所以我們要想辦法在 fnBound 和 fnToBind 之間建立一個原型鏈關係。這裡有幾種我們可能會使用的方法:

方法一:

// 這裏的 this 指的是 fnToBind
fnBound.prototype = this.prototype

這樣只是拷貝了原型引用,如果修改 fnBound.prototype ,則會影響到 fnToBind.prototype ,所以不能用這種方法

方法二:

// this 指的是 fnToBind
fnBound.prototype = Object.create(this.prototype)

通過 Object.create 可以創建一個 __proto__ 指向   this.prototype 的實例對象,之後再讓 fnBound.prototype 指向這個對象,則可以在 fnToBind 和 fnBound 之間建立原型關係。但由於 Object.create 是 ES6 的方法,所以無法在我們的 ES3 代碼中使用。

方法三:

// this 指的是 fnToBind
const Fn = function(){}
Fn.prototype = this.prototype
fnBound.prototype = new Fn()

這是上面代碼採用的方法:通過空構造函數 Fn 在 fnToBind 和 fnBound 之間建立了一個聯繫。如果要通過實例去訪問 fnToBind 的原型上面的屬性,可以沿着如下原型鏈查找:

實例 => 實例.__proto__ = fnBound.prototype = new Fn() => new Fn().__proto__ = Fn.prototype = fnToBind.prototype

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

站長推薦

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

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