( 譯、持續更新 ) JavaScript 上分小技巧(四)


后續如有內容,本篇將會照常更新並排滿15個知識點,以下是其他幾篇譯文的地址:

第一篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(一)

第二篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(二)

第三篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(三)

#59 - ES6,var vs let

var關鍵字定義的變量根據定義的環境用於function內,function外或者全局;而let定義的變量只用於"塊"范圍。

function varvslet() {
  console.log(i); // i is undefined  由於變量提升
  // console.log(j); // ReferenceError: j is not defined

  for( var i = 0; i < 3; i++ ) {
    console.log(i); // 0, 1, 2
  };

  console.log(i); // 3
  // console.log(j); // ReferenceError: j is not defined

  for( let j = 0; j < 3; j++ ) {
    console.log(j);
  };

  console.log(i); // 3
  // console.log(j); // ReferenceError: j is not defined
}

從上面代碼可以看出,let不會得到提升,且僅用於該變量使用的塊范圍。

let存在自己封閉的塊范圍,並且在循環場景中會確保在上一次循環迭代結束重新分配它的值:

for (var i = 0; i < 5; ++i) {
  setTimeout(function () {
    console.log(i); // 由於異步,輸出 5, 5, 5, 5, 5
  }, 100);  
}

for (let i = 0; i < 5; ++i) {
  setTimeout(function () {
    console.log(i); // 輸出 0, 1, 2, 3, 4 
  }, 100);  
}

參考資料:

Let keyword vs var keyword

For and against let

Explanation of let and block scoping with for loops

#58 - 跳出/繼續執行循環語句

對於for循環,我們通常用break來跳出循環:

const a = [0, 1, 2, 3, 4];
for (var i = 0; i < a.length; i++) {
  if (a[i] === 2) {
    break; // stop the loop
  }
  console.log(a[i]);
}
//> 0, 1

在forEach中,就不能用break來跳出循環了,與之功能最相近的是return了:

[0, 1, 2, 3, 4].forEach(function(val, i) {
  if (val === 2) {
    // 我們該怎么跳出循環呢?
    return true;
  }
  console.log(val);
});
//> 0, 1, 3, 4

.some是Array原型上的一個方法,用於檢測該數組中是否存在滿足提供的規則的元素,當檢測到元素滿足條件並且返回true,則會停止繼續執行。.some MDN詳細資料

const isBiggerThan10 = numb => numb > 10;

[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true

[0, 1, 2, 3, 4].some(function(val, i) {
  if (val === 2) {
    return true;
  }
  console.log(val); // your code
});
//> 0, 1

使用.some,實現了和forEach相似的功能,並且在滿足給定條件時會跳出循環。

使用.some,我們必須返回相反的值。當return的是false,循環會繼續執行。

const isTwoPresent = [0, 1, 2, 3, 4].some(function(val, i) {
  if (val === 2) {
    return true; // break
  }
  console.log(val);
});
console.log(isTwoPresent);
//> true

// 當return的是true時,console.log(val)會輸出:0,1;當return的是false時,console.log(val)會輸出0,1,3,4

#57 - JavaScript中的逗號運算符

眾所周知,逗號運算符可以作為分隔符抑或同時執行多個變量聲明,如:

for(var i=0, j=0; i<5; i++, j++, j++){
    console.log("i:"+i+", j:"+j);
}
// 輸出:
i:0, j:0
i:1, j:2
i:2, j:4
i:3, j:6
i:4, j:8

然而當放在表達式中,他會從左到右的評估每一個表達式,並且返回最右表達式的結果。

function a(){console.log('a'); return 'a';} 
function b(){console.log('b'); return 'b';} 
function c(){console.log('c'); return 'c';}

var x = (a(), b(), c());
console.log(x);
// 輸出:
"a"
"b"
"c"

"c"

注意:逗號運算符在JavaScript中優先級是最低的,所以,當沒有大括號的時候,表達式會變成:var (x = a()), b(), c();

#56 - JavaScript 拷貝到剪切板

現在有個需求,我們想要將字符串拷貝到剪切板中以供粘貼,用js怎么操作呢?

其實實現起來很簡單,我們需要一個input來作為拷貝內容的載體,然后選擇拷貝內容,執行execCommand命令即可。execCommand('copy')命令會將實際選中的值拷貝到剪切板,當然,也可以是cut,paste等命令。

<input type="text" class="copy" value="hello world" />
<input type="text" class="paste" />
document.querySelector('.do-copy').addEventListener('click',function(e){
    document.querySelector('.copy').select();
    document.execCommand('copy');
},!1)

參考資料:execCommand MDN

然而發現各種測paste命令,好像存在問題,stackoverflow上的解答也不盡人意...(沒能滿意的跑順暢)

          ### 2016-10-02 更新 ###          

# 55 - 讓數組進行多次循環

有些時候,我們需要對一個數組進行多次循環。以下代碼展示了如何進行一個數組的循環。

var aList = ['A','B','C','D','E'];

function make_looper( arr ){

    arr.loop_idx = 0;

    // 返回當前循環到的元素
    arr.current = function(){

      if( this.loop_idx < 0 ){// 第一次驗證
        this.loop_idx = this.length - 1;// 更新循環索引
      }

      if( this.loop_idx >= this.length ){// 第二次驗證
        this.loop_idx = 0;// 更新循環索引
      }

      return arr[ this.loop_idx ];//return item
    };

    // 對循環索引進行一次加操作,並且返回當前元素
    arr.next = function(){
      this.loop_idx++;
      return this.current();
    };
    // 對循環索引進行一次減操作,並且返回當前元素
    arr.prev = function(){
      this.loop_idx--;
      return this.current();
    };
}

make_looper( aList);

aList.current();// -> A
aList.next();// -> B
aList.next();// -> C
aList.next();// -> D
aList.next();// -> E
aList.next();// -> A
aList.pop() ;// -> E
aList.prev();// -> D
aList.prev();// -> C
aList.prev();// -> B
aList.prev();// -> A
aList.prev();// -> D

使用%操作符便更好了:

var aList = ['A','B','C','D','E'];

function make_looper( arr ){

    arr.loop_idx = 0;

    // 返回當前循環到的元素
    arr.current = function(){
      this.loop_idx = ( this.loop_idx ) % this.length;// 沒有驗證 !!
      return arr[ this.loop_idx ];
    };

    // 對循環索引進行一次加操作,並且返回當前元素
    arr.next = function(){
      this.loop_idx++;
      return this.current();
    };

    // 對循環索引進行一次減操作,並且返回當前元素
    arr.prev = function(){
      this.loop_idx += this.length - 1;
      return this.current();
    };
}

make_looper( aList);

aList.current();// -> A
aList.next();// -> B
aList.next();// -> C
aList.next();// -> D
aList.next();// -> E
aList.next();// -> A
aList.pop() ;// -> E
aList.prev();// -> D
aList.prev();// -> C
aList.prev();// -> B
aList.prev();// -> A
aList.prev();// -> D

# 53 - 獲取文件擴展名

如何獲取文件擴展名?

var file1 = "50.xsl";
var file2 = "30.doc";
getFileExtension(file1); //return xsl
getFileExtension(file2); //return doc

function getFileExtension(filename) {
  /*TODO*/
}

解決方案一:

function getFileExtension1(filename) {
  return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename)[0] : undefined;
}

解決方案二:

function getFileExtension2(filename) {
  return filename.split('.').pop();
}

然而這兩個解決方案並不能滿足一些特殊情況,下面是一個更好的解決方案。

解決方案三(使用slice和lastIndexOf):

function getFileExtension3(filename) {
  return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}

console.log(getFileExtension3('')); // ''
console.log(getFileExtension3('filename')); // ''
console.log(getFileExtension3('filename.txt')); // 'txt'
console.log(getFileExtension3('.hiddenfile')); // ''
console.log(getFileExtension3('filename.with.many.dots.ext')); // 'ext'

日常解釋以上代碼:

. String.lastIndexOf() 方法返回指定值的最后一個符合條件的值(在此案例中是"."),如果沒有值則返回-1。

. "filename"和".hiddenfile"執行"lastIndexOf"方法后返回的值分別是0和-1。無符號右位移操作符 將-1轉化為4294967295,-2轉化為4294967294,這個小技巧用以確保在一些特殊情況下文件名不會被改變(當取"."的lastIndexOf為0或者-1時,截取的便是整個文件名,無符號右位移2位,這時則變成了Integer的最大值,免去了"沒有擴展名、只有擴展名"這兩個條件的判斷)。

. String.prototype.slice() 根據上面的計算方式得出的index來提取文件擴展名。如果返回的index值大與文件名的長度,則返回""。

解決方案四(使用正則,好友 @chengby 提供):

function getExt(file){
  return file.match(/\S+\.+(\S*)/)?file.match(/\S+\.+(\S*)/)[1]:"";
}

對比:

解決方案一
''                                                 undefined
'filename'                                     undefined
'filename.txt'                                'txt'
'.hiddenfile'                                  'hiddenfile'
'filename.with.many.dots.ext'        'ext'
解決方案二
''                                                ''
'filename'                                    'filename'
'filename.txt'                               'txt'
'.hiddenfile'                                 'hiddenfile'
'filename.with.many.dots.ext'        'ext'
解決方案三
''                                                 ''
'filename'                                     ''
'filename.txt'                                'txt'
'.hiddenfile'                                  ''
'filename.with.many.dots.ext'        'ext'

解決方案四
''                                                 ''
'filename'                                     ''
'filename.txt'                                'txt'
'.hiddenfile'                                  ''
'filename.with.many.dots.ext'        'ext'

          ### 2016-06-02 更新 ###          

# 52 - "new"操作的返回值(構造函數的返回值)

在一些情況下,我們會使用"new"來為對象指定一個新的實例。本章將展示一些使用"new" 生成實例背后發生的事情。

"new" 在JavaScript中是一個操作符,在合理的情況下會返回一個對象的實例。這意味着我們需要一個構造函數:

function Thing() {
  this.one = 1;
  this.two = 2;
}

var myThing = new Thing();

myThing.one // 1
myThing.two // 2

注意:this 指向的是通過"new"所創建的新對象。如過直接調用Thing(),而不是通過new 操作,則不會創建對象,這時候的this 指向的是全局對象,也就是window 。

1.這時候你會突然發現多了2個全局變量,one和two。

2.myThing 現在是undefined,因為Thing()沒有返回任何值。

現在,我們得到下一個例子,看着不怎么可靠的例子。這里我們對構造函數加了些料:

function Thing() {
  this.one = 1;
  this.two = 2;

  return 5;
}

var myThing = new Thing();

現在myThing等於什么呢?5?一個對象?

結果是:

myThing.one // 1
myThing.two // 2

有趣的是,我們並沒有看到理應從構造函數中返回的數值5 。這很奇怪,不是么?函數發生了什么?5在哪?那么再試試其他的看看。

我們return一個非原始類型看看,返回一個對象:

function Thing() {
  this.one = 1;
  this.two = 2;

  return {
    three: 3,
    four: 4
  };
}

var myThing = new Thing();

我們console下,看看是什么樣的結果:

console.log(myThing);
/**************************************************
**   Object {three: 3, four: 4}                  
**   this.one 和 this.two 竟然不見了,發生了什么?   
***************************************************/

這里正是我們需要學習的:當你結合"new"關鍵字調用一個函數的時候,你可以使用this 關鍵字設置屬性(你可能已經知道了)。如果使用new關鍵字操作存在返回原始值的函數,將不會得到你所期望的值,而是返回這個函數所構造的實例(你所設置的屬性,如 this.one = 1 ; )

然而,當返回值是非原始類型的時候,如object、array,或者function,那么將覆蓋這個實例,並且返回這個非原始類型的值,完全將你對這個this上的屬性指定給覆蓋了。

          ### 2016-04-14 更新 ###          

#51 - 輕松的監聽DOM事件

大部分人依舊這么做:

· element.addEventListener('type', obj.method.bind(obj))
· element.addEventListener('type', function (event) {})
· element.addEventListener('type', (event) => {})

上面的例子都可以創建一個新的匿名事件處理,當這些事件不再被需要的時候,不會被刪除。當不再需要的事件被用戶意外操作或者事件冒泡而觸發,則可能導致性能問題或意外的邏輯bug。

安全的事件處理模式應當如下:

參考使用

const handler = function () {
  console.log("Tada!")
}
element.addEventListener("click", handler)
// 之后
element.removeEventListener("click", handler)

對函數命名並且移除事件

element.addEventListener('click', function click(e) {
  if (someCondition) {
    return e.currentTarget.removeEventListener('click', click);
  }
});

更好的方法:

function handleEvent (eventName, {onElement, withCallback, useCapture = false} = {}, thisArg) {
  const element = onElement || document.documentElement

  function handler (event) {
    if (typeof withCallback === 'function') {
      withCallback.call(thisArg, event)
    }
  }

  handler.destroy = function () {
    return element.removeEventListener(eventName, handler, useCapture)
  }

  element.addEventListener(eventName, handler, useCapture)
  return handler
}

// 需要用到的時候
const handleClick = handleEvent('click', {
  onElement: element,
  withCallback: (event) => {
    console.log('Tada!')
  }
})

// 需要移除的時候
handleClick.destroy()

          ### 2016-03-21 更新 ###          

#50 - 實用的控制台打印技巧

在執行代碼的時候,你可以想要知道函數中某個變量值是否發生改變抑或查看代碼執行是否有效,很多人習慣用"alert"來在適當的地方彈出顯示,然而這是不方便的,一方面alert能顯示的結果有限(如打印object時將會是[object Object]),另一方面它阻礙了代碼的繼續執行。

我們應該更需要去熟悉使用瀏覽器的開發工具--Console(用於顯示數據)和Sources(用於斷點跟蹤)。這里我們不多做介紹,只給出相應的圖片展示吧:

console.log(value);

使用斷點:

更多學習應用控制台的文章:Debugging JavaScript(Chrome) 、 Set a conditional breakpoint(FireFox) 、 Debugger(Edge) 、Firebug控制台詳解

#49 - JS中最簡單的方式獲取時間戳

我們經常需要獲取時間戳進行計算,有好幾種方式來獲取時間戳,目前最簡單也是最快的方法是:

const timestamp = Date.now();

或者

const timestamp = new Date().getTime();

獲取具體時間 yyyy-mm-dd 的時間戳,我們可以通過給Date構造函數傳入一個參數,例如:

const timestamp = new Date('2012-06-08').getTime()

也可以在聲明日期對象時添加一個+符號,如下所示:

const timestamp = +new Date()

或者獲取指定日期的時間戳

const timestamp = +new Date('2012-06-08')

引擎中調用了Date對象valueOf方法,返回一元運算符"+"配合返回的值調用toNumber()。更多詳細說明請查看以下鏈接:

Date.prototype.valueOf 
一元運算符"+" 
toNumber() 

#48 - 內置函數reduce的用法
正如文檔所寫,reduce()方法應用一個函數以針對數組的值(從左到右)減至最后一個值。
reduce()
reduce()方法接收兩個參數(M:mandatory,O:options):
 (M)一個回調函數,用於處理與先前的計算結果和下一個元素。
 (O)被作為回調函數的第一個調用的參數的初始值。
所以,我們來看看普通用法,后面再來一個更先進的。
常見的用法(積累,連接)
假如我們在購物,並且購物車足夠滿,讓我們計算價格總和:

// 現在的價格
var items = [{price: 10}, {price: 120}, {price: 1000}];
// reducer函數
var reducer = function add(sumSoFar, nextPrice) { return sumSoFar + nextPrice.price; };
// 處理...
var total = items.reduce(reducer, 0);
console.log(total); // 1130

函數的可選參數在一般情況下是0,它可以是個對象,也可以是個數組。
現在,我們獲得一張20元的代金券:

var total = items.reduce(reducer,-20);
console.log(total); // 1110

高端用法(組合)
落后的思想是將reducers分別寫成單獨的功能,並最終計算出一個新的reducers的功能。
為了說明這一點,讓我們創建一個對象與一些能夠計算不同貨幣美元的總價格的reducers功能。

var reducers = {
  totalInDollar: function(state, item) {
    state.dollars += item.price;
    return state;
  },
  totalInEuros : function(state, item) {
    state.euros += item.price * 0.897424392;
    return state;
  },
  totalInYen : function(state, item) {
    state.yens += item.price * 113.852;
    return state;
  },
  totalInPounds : function(state, item) {
    state.pounds += item.price * 0.692688671;
    return state;
  }
  // 更多...
};

然后,我們創建一個新的功能:
 · 負責應用reduce的各部分功能。
 · 將返回一個新的回調函數。

var combineTotalPriceReducers = function(reducers) {
  return function(state, item) {
    return Object.keys(reducers).reduce(
      function(nextState, key) {
        reducers[key](state, item);
        return state;
      },
      {}      
    );
  }
};

現在,讓我們看看怎么使用這個:

var bigTotalPriceReducer = combineTotalPriceReducers(reducers);
var initialState = {dollars: 0, euros:0, yens: 0, pounds: 0};
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);
/*
Object {dollars: 1130, euros: 1015.11531904, yens: 127524.24, pounds: 785.81131152}
*/

我希望這個方法能夠在你所需要的時候給你提供一個reduce使用的思路。
#47 - 基本聲明
下面是javascript中聲明變量的不同方式。console.log能夠很好的解釋這里將發生什么。

var y, x = y = 1 //== var x; var y; x = y = 1
console.log('_> 1:', `x = ${x}, y = ${y}`)
// 將會打印
//_> 1: x = 1, y = 1

首先,我們設置兩個變量,在這里沒更多的操作。

;(() => { 
  var x = y = 2 // == var x; y = 2;
  console.log('2.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 2.1:', `x = ${x}, y = ${y}`)
// 將會打印
//2.0: x = 2, y = 2
//_> 2.1: x = 1, y = 2

正如你所見的,這里的代碼只改變了全局的y,因為我們沒有在閉包中聲明變量。

;(() => { 
  var x, y = 3 // == var x; var y = 3;
  console.log('3.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 3.1:', `x = ${x}, y = ${y}`)
// 將會打印
//3.0: x = undefined, y = 3
//_> 3.1: x = 1, y = 2

現在我們通過var來聲明變量。這意味着他們只存在封閉的語境中。

;(() => { 
  var y, x = y = 4 // == var x; var y; x = y = 4
  console.log('4.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 4.1:', `x = ${x}, y = ${y}`)
// 將會打印
//4.0: x = 4, y = 4
//_> 4.1: x = 1, y = 2

這兩個變量都通過var聲明並且只會給定了值。因為local>global,所以x和y在本地封閉語境中,意味着全局的x和y沒做改變。

x = 5 // x = 5
console.log('_> 5:', `x = ${x}, y = ${y}`)
// 將會打印
//_> 5: x = 5, y = 2

這最后一行是明確的。
更多的變量相關信息:MDN
#46 - js純粹的檢測文檔加載完畢
使用javascript的 readyState 以跨瀏覽器的方式來檢測文檔是否加載。

if (document.readyState === 'complete') {
    // 頁面已經完全加載
}

你能夠檢查文檔是否加載...

let stateCheck = setInterval(() => {
    if (document.readyState === 'complete') {
    clearInterval(stateCheck);
     // document ready
  }
}, 100);

或者使用onreadystatechange...

document.onreadystatechange = () => {
  if (document.readyState === 'complete') {
   // document ready
  }
};

使用document.readyState === 'interactive'檢測DOM是否ready。
          ### 2016-02-18 更新 ###          


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM