[JS] ECMAScript 6 - String, Number, Function : compare with c#

 

 

 

字符串的擴展 

jsjavascript

 

Ref: 模板字符串php

策略:${var},放在反引號中!html

經過tag來表示字符串。java

 使用for輸出完整的字符串,記得最後一個strings[strings.length-1]。git

 

c#es6

Ref: 一個很是好的C#字符串操做處理類StringHelper.csgithub

Ref: 字符串(C# 編程指南)正則表達式

Ref: 經常使用C#字符串函數大全express

 

 

正則表達式的擴展

js編程

修飾符:

    • i(intensity)  :大小寫不敏感。
    • g(global)    :全局查找,對於一些特定的函數,將迭代完整的字符串,得到全部的匹配結果,而不只僅在獲得第一個匹配後就中止進行。
    • m(multiple):檢測字符串中的換行符,主要是影響字符串開始標識符^和結束標識符$的使用。
var urlReg     = /(\w+):\/\/([\w.]+)\/(\S*)/;
var myHomepage = "http://www.wanjilong.com/homepage";
var result     = myHomepage.match(urlReg);
console.log(result);
for (var i = 0, len = result.length; i < len; ++i) { console.log(i, result[i]); }

  

Ref: JavaScript 正則表達式

Ref: JavaScript RegExp 對象

var patt = new RegExp(pattern,modifiers);

或者,更簡單的方法

var patt = /pattern/modifiers;

test():一個字符串是否匹配某個模式

exec():檢索字符串中的正則表達式的匹配

Method Description
exec RegExp method that executes a search for a match in a string. It returns an array of information or null on a mismatch.
test RegExp method that tests for a match in a string. It returns true or false.
match String method that executes a search for a match in a string. It returns an array of information or null on a mismatch.
search String method that tests for a match in a string. It returns the index of the match, or -1 if the search fails.
replace String method that executes a search for a match in a string, and replaces the matched substring with a replacement substring.
split String method that uses a regular expression or a fixed string to break a string into an array of substrings.

 

c#

Ref: C# 正則表達式

匹配:匹配了以 'm' 開頭以 'e' 結尾的單詞

using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
   class Program
   {
      private static void showMatch(string text, string expr)
      {
         Console.WriteLine("The Expression: " + expr);
         MatchCollection mc = Regex.Matches(text, expr);
         foreach (Match m in mc)
         {
            Console.WriteLine(m);
         }
      }
      static void Main(string[] args)
      {
         string str = "make maze and manage to measure it";

         Console.WriteLine("Matching words start with 'm' and ends with 'e':");
         showMatch(str, @"\bm\S*e\b");
         Console.ReadKey();
      }
   }
}

替換:替換掉多餘的空格

using System;
using System.Text.RegularExpressions;

namespace RegExApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         string input       = "Hello   World   ";
         string pattern     = "\\s+";
         string replacement = " ";
         Regex rgx = new Regex(pattern);
         string result = rgx.Replace(input, replacement);

         Console.WriteLine("Original String: {0}", input);
         Console.WriteLine("Replacement String: {0}", result);    
         Console.ReadKey();
      }
   }
}

 

 

數值的擴展

js

 

c#

Ref: C#數學計算包 Math.NET

Ref: Math Class【MSDN】

 

 

函數的擴展

js

  • 函數的 length 屬性
(function (a) {}).length            // 1
(function (a = 5) {}).length        // 0
(function (a, b, c = 5) {}).length  // 2

 

指定了默認值後,length屬性將失真。

後文的 rest 參數也不會計入length屬性

(function(...args) {}).length // 0

/**
* 只計算最後一個有默認值參數以前的」未默認初始化「的參數的個數
*/ (
function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1

  

  • 做用域

例一

let x = 1;

function f(y = x) {   # 調用函數時,參數造成一個單獨的做用域
  let x = 2;          # 這一條語句 其實沒用
  console.log(y);
}

f() // 1f

注意:參數中默認爲是let,故鎖住了做用域

例二

let foo = 'outer';

function bar( func = () => foo ) {  // 同理,foo是外層的
  let foo = 'inner';
  console.log(func());
}

bar(); // outer

 

例三:這裏是三個做用域的x。

var x = 1;
function foo( x, y = function() { x = 2; } ) {  // 參數中的x是單獨的做用域 var x = 3; // 函數內部的x是另外一個做用域   y(); // 執行後,內部變量和外部全局變量的值都沒變 console.log(x); } foo() // 3 x // 1yxx

這裏,只有兩個做用域,函數參數和函數內部的做用域是同樣的。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;                                        // 內部變量就指向第一個參數
  y();
  console.log(x);
}

foo() // 2
x // 1xx

  

  • 應用

至關棒的tricky的方法:利用參數默認值,能夠指定某一個參數不得省略,若是省略就拋出一個錯誤。

一個不能夠省略的參數:

一個能夠省略的參數:

function foo(optional = undefined) { ··· }

 

 

  • rest 參數

比過去的argument要好

// arguments變量的寫法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

對象不是數組,而是一個相似數組的對象。
// 因此爲了使用數組的方法,必須使用先將其轉爲數組。

-----------------------------------------------------------------------
// rest參數的寫法 const sortNumbers = (...numbers) => numbers.sort();

// rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法均可以使用。// argumentsArray.prototype.slice.call

 

rest 參數必須在尾部

rest 參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。

// 數組參數的典型用法

function
push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)

 

  • 嚴格模式

只要參數使用了默認值、解構賦值、或者擴展運算符,就不能顯式指定嚴格模式。

產生了矛盾點:"只有從函數體之中,才能知道參數是否應該以嚴格模式執行,可是參數卻應該先於函數體執行"

// 報錯
function doSomething(value = 070) {
  'use strict';  # 在執行這裏前,如何處理參數是個問題 return value; }

那乾脆禁止掉就行了!

 

但並非說,這跟‘嚴格模式’的初衷有衝突,兩種方法能夠規避這種限制。

第一種,是設定全局性的嚴格模式,這是合法的。

'use strict';

function doSomething(a, b = a) {
  // code
}

第二種,是把函數包在一個無參數的當即執行函數裏面。

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

參考:[JS] Why "strict mode" here

 

  • name 屬性
const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

其餘狀況見原文。

 

  • 箭頭函數

一樣的,注意大括號(代碼塊) 是否做爲了返回值。

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

// 雖然能夠運行,但會獲得錯誤的結果
// 因爲引擎認爲大括號是代碼塊,因此執行了一行語句a: 1

// 這時,a能夠被解釋爲語句的標籤,所以實際執行的語句是1;
// 而後函數就結束了,沒有返回值。
let foo = () => { a: 1 };
a: 1a1;foo() // undefined

// 若是箭頭函數只有一行語句,且不須要返回值,能夠採用下面的寫法,
// 就不用寫大括號了
let fn = () => void doesNotReturn();

 

便捷一:箭頭函數能夠與變量解構結合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同於 function full(person) { return person.first + ' ' + person.last; }

便捷二:簡化回調函數

// 正常函數寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數寫法
[1,2,3].map(x => x * x);

便捷三:與 rest 參數相結合。

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail]; headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]]

 

箭頭函數的使用注意點

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。 

this對象的指向是可變的;

可是在箭頭函數中,它是固定的。

做用一

例子一:

function foo() {
  setTimeout( () => { console.log('id:', this.id);   }, 100 );
}

var id = 21;
// 對象{id:42}做爲了參數
foo.call({ id: 42 });
// id: 42

例子二:

Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。

前者的this綁定定義時所在的做用域(即Timer函數),

後者的this指向運行時所在的做用域(即全局對象)。

因此,3100 毫秒以後,timer.s1被更新了 3 次,而timer.s2一次都沒更新。

 

做用二

箭頭函數可讓this指向固定化,這種特性頗有利於封裝回調函數

注意:

this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this

致使內部的this就是外層代碼塊的this。

除了this,如下三個變量在箭頭函數之中也是不存在的,指向外層函數的對應變量:argumentssupernew.target

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

請問下面的代碼之中有幾個this

 

 

(2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。 

(3)能夠用 rest 參數,但不可使用arguments對象,該對象在函數體內不存在。

(4)不可使用yield命令,所以箭頭函數不能用做 Generator 函數。

 

  • 嵌套的箭頭函數

以下,可見箭頭函數帶來的好處,有點builder or pipeline的感受。

function insert(value) {
  return { into: function (array) {
    return { after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]

上面這個函數,可使用箭頭函數改寫。【記得加圓括號

可讀性提高,也能夠採用下面的寫法

const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12

 

箭頭函數還有一個功能,就是能夠很方便地改寫 λ 演算。

λ 演算】就是一種特殊的語法所書寫的匿名函數。

參見:神奇的λ演算

 

  • call, apply

在原生js中會有三個很常見的函數,call, apply, bind。他們的做用就是改變當前函數的this指針。

Ref: 如何理解和熟練運用js中的call及apply?

-- 理論 --

當一個object沒有某個方法,可是其餘的有,咱們能夠藉助call或apply用其它對象的方法來操做!

另一個對象whiteDog = {food:"bone"},

咱們不想對它從新定義say方法,咱們能夠經過call或apply用blackCat的say方法:blackCat.say.call(whiteDog)。

此時,say:function()中的this指針就成了whiteDog,this.food就變成了「bone」。

-- 實戰 --

用的比較多的,經過document.getElementsByTagName選擇的dom 節點是一種相似array的array。
它不能應用Array下的push,pop等方法。咱們能夠經過:
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
這樣domNodes就能夠應用Array下的全部方法了。


  • 雙冒號運算符

箭頭函數能夠綁定this對象,大大減小了顯式綁定this對象的寫法(callapplybind)。

可是,箭頭函數並不適用於全部場合,因此如今有一個提案,提出了「函數綁定」(function bind)運算符,用來取代callapplybind調用。

【提案暫時不看】

 

  • 尾調用優化

尾調用(Tail Call)是函數式編程的一個重要概念,自己很是簡單,一句話就能說清楚,就是指某個函數的最後一步是調用另外一個函數。

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

 

"尾調用"因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就能夠了。

大大節省內存!

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同於
function f() {
  return g(3);
}
f();

// 等同於
g(3);

反例子:

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;  // 由於這裏,因此被迫在調用inner時還須要保留住var one,也就不是tail call了
  }
  return inner(a);
}

 

NB: ES6 的尾調用優化只在嚴格模式下開啓,正常模式是無效的。

這是由於在正常模式下,函數內部有兩個變量,能夠跟蹤函數的調用棧。

    • func.arguments:返回調用時函數的參數。
    • func.caller:返回調用當前函數的那個函數。

 

  • 尾遞歸

對於尾遞歸來講,因爲只存在一個調用幀,因此永遠不會發生「棧溢出」錯誤。  

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

--------------------------------------------------
改寫成尾遞歸,只保留一個調用記錄,複雜度由O(n) --> O(1) 。
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120

 

可見,訣竅就在於:把全部用到的內部變量改寫成函數的參數。

還有就是:只須要知道循環能夠用遞歸代替,而一旦使用遞歸,就最好使用尾遞歸。

其餘部分,柯里化(currying)【將多參數的函數轉換成單參數的形式】詳見連接。

 

尾遞歸優化只在嚴格模式下生效。

在正常模式下,或者那些不支持該功能的環境中,就是本身實現尾遞歸優化。 

減小調用棧?就是採用「循環」換掉「遞歸」。

詳見原連接。

 

  • 函數參數的尾逗號

ES2017 容許函數的最後一個參數有尾逗號(trailing comma)。

這樣的規定也使得,函數參數數組對象的尾逗號規則,保持一致了。

 

 

函數是一種對象

  • 處處都是」Object" 

基本值類型不是對象(number、string、Boolean、Undefined);

剩下的引用類型(函數、數組、null...)都是對象。

 

對象是經過函數建立的,而函數又是一種對象。那麼這是爲何呢?這就牽扯到prototype原型。 

 

相關文章
相關標籤/搜索